diff --git a/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsX.java b/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsX.java deleted file mode 100644 index 1986e6fb93..0000000000 --- a/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsX.java +++ /dev/null @@ -1,44 +0,0 @@ -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.PacketCheck; -import ac.grim.grimac.player.GrimPlayer; -import ac.grim.grimac.utils.nmsutil.BlockBreakSpeed; -import com.github.retrooper.packetevents.event.PacketReceiveEvent; -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.wrapper.play.client.WrapperPlayClientPlayerDigging; - -@CheckData(name = "BadPacketsX", experimental = true) -public class BadPacketsX extends Check implements PacketCheck { - public BadPacketsX(GrimPlayer player) { - super(player); - } - - public final boolean noFireHitbox = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_15_2); - - public final void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging dig, StateType block) { - if (dig.getAction() != DiggingAction.START_DIGGING && dig.getAction() != DiggingAction.FINISHED_DIGGING) - return; - - // 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; - - if (invalid && flagAndAlert("block=" + block.getName() + ", type=" + dig.getAction()) && shouldModifyPackets()) { - event.setCancelled(true); - player.onPacketCancel(); - } - } -} diff --git a/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsZ.java b/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsZ.java deleted file mode 100644 index d8e401e8db..0000000000 --- a/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsZ.java +++ /dev/null @@ -1,105 +0,0 @@ -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.PacketCheck; -import ac.grim.grimac.player.GrimPlayer; -import ac.grim.grimac.utils.anticheat.MessageUtil; -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 = "BadPacketsZ", experimental = true) -public class BadPacketsZ extends Check implements PacketCheck { - public BadPacketsZ(final GrimPlayer player) { - super(player); - } - - private boolean lastBlockWasInstantBreak = false; - private Vector3i lastBlock, lastCancelledBlock, lastLastBlock = null; - private final int exemptedY = player.getClientVersion().isOlderThan(ClientVersion.V_1_8) ? 255 : (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14) ? -1 : 4095); - - // The client sometimes sends a wierd cancel packet - private boolean shouldExempt(final Vector3i pos) { - // lastLastBlock is always null when this happens, and lastBlock isn't - if (lastLastBlock != null || lastBlock == null) - return false; - - // on pre 1.14.4 clients, the YPos of this packet is always the same - if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) && pos.y != exemptedY) - return false; - - // and if this block is not an instant break - return player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || getBlockDamage(player, pos) < 1; - } - - 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; - lastLastBlock = lastBlock; - lastBlock = pos; - } - - if (dig.getAction() == DiggingAction.CANCELLED_DIGGING) { - final Vector3i pos = dig.getBlockPosition(); - - 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()) { - event.setCancelled(true); - player.onPacketCancel(); - resyncPosition(player, pos); - } - } - } - } - - lastCancelledBlock = pos; - lastLastBlock = null; - lastBlock = null; - return; - } - - 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()) { - 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)) { - lastLastBlock = null; - lastBlock = null; - } - } - } -} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/AirLiquidBreak.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/AirLiquidBreak.java new file mode 100644 index 0000000000..162d202640 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/AirLiquidBreak.java @@ -0,0 +1,45 @@ +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.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; + +@CheckData(name = "AirLiquidBreak", experimental = true) +public class AirLiquidBreak extends Check implements BlockBreakCheck { + public AirLiquidBreak(GrimPlayer player) { + super(player); + } + + public final boolean noFireHitbox = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_15_2); + + @Override + public void onBlockBreak(BlockBreak blockBreak) { + if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) { + return; + } + + final StateType block = blockBreak.block.getType(); + + // the block does not have a hitbox + final 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()) { + blockBreak.cancel(); + } + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/FarBreak.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/FarBreak.java new file mode 100644 index 0000000000..b754e23ccd --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/FarBreak.java @@ -0,0 +1,44 @@ +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 org.bukkit.util.Vector; + +@CheckData(name = "FarBreak", experimental = true) +public class FarBreak extends Check implements BlockBreakCheck { + public FarBreak(GrimPlayer player) { + super(player); + } + + @Override + public void onBlockBreak(BlockBreak blockBreak) { + if (player.compensatedEntities.getSelf().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.getSelf().getAttributeValue(Attributes.PLAYER_BLOCK_INTERACTION_RANGE); + double threshold = player.getMovementThreshold(); + maxReach += Math.hypot(threshold, threshold); + + if (min > maxReach * maxReach) { + if (flagAndAlert("distance=" + Math.sqrt(min)) && shouldModifyPackets()) { + blockBreak.cancel(); + } + } + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/FastBreakA.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/FastBreakA.java new file mode 100644 index 0000000000..3c637c6ac0 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/FastBreakA.java @@ -0,0 +1,122 @@ +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 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.StateTypes; +import com.github.retrooper.packetevents.util.Vector3i; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; + +// 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 = "FastBreakA") +public class FastBreakA extends Check implements BlockBreakCheck { + public FastBreakA(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 (isDisabled()) return; + + if (blockBreak.action == DiggingAction.START_DIGGING) { + WrappedBlockState block = player.compensatedWorld.getWrappedBlockStateAt(blockBreak.position); + + // 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 = 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) && 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 + ",Balance=" + blockBreakBalance) && shouldModifyPackets()) { + blockBreak.cancel(); + } + } + + lastFinishBreak = 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 (!isDisabled() && targetBlock != null && ( + player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) + ? event.getPacketType() == PacketType.Play.Client.ANIMATION + : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) + )) { + 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); + } + + private boolean isDisabled() { + return player.getClientVersion().isOlderThan(ClientVersion.V_1_9) && GrimAPI.INSTANCE.getConfigManager().isExperimentalChecks(); + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/FastBreakB.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/FastBreakB.java new file mode 100644 index 0000000000..03d4bf32b7 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/FastBreakB.java @@ -0,0 +1,135 @@ +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.math.VectorUtils; +import ac.grim.grimac.utils.nmsutil.BlockBreakSpeed; +import ac.grim.grimac.utils.nmsutil.Ray; +import ac.grim.grimac.utils.nmsutil.ReachUtils; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +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.world.BlockFace; +import com.github.retrooper.packetevents.util.Vector3d; +import com.github.retrooper.packetevents.util.Vector3f; +import com.github.retrooper.packetevents.util.Vector3i; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@CheckData(name = "FastBreakB", experimental = true) +public class FastBreakB extends Check implements BlockBreakCheck { + public FastBreakB(GrimPlayer playerData) { + super(playerData); + } + + private Vector3i targetBlock = null; + private double progress; + + @Override + public void onBlockBreak(BlockBreak blockBreak) { + if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9)) { + return; + } + + if (blockBreak.action == DiggingAction.START_DIGGING) { + targetBlock = blockBreak.position; + progress = BlockBreakSpeed.getBlockDamage(player, targetBlock); + } + + if (blockBreak.action == DiggingAction.FINISHED_DIGGING && progress < 1) { + if (flagAndAlert(String.format("progress=%.2f", progress)) && shouldModifyPackets()) { + blockBreak.cancel(); + player.onPacketCancel(); + } + } + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + if (targetBlock == null || progress >= 1) { + return; + } + + if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) { + if (!player.packetStateData.isSlowedByUsingItem() || player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_7_10)) { + if (player.compensatedEntities.getSelf().inVehicle() || didRayTraceHit() && isWithinRange()) { + progress += BlockBreakSpeed.getBlockDamage(player, targetBlock); + } + } + } + } + + private boolean isWithinRange() { + double min = Double.MAX_VALUE; + for (double d : player.getPossibleEyeHeights()) { + SimpleCollisionBox box = new SimpleCollisionBox(targetBlock); + 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.getSelf().getAttributeValue(Attributes.PLAYER_BLOCK_INTERACTION_RANGE); + double threshold = player.getMovementThreshold(); + maxReach += Math.hypot(threshold, threshold); + + return min <= maxReach * maxReach; + } + + private boolean didRayTraceHit() { + SimpleCollisionBox box = new SimpleCollisionBox(targetBlock); + + // Start checking if player is in the block + double minEyeHeight = Collections.min(player.getPossibleEyeHeights()); + double maxEyeHeight = Collections.max(player.getPossibleEyeHeights()); + + SimpleCollisionBox eyePositions = new SimpleCollisionBox(player.lastX, player.lastY + minEyeHeight, player.lastZ, player.lastX, player.lastY + maxEyeHeight, player.lastZ); + 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 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.getSelf().getAttributeValue(Attributes.PLAYER_BLOCK_INTERACTION_RANGE); + for (double d : player.getPossibleEyeHeights()) { + for (Vector3f lookDir : possibleLookDirs) { + Vector3d starting = new Vector3d(player.lastX, player.lastY + d, player.lastZ); + Ray trace = new Ray(player, starting.getX(), starting.getY(), starting.getZ(), lookDir.getX(), lookDir.getY()); + Pair intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance)); + + if (intercept.getFirst() != null) return true; + } + } + + return false; + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/MultiBreak.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/MultiBreak.java new file mode 100644 index 0000000000..b48137dc44 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/MultiBreak.java @@ -0,0 +1,72 @@ +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.ClientVersion; +import com.github.retrooper.packetevents.protocol.player.DiggingAction; +import com.github.retrooper.packetevents.protocol.world.BlockFace; +import com.github.retrooper.packetevents.util.Vector3i; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; + +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 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.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) { + 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 (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) { + hasBroken = false; + } + } + + @Override + public void onPredictionComplete(PredictionComplete predictionComplete) { + if (player.getClientVersion().isNewerThan(ClientVersion.V_1_8) && !player.skippedTickInActualMovement && predictionComplete.isChecked()) { + for (String verbose : flags) { + flagAndAlert(verbose); + } + } + + flags.clear(); + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/NoBreakDelay.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/NoBreakDelay.java new file mode 100644 index 0000000000..9d75f86483 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/NoBreakDelay.java @@ -0,0 +1,59 @@ +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.anticheat.update.PredictionComplete; +import com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.player.ClientVersion; +import com.github.retrooper.packetevents.protocol.player.DiggingAction; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; + +@CheckData(name = "NoBreakDelay", experimental = true) +public class NoBreakDelay extends Check implements BlockBreakCheck { + public NoBreakDelay(GrimPlayer player) { + super(player); + } + + private boolean hasFinished; + private int flags; + + @Override + public void onBlockBreak(BlockBreak blockBreak) { + if (blockBreak.action == DiggingAction.START_DIGGING) { + if (hasFinished) { + if (player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8)) { + if (flagAndAlert() && shouldModifyPackets()) { + blockBreak.cancel(); + } + } else { + flags++; + } + } + } + + if (blockBreak.action == DiggingAction.FINISHED_DIGGING) { + hasFinished = true; + } + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !player.packetStateData.lastPacketWasTeleport && !player.packetStateData.lastPacketWasOnePointSeventeenDuplicate) { + hasFinished = false; + } + } + + @Override + public void onPredictionComplete(PredictionComplete predictionComplete) { + if (!player.skippedTickInActualMovement) { + for (; flags > 0; flags--) { + flagAndAlert(); + } + } + + flags = 0; + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/NoBreakDelayMitigation.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/NoBreakDelayMitigation.java new file mode 100644 index 0000000000..ae80b9362c --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/NoBreakDelayMitigation.java @@ -0,0 +1,45 @@ +package ac.grim.grimac.checks.impl.breaking; + +import ac.grim.grimac.api.config.ConfigManager; +import ac.grim.grimac.checks.Check; +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 com.github.retrooper.packetevents.event.PacketReceiveEvent; +import com.github.retrooper.packetevents.protocol.player.ClientVersion; +import com.github.retrooper.packetevents.protocol.player.DiggingAction; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; + +public class NoBreakDelayMitigation extends Check implements BlockBreakCheck { + public NoBreakDelayMitigation(GrimPlayer player) { + super(player); + } + + private int minTicks; + private int ticks; + + @Override + public void onBlockBreak(BlockBreak blockBreak) { + if (blockBreak.action == DiggingAction.START_DIGGING + && player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_8) + && ticks < minTicks + ) blockBreak.cancel(); + + if (blockBreak.action == DiggingAction.FINISHED_DIGGING) { + ticks = 0; + } + } + + @Override + public void onPacketReceive(PacketReceiveEvent event) { + if (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType()) && !player.packetStateData.lastPacketWasTeleport && !player.packetStateData.lastPacketWasOnePointSeventeenDuplicate) { + ticks++; + } + } + + @Override + public void onReload(ConfigManager config) { + minTicks = GrimMath.clampInt(config.getIntElse("min-break-delay", 3), 0, 6); + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/NoSwingBreak.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/NoSwingBreak.java new file mode 100644 index 0000000000..b774970ad1 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/NoSwingBreak.java @@ -0,0 +1,43 @@ +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; +import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerFlying; + +@CheckData(name = "NoSwingBreak", 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 (WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) { + if (sentBreak && !sentAnimation) { + flagAndAlert(); + } + + sentAnimation = sentBreak = false; + } + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/PositionBreakA.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/PositionBreakA.java new file mode 100644 index 0000000000..14264c7237 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/PositionBreakA.java @@ -0,0 +1,74 @@ +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.ClientVersion; +import com.github.retrooper.packetevents.protocol.player.DiggingAction; + +import java.util.Collections; + +@CheckData(name = "PositionBreakA", experimental = true) +public class PositionBreakA extends Check implements BlockBreakCheck { + public PositionBreakA(GrimPlayer player) { + super(player); + } + + @Override + public void onBlockBreak(BlockBreak blockBreak) { + if (player.compensatedEntities.getSelf().inVehicle()) return; // falses + if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) return; // buggy + + SimpleCollisionBox combined = blockBreak.getCombinedBox(); + + // Alright, now that we have the most optimal positions for each place + // Please note that minY may be lower than maxY, this is INTENTIONAL! + // Each position represents the best case scenario to have clicked + // + // We will now calculate the most optimal position for the player's head to be in + double minEyeHeight = Collections.min(player.getPossibleEyeHeights()); + double maxEyeHeight = Collections.max(player.getPossibleEyeHeights()); + // I love the idle packet, why did you remove it mojang :( + // Don't give 0.03 lenience if the player is a 1.8 player and we know they couldn't have 0.03'd because idle packet + double movementThreshold = !player.packetStateData.didLastMovementIncludePosition || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? player.getMovementThreshold() : 0; + + SimpleCollisionBox eyePositions = new SimpleCollisionBox(player.x, player.y + minEyeHeight, player.z, player.x, player.y + maxEyeHeight, player.z); + eyePositions.expand(movementThreshold); + + // 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 = false; + switch (blockBreak.face) { + case NORTH: // Z- face + flag = eyePositions.minZ > combined.minZ; + break; + case SOUTH: // Z+ face + flag = eyePositions.maxZ < combined.maxZ; + break; + case EAST: // X+ face + flag = eyePositions.maxX < combined.maxX; + break; + case WEST: // X- face + flag = eyePositions.minX > combined.minX; + break; + case UP: // Y+ face + flag = eyePositions.maxY < combined.maxY; + break; + case DOWN: // Y- face + flag = eyePositions.minY > combined.minY; + break; + } + + if (flag && flagAndAlert("action=" + blockBreak.action) && shouldModifyPackets()) { + blockBreak.cancel(); + } + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/PositionBreakB.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/PositionBreakB.java new file mode 100644 index 0000000000..5de10e30ad --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/PositionBreakB.java @@ -0,0 +1,37 @@ +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 BlockFace releaseFace = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8) ? BlockFace.DOWN : BlockFace.SOUTH; + + @Override + public void onBlockBreak(BlockBreak blockBreak) { + if (blockBreak.action == DiggingAction.START_DIGGING) { + if (blockBreak.face == lastFace) { + lastFace = null; + } + } + + if (lastFace != null) { + flagAndAlert(); + } + + if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) { + lastFace = blockBreak.face == releaseFace ? null : blockBreak.face; + } + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/RotationBreak.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/RotationBreak.java new file mode 100644 index 0000000000..b1bb0e1a41 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/RotationBreak.java @@ -0,0 +1,114 @@ +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.compensatedEntities.getSelf().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.compensatedEntities.getSelf().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); + + // Start checking if player is in the block + double minEyeHeight = Collections.min(player.getPossibleEyeHeights()); + double maxEyeHeight = Collections.max(player.getPossibleEyeHeights()); + + 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 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.getSelf().getAttributeValue(Attributes.PLAYER_BLOCK_INTERACTION_RANGE); + for (double d : player.getPossibleEyeHeights()) { + 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 intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance)); + + if (intercept.getFirst() != null) return true; + } + } + + return false; + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/breaking/WrongBreak.java b/src/main/java/ac/grim/grimac/checks/impl/breaking/WrongBreak.java new file mode 100644 index 0000000000..e846421f86 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/impl/breaking/WrongBreak.java @@ -0,0 +1,91 @@ +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 com.github.retrooper.packetevents.PacketEvents; +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 static ac.grim.grimac.utils.nmsutil.BlockBreakSpeed.getBlockDamage; + +@CheckData(name = "WrongBreak", experimental = true) +public class WrongBreak extends Check implements BlockBreakCheck { + public WrongBreak(final GrimPlayer player) { + super(player); + } + + private boolean lastBlockWasInstantBreak = false; + private Vector3i lastBlock, lastCancelledBlock, lastLastBlock = null; + private final int exemptedY = player.getClientVersion().isOlderThan(ClientVersion.V_1_8) ? 255 : (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14) ? -1 : 4095); + + // The client sometimes sends a wierd cancel packet + private boolean shouldExempt(final Vector3i pos) { + // lastLastBlock is always null when this happens, and lastBlock isn't + if (lastLastBlock != null || lastBlock == null) + return false; + + // on pre 1.14.4 clients, the YPos of this packet is always the same + if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) && pos.y != exemptedY) + return false; + + // and if this block is not an instant break + 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) { + lastBlockWasInstantBreak = getBlockDamage(player, blockBreak.position) >= 1; + lastCancelledBlock = null; + lastLastBlock = lastBlock; + lastBlock = blockBreak.position; + } + + if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) { + if (!shouldExempt(blockBreak.position)) { + if (!blockBreak.position.equals(lastBlock)) { + // https://github.com/GrimAnticheat/Grim/issues/1512 + if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || (!lastBlockWasInstantBreak && blockBreak.position.equals(lastCancelledBlock))) { + if (flagAndAlert("action=CANCELLED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(blockBreak.position))) { + if (shouldModifyPackets()) { + blockBreak.cancel(); + } + } + } + } + } else { + blockBreak.isWeirdCancel = true; + } + + lastCancelledBlock = blockBreak.position; + lastLastBlock = null; + lastBlock = null; + } + + if (blockBreak.action == DiggingAction.FINISHED_DIGGING) { + if (!blockBreak.position.equals(lastCancelledBlock) // https://bugs.mojang.com/browse/MC-255057 + && (!lastBlockWasInstantBreak || player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) + && !blockBreak.position.equals(lastBlock)) { + if (flagAndAlert("action=FINISHED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(blockBreak.position))) { + if (shouldModifyPackets()) { + blockBreak.cancel(); + } + } + } + + lastCancelledBlock = null; + + // 1.14.4+ clients don't send another start break in protected regions + if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) { + lastLastBlock = null; + lastBlock = null; + } + } + } +} diff --git a/src/main/java/ac/grim/grimac/checks/impl/misc/FastBreak.java b/src/main/java/ac/grim/grimac/checks/impl/misc/FastBreak.java deleted file mode 100644 index 2bf67f94fa..0000000000 --- a/src/main/java/ac/grim/grimac/checks/impl/misc/FastBreak.java +++ /dev/null @@ -1,155 +0,0 @@ -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); - } -} diff --git a/src/main/java/ac/grim/grimac/checks/type/BlockBreakCheck.java b/src/main/java/ac/grim/grimac/checks/type/BlockBreakCheck.java new file mode 100644 index 0000000000..07b7722fa3 --- /dev/null +++ b/src/main/java/ac/grim/grimac/checks/type/BlockBreakCheck.java @@ -0,0 +1,8 @@ +package ac.grim.grimac.checks.type; + +import ac.grim.grimac.utils.anticheat.update.BlockBreak; + +public interface BlockBreakCheck extends PostPredictionCheck { + default void onBlockBreak(final BlockBreak blockBreak) {} + default void onPostFlyingBlockBreak(final BlockBreak blockBreak) {} +} diff --git a/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java b/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java index 610f87507b..43fe17f1e3 100644 --- a/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java +++ b/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java @@ -1,8 +1,6 @@ package ac.grim.grimac.events.packets; import ac.grim.grimac.GrimAPI; -import ac.grim.grimac.checks.impl.badpackets.BadPacketsX; -import ac.grim.grimac.checks.impl.badpackets.BadPacketsZ; import ac.grim.grimac.events.packets.patch.ResyncWorldUtil; import ac.grim.grimac.player.GrimPlayer; import ac.grim.grimac.utils.anticheat.update.*; @@ -53,6 +51,8 @@ import java.util.List; import java.util.function.BiFunction; +import static ac.grim.grimac.events.packets.patch.ResyncWorldUtil.resyncPosition; + public class CheckManagerListener extends PacketListenerAbstract { public CheckManagerListener() { @@ -207,6 +207,40 @@ public static void handleQueuedPlaces(GrimPlayer player, boolean hasLook, float } } + public static void handleQueuedBreaks(GrimPlayer player, boolean hasLook, float pitch, float yaw, long now) { + BlockBreak blockBreak; + while ((blockBreak = player.queuedBreaks.poll()) != null) { + double lastX = player.x; + double lastY = player.y; + double lastZ = player.z; + + player.x = player.packetStateData.lastClaimedPosition.getX(); + player.y = player.packetStateData.lastClaimedPosition.getY(); + player.z = player.packetStateData.lastClaimedPosition.getZ(); + + if (player.compensatedEntities.getSelf().getRiding() != null) { + Vector3d posFromVehicle = BoundingBoxSize.getRidingOffsetFromVehicle(player.compensatedEntities.getSelf().getRiding(), player); + player.x = posFromVehicle.getX(); + player.y = posFromVehicle.getY(); + player.z = posFromVehicle.getZ(); + } + + // Less than 15 milliseconds ago means this is likely (fix all look vectors being a tick behind server sided) + // Or mojang had the idle packet... for the 1.7/1.8 clients + // No idle packet on 1.9+ + if ((now - player.lastBlockBreak < 15 || player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) && hasLook) { + player.xRot = yaw; + player.yRot = pitch; + } + + player.checkManager.onPostFlyingBlockBreak(blockBreak); + + player.x = lastX; + player.y = lastY; + player.z = lastZ; + } + } + private static void handleUseItem(GrimPlayer player, ItemStack placedWith, InteractionHand hand) { // Lilypads are USE_ITEM (THIS CAN DESYNC, WTF MOJANG) if (placedWith.getType() == ItemTypes.LILY_PAD) { @@ -456,39 +490,53 @@ public void onPacketReceive(PacketReceiveEvent event) { if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) { WrapperPlayClientPlayerDigging dig = new WrapperPlayClientPlayerDigging(event); - WrappedBlockState block = player.compensatedWorld.getWrappedBlockStateAt(dig.getBlockPosition()); + DiggingAction action = dig.getAction(); - player.checkManager.getPacketCheck(BadPacketsX.class).handle(event, dig, block.getType()); - player.checkManager.getPacketCheck(BadPacketsZ.class).handle(event, dig); + if (action == DiggingAction.START_DIGGING || action == DiggingAction.FINISHED_DIGGING || action == DiggingAction.CANCELLED_DIGGING) { + player.lastBlockBreak = System.currentTimeMillis(); - if (dig.getAction() == DiggingAction.FINISHED_DIGGING) { - // Not unbreakable - if (!block.getType().isAir() && block.getType().getHardness() != -1.0f && !event.isCancelled()) { - player.compensatedWorld.startPredicting(); - player.compensatedWorld.updateBlock(dig.getBlockPosition().getX(), dig.getBlockPosition().getY(), dig.getBlockPosition().getZ(), 0); - player.compensatedWorld.stopPredicting(dig); - } - } + Vector3i position = dig.getBlockPosition(); + WrappedBlockState block = player.compensatedWorld.getWrappedBlockStateAt(position); - if (dig.getAction() == DiggingAction.START_DIGGING && !event.isCancelled()) { - double damage = BlockBreakSpeed.getBlockDamage(player, dig.getBlockPosition()); + BlockBreak blockBreak = new BlockBreak(position, dig.getBlockFace(), action, player, block); + player.checkManager.onBlockBreak(blockBreak); - //Instant breaking, no damage means it is unbreakable by creative players (with swords) - if (damage >= 1) { - player.compensatedWorld.startPredicting(); - if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && Materials.isWaterSource(player.getClientVersion(), block)) { - // Vanilla uses a method to grab water flowing, but as you can't break flowing water - // We can simply treat all waterlogged blocks or source blocks as source blocks - player.compensatedWorld.updateBlock(dig.getBlockPosition(), StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion)); - } else { - player.compensatedWorld.updateBlock(dig.getBlockPosition().getX(), dig.getBlockPosition().getY(), dig.getBlockPosition().getZ(), 0); + if (event.isCancelled() || blockBreak.isCancelled()) { + if (!event.isCancelled()) { + event.setCancelled(true); + player.onPacketCancel(); + } + + resyncPosition(player, position, dig.getSequence()); + } else { + player.queuedBreaks.add(blockBreak); + + if (action == DiggingAction.FINISHED_DIGGING) { + // Not unbreakable + if (!block.getType().isAir() && block.getType().getHardness() != -1.0f) { + player.compensatedWorld.startPredicting(); + player.compensatedWorld.updateBlock(position.x, position.y, position.z, 0); + player.compensatedWorld.stopPredicting(dig); + } + } + + if (action == DiggingAction.START_DIGGING) { + double damage = BlockBreakSpeed.getBlockDamage(player, position); + + // Instant breaking, no damage means it is unbreakable by creative players (with swords) + if (damage >= 1) { + player.compensatedWorld.startPredicting(); + if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && Materials.isWaterSource(player.getClientVersion(), block)) { + // Vanilla uses a method to grab water flowing, but as you can't break flowing water + // We can simply treat all waterlogged blocks or source blocks as source blocks + player.compensatedWorld.updateBlock(position, StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion)); + } else { + player.compensatedWorld.updateBlock(position.x, position.y, position.z, 0); + } + player.compensatedWorld.stopPredicting(dig); + } } - player.compensatedWorld.stopPredicting(dig); - } - } - if (!event.isCancelled()) { - if (dig.getAction() == DiggingAction.START_DIGGING || dig.getAction() == DiggingAction.FINISHED_DIGGING || dig.getAction() == DiggingAction.CANCELLED_DIGGING) { player.compensatedWorld.handleBlockBreakPrediction(dig); } } @@ -680,6 +728,7 @@ private void handleFlying(GrimPlayer player, double x, double y, double z, float } handleQueuedPlaces(player, hasLook, pitch, yaw, now); + handleQueuedBreaks(player, hasLook, pitch, yaw, now); // We can set the new pos after the places if (hasPosition) { diff --git a/src/main/java/ac/grim/grimac/events/packets/PacketPlayerDigging.java b/src/main/java/ac/grim/grimac/events/packets/PacketPlayerDigging.java index f084272e11..d6037b4558 100644 --- a/src/main/java/ac/grim/grimac/events/packets/PacketPlayerDigging.java +++ b/src/main/java/ac/grim/grimac/events/packets/PacketPlayerDigging.java @@ -191,6 +191,7 @@ public void onPacketReceive(PacketReceiveEvent event) { final GrimPlayer player = GrimAPI.INSTANCE.getPlayerDataManager().getPlayer(event.getUser()); if (player == null) return; + // do we need to do this with block breaks too? // Prevent issues if the player switches slots, while lagging, standing still, and is placing blocks CheckManagerListener.handleQueuedPlaces(player, false, 0, 0, System.currentTimeMillis()); diff --git a/src/main/java/ac/grim/grimac/events/packets/patch/ResyncWorldUtil.java b/src/main/java/ac/grim/grimac/events/packets/patch/ResyncWorldUtil.java index 40cc77f5c0..8c9d4fa7f7 100644 --- a/src/main/java/ac/grim/grimac/events/packets/patch/ResyncWorldUtil.java +++ b/src/main/java/ac/grim/grimac/events/packets/patch/ResyncWorldUtil.java @@ -9,9 +9,12 @@ import com.github.retrooper.packetevents.netty.channel.ChannelHelper; import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; import com.github.retrooper.packetevents.util.Vector3i; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerAcknowledgeBlockChanges; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockChange; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange; import io.github.retrooper.packetevents.util.folia.FoliaScheduler; import org.bukkit.Chunk; +import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; @@ -113,4 +116,34 @@ public static void resyncPositions(GrimPlayer player, int minBlockX, int mY, int } }, null, 0); } + + public static void resyncPosition(GrimPlayer player, Vector3i pos, int sequence) { + if (player.bukkitPlayer == null) return; + + FoliaScheduler.getEntityScheduler().execute(player.bukkitPlayer, GrimAPI.INSTANCE.getPlugin(), () -> { + if (!player.bukkitPlayer.isOnline() || !player.getSetbackTeleportUtil().hasAcceptedSpawnTeleport) return; + + final int chunkX = pos.x >> 4; + final int chunkZ = pos.z >> 4; + + if (!player.compensatedWorld.isChunkLoaded(chunkX, chunkZ)) return; + if (player.bukkitPlayer.getLocation().distance(new Location(player.bukkitPlayer.getWorld(), pos.x, pos.y, pos.z)) >= 64) return; + if (!player.bukkitPlayer.getWorld().isChunkLoaded(chunkX, chunkZ)) return; // Don't load chunks sync + + final Block block = player.bukkitPlayer.getWorld().getChunkAt(chunkX, chunkZ).getBlock(pos.x & 15, pos.y, pos.z & 15); + + final int blockId; + if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_13)) { + // Cache this because strings are expensive + blockId = blockDataToId.computeIfAbsent(block.getBlockData(), data -> WrappedBlockState.getByString(PacketEvents.getAPI().getServerManager().getVersion().toClientVersion(), data.getAsString(false)).getGlobalId()); + } else { + blockId = (block.getType().getId() << 4) | block.getData(); + } + + player.user.sendPacket(new WrapperPlayServerBlockChange(pos, 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(sequence)); // Make 1.19 clients apply the changes + } + }, null, 0); + } } diff --git a/src/main/java/ac/grim/grimac/manager/CheckManager.java b/src/main/java/ac/grim/grimac/manager/CheckManager.java index c396ca0455..2c81a9e2bd 100644 --- a/src/main/java/ac/grim/grimac/manager/CheckManager.java +++ b/src/main/java/ac/grim/grimac/manager/CheckManager.java @@ -6,13 +6,14 @@ import ac.grim.grimac.checks.impl.aim.AimModulo360; import ac.grim.grimac.checks.impl.aim.processor.AimProcessor; import ac.grim.grimac.checks.impl.badpackets.*; +import ac.grim.grimac.checks.impl.breaking.*; import ac.grim.grimac.checks.impl.combat.Reach; import ac.grim.grimac.checks.impl.crash.*; import ac.grim.grimac.checks.impl.exploit.ExploitA; import ac.grim.grimac.checks.impl.exploit.ExploitB; import ac.grim.grimac.checks.impl.groundspoof.NoFallA; import ac.grim.grimac.checks.impl.misc.ClientBrand; -import ac.grim.grimac.checks.impl.misc.FastBreak; +import ac.grim.grimac.checks.impl.breaking.FastBreakA; import ac.grim.grimac.checks.impl.misc.GhostBlockMitigation; import ac.grim.grimac.checks.impl.misc.TransactionOrder; import ac.grim.grimac.checks.impl.movement.*; @@ -49,7 +50,7 @@ public class CheckManager { ClassToInstanceMap rotationCheck; ClassToInstanceMap vehicleCheck; ClassToInstanceMap prePredictionChecks; - + ClassToInstanceMap blockBreakChecks; ClassToInstanceMap blockPlaceCheck; ClassToInstanceMap postPredictionCheck; @@ -91,10 +92,7 @@ public CheckManager(GrimPlayer player) { .put(BadPacketsU.class, new BadPacketsU(player)) .put(BadPacketsV.class, new BadPacketsV(player)) .put(BadPacketsW.class, new BadPacketsW(player)) - .put(BadPacketsX.class, new BadPacketsX(player)) .put(BadPacketsY.class, new BadPacketsY(player)) - .put(BadPacketsZ.class, new BadPacketsZ(player)) - .put(FastBreak.class, new FastBreak(player)) .put(TransactionOrder.class, new TransactionOrder(player)) .put(NoSlowB.class, new NoSlowB(player)) .put(SetbackBlocker.class, new SetbackBlocker(player)) // Must be last class otherwise we can't check while blocking packets @@ -148,6 +146,21 @@ public CheckManager(GrimPlayer player) { .put(GhostBlockMitigation.class, new GhostBlockMitigation(player)) .build(); + blockBreakChecks = new ImmutableClassToInstanceMap.Builder() + .put(RotationBreak.class, new RotationBreak(player)) + .put(WrongBreak.class, new WrongBreak(player)) + .put(AirLiquidBreak.class, new AirLiquidBreak(player)) + .put(PositionBreakA.class, new PositionBreakA(player)) + .put(PositionBreakB.class, new PositionBreakB(player)) + .put(MultiBreak.class, new MultiBreak(player)) + .put(FarBreak.class, new FarBreak(player)) + .put(NoSwingBreak.class, new NoSwingBreak(player)) + .put(NoBreakDelay.class, new NoBreakDelay(player)) + .put(NoBreakDelayMitigation.class, new NoBreakDelayMitigation(player)) + .put(FastBreakA.class, new FastBreakA(player)) + .put(FastBreakB.class, new FastBreakB(player)) + .build(); + prePredictionChecks = new ImmutableClassToInstanceMap.Builder() .put(TimerCheck.class, new TimerCheck(player)) .put(CrashA.class, new CrashA(player)) @@ -171,6 +184,7 @@ public CheckManager(GrimPlayer player) { .putAll(postPredictionCheck) .putAll(blockPlaceCheck) .putAll(prePredictionChecks) + .putAll(blockBreakChecks) .build(); } @@ -205,6 +219,9 @@ public void onPacketReceive(final PacketReceiveEvent packet) { for (BlockPlaceCheck check : blockPlaceCheck.values()) { check.onPacketReceive(packet); } + for (BlockBreakCheck check : blockBreakChecks.values()) { + check.onPacketReceive(packet); + } } public void onPacketSend(final PacketSendEvent packet) { @@ -250,6 +267,9 @@ public void onPredictionFinish(final PredictionComplete complete) { for (BlockPlaceCheck check : blockPlaceCheck.values()) { check.onPredictionComplete(complete); } + for (BlockBreakCheck check : blockBreakChecks.values()) { + check.onPredictionComplete(complete); + } } public void onBlockPlace(final BlockPlace place) { @@ -264,6 +284,18 @@ public void onPostFlyingBlockPlace(final BlockPlace place) { } } + public void onBlockBreak(final BlockBreak blockBreak) { + for (BlockBreakCheck check : blockBreakChecks.values()) { + check.onBlockBreak(blockBreak); + } + } + + public void onPostFlyingBlockBreak(final BlockBreak blockBreak) { + for (BlockBreakCheck check : blockBreakChecks.values()) { + check.onPostFlyingBlockBreak(blockBreak); + } + } + public ExplosionHandler getExplosionHandler() { return getPostPredictionCheck(ExplosionHandler.class); } diff --git a/src/main/java/ac/grim/grimac/player/GrimPlayer.java b/src/main/java/ac/grim/grimac/player/GrimPlayer.java index d55074e57e..477fd93214 100644 --- a/src/main/java/ac/grim/grimac/player/GrimPlayer.java +++ b/src/main/java/ac/grim/grimac/player/GrimPlayer.java @@ -14,6 +14,7 @@ import ac.grim.grimac.predictionengine.PointThreeEstimator; import ac.grim.grimac.predictionengine.UncertaintyHandler; import ac.grim.grimac.utils.anticheat.LogUtil; +import ac.grim.grimac.utils.anticheat.update.BlockBreak; import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; import ac.grim.grimac.utils.data.*; import ac.grim.grimac.utils.data.packetentity.PacketEntity; @@ -198,6 +199,7 @@ public class GrimPlayer implements GrimUser { public DimensionType dimensionType; public Vector3d bedPosition; public long lastBlockPlaceUseItem = 0; + public long lastBlockBreak = 0; public AtomicInteger cancelledPackets = new AtomicInteger(0); public MainSupportingBlockData mainSupportingBlockData = new MainSupportingBlockData(null, false); @@ -211,6 +213,7 @@ public void onPacketCancel() { public int totalFlyingPacketsSent; public Queue placeUseItemPackets = new LinkedBlockingQueue<>(); + public Queue queuedBreaks = new LinkedBlockingQueue<>(); // This variable is for support with test servers that want to be able to disable grim // Grim disabler 2022 still working! public boolean disableGrim = false; @@ -331,8 +334,10 @@ public boolean addTransactionResponse(short id) { playerClockAtLeast = data.getSecond(); } while (data.getFirst() != id); - // A transaction means a new tick, so apply any block places - CheckManagerListener.handleQueuedPlaces(this, false, 0, 0, System.currentTimeMillis()); + // A transaction means a new tick, so apply any block places or breaks + long now = System.currentTimeMillis(); + CheckManagerListener.handleQueuedPlaces(this, false, 0, 0, now); + CheckManagerListener.handleQueuedBreaks(this, false, 0, 0, now); latencyUtils.handleNettySyncTransaction(lastTransactionReceived.get()); } diff --git a/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockBreak.java b/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockBreak.java new file mode 100644 index 0000000000..357781c192 --- /dev/null +++ b/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockBreak.java @@ -0,0 +1,61 @@ +package ac.grim.grimac.utils.anticheat.update; + +import ac.grim.grimac.player.GrimPlayer; +import ac.grim.grimac.utils.collisions.HitboxData; +import ac.grim.grimac.utils.collisions.datatypes.CollisionBox; +import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox; +import com.github.retrooper.packetevents.protocol.player.DiggingAction; +import com.github.retrooper.packetevents.protocol.world.BlockFace; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; +import com.github.retrooper.packetevents.util.Vector3i; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +public class BlockBreak { + public final Vector3i position; + public final BlockFace face; + public final DiggingAction action; + @Getter + private boolean cancelled; + + // set by WrongBreak + public boolean isWeirdCancel; + + private final GrimPlayer player; + public final WrappedBlockState block; + + public BlockBreak(Vector3i position, BlockFace face, DiggingAction action, GrimPlayer player, WrappedBlockState block) { + this.position = position; + this.face = face; + this.action = action; + this.player = player; + this.block = block; + } + + public void cancel() { + cancelled = true; + } + + public SimpleCollisionBox getCombinedBox() { + // Alright, instead of skidding AACAdditionsPro, let's just use bounding boxes + CollisionBox placedOn = HitboxData.getBlockHitbox(player, player.getInventory().getHeldItem().getType().getPlacedType(), player.getClientVersion(), block, position.x, position.y, position.z); + + List boxes = new ArrayList<>(); + placedOn.downCast(boxes); + + SimpleCollisionBox combined = new SimpleCollisionBox(position.x, position.y, position.z); + for (SimpleCollisionBox box : boxes) { + double minX = Math.max(box.minX, combined.minX); + double minY = Math.max(box.minY, combined.minY); + double minZ = Math.max(box.minZ, combined.minZ); + double maxX = Math.min(box.maxX, combined.maxX); + double maxY = Math.min(box.maxY, combined.maxY); + double maxZ = Math.min(box.maxZ, combined.maxZ); + combined = new SimpleCollisionBox(minX, minY, minZ, maxX, maxY, maxZ); + } + + return combined; + } +} diff --git a/src/main/java/ac/grim/grimac/utils/math/GrimMath.java b/src/main/java/ac/grim/grimac/utils/math/GrimMath.java index b9758391e2..47eb64a847 100644 --- a/src/main/java/ac/grim/grimac/utils/math/GrimMath.java +++ b/src/main/java/ac/grim/grimac/utils/math/GrimMath.java @@ -60,6 +60,13 @@ public static double clamp(double num, double min, double max) { return Math.min(num, max); } + public static int clampInt(int num, int min, int max) { + if (num < min) { + return min; + } + return Math.min(num, max); + } + public static float clampFloat(float num, float min, float max) { if (num < min) { return min; diff --git a/src/main/resources/config/de.yml b/src/main/resources/config/de.yml index 29d10af784..45d4a3e874 100644 --- a/src/main/resources/config/de.yml +++ b/src/main/resources/config/de.yml @@ -164,6 +164,12 @@ exploit: # Ihr gültiger Bereich ist von 2 bis 4 begrenzt distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # Aktivieren Sie die Protokollierung von Plugins, die in netty on join injiziert wurden, um Kompatibilitätsprobleme zu beheben. debug-pipeline-on-join: false diff --git a/src/main/resources/config/en.yml b/src/main/resources/config/en.yml index aff61a8f20..2c37cfcd7d 100644 --- a/src/main/resources/config/en.yml +++ b/src/main/resources/config/en.yml @@ -167,6 +167,12 @@ exploit: # Its valid range is limited from 2 to 4 distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # Enable logging plugins who have injected into netty on join to debug compatibility issues debug-pipeline-on-join: false diff --git a/src/main/resources/config/es.yml b/src/main/resources/config/es.yml index b83737e466..08abc43525 100644 --- a/src/main/resources/config/es.yml +++ b/src/main/resources/config/es.yml @@ -168,6 +168,12 @@ exploit: allow-building-on-ghostblocks: true distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # Habilitar el registro de plugins que se han inyectado a netty para intentar resolver problemas de compatibilidad. debug-pipeline-on-join: false diff --git a/src/main/resources/config/fr.yml b/src/main/resources/config/fr.yml index 0e1c36c451..dd8886e7cd 100644 --- a/src/main/resources/config/fr.yml +++ b/src/main/resources/config/fr.yml @@ -163,6 +163,12 @@ exploit: allow-building-on-ghostblocks: true distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # Activer l'enregistrement des plugins ayant injecté dans Netty lors de la connexion pour déboguer les problèmes de compatibilité debug-pipeline-on-join: false diff --git a/src/main/resources/config/it.yml b/src/main/resources/config/it.yml index 82926afb77..eb12a43759 100644 --- a/src/main/resources/config/it.yml +++ b/src/main/resources/config/it.yml @@ -144,6 +144,12 @@ exploit: # Distanza per controllare i ghost blocks distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # Enable logging plugins who have injected into netty on join to debug compatibility issues debug-pipeline-on-join: false diff --git a/src/main/resources/config/nl.yml b/src/main/resources/config/nl.yml index f198782f54..87422a45a8 100644 --- a/src/main/resources/config/nl.yml +++ b/src/main/resources/config/nl.yml @@ -167,6 +167,12 @@ exploit: # Het geldige bereik is beperkt van 2 tot 4 distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # Logging plugins inschakelen die geïnjecteerd zijn in netty on join om compatibiliteitsproblemen te debuggen debug-pipeline-on-join: false diff --git a/src/main/resources/config/pt.yml b/src/main/resources/config/pt.yml index 386bf6d103..46a899b11d 100644 --- a/src/main/resources/config/pt.yml +++ b/src/main/resources/config/pt.yml @@ -172,6 +172,12 @@ exploit: # O valor válido é limitado de 2 a 4. distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # Registra plugins que são injetados na netty ao entrar para depurar erros de compatibilidade. debug-pipeline-on-join: false diff --git a/src/main/resources/config/ru.yml b/src/main/resources/config/ru.yml index 0cd1bae724..225738caad 100644 --- a/src/main/resources/config/ru.yml +++ b/src/main/resources/config/ru.yml @@ -163,6 +163,12 @@ exploit: allow-building-on-ghostblocks: true distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # Включить запись в журнал плагинов, которые внедрились в netty при присоединении для отладки проблем совместимости debug-pipeline-on-join: false diff --git a/src/main/resources/config/tr.yml b/src/main/resources/config/tr.yml index 4e5e23c480..90ad136b90 100644 --- a/src/main/resources/config/tr.yml +++ b/src/main/resources/config/tr.yml @@ -167,6 +167,12 @@ exploit: # Geçerli aralığı 2 ile 4 arasında sınırlıdır distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # Netty'ye enjekte olan eklentileri giriş sırasında günlüklemeyi etkinleştirerek uyumluluk sorunlarını gider debug-pipeline-on-join: false diff --git a/src/main/resources/config/zh.yml b/src/main/resources/config/zh.yml index 01cacd08eb..0872a54128 100644 --- a/src/main/resources/config/zh.yml +++ b/src/main/resources/config/zh.yml @@ -165,6 +165,12 @@ exploit: allow-building-on-ghostblocks: true distance-to-check-if-ghostblocks: 2 +# The minimum tick delay allowed between block breaks. +# this is usually 6, but releasing the block break key resets this +# delay, thus there is no alert for break delays less than this. +# Only affects pre-1.9 clients, and must be from 0 to 6. +min-break-delay: 3 + # 启用在加入时注入 netty 的日志插件以调试兼容性问题 debug-pipeline-on-join: false diff --git a/src/main/resources/punishments/de.yml b/src/main/resources/punishments/de.yml index ffb36b28d9..4deec85bf2 100644 --- a/src/main/resources/punishments/de.yml +++ b/src/main/resources/punishments/de.yml @@ -71,6 +71,7 @@ Punishments: - "EntityControl" - "NoSlow" - "Place" + - "Break" - "Baritone" - "FastBreak" - "TransactionOrder" diff --git a/src/main/resources/punishments/en.yml b/src/main/resources/punishments/en.yml index 00f6e842c5..0821949f6d 100644 --- a/src/main/resources/punishments/en.yml +++ b/src/main/resources/punishments/en.yml @@ -71,6 +71,7 @@ Punishments: - "EntityControl" - "NoSlow" - "Place" + - "Break" - "Baritone" - "FastBreak" - "TransactionOrder" diff --git a/src/main/resources/punishments/es.yml b/src/main/resources/punishments/es.yml index d8542bf0b1..44f48fca25 100644 --- a/src/main/resources/punishments/es.yml +++ b/src/main/resources/punishments/es.yml @@ -71,6 +71,7 @@ Punishments: - "EntityControl" - "NoSlow" - "Place" + - "Break" - "Baritone" - "FastBreak" - "TransactionOrder" diff --git a/src/main/resources/punishments/fr.yml b/src/main/resources/punishments/fr.yml index f1113cc77a..a33ae014aa 100644 --- a/src/main/resources/punishments/fr.yml +++ b/src/main/resources/punishments/fr.yml @@ -71,6 +71,7 @@ Punishments: - "EntityControl" - "NoSlow" - "Place" + - "Break" - "Baritone" - "FastBreak" - "TransactionOrder" diff --git a/src/main/resources/punishments/it.yml b/src/main/resources/punishments/it.yml index 4521e1622e..5a57b7cf79 100644 --- a/src/main/resources/punishments/it.yml +++ b/src/main/resources/punishments/it.yml @@ -58,6 +58,7 @@ Punishments: - "EntityControl" - "NoSlow" - "Place" + - "Break" - "Baritone" - "FastBreak" - "TransactionOrder" diff --git a/src/main/resources/punishments/nl.yml b/src/main/resources/punishments/nl.yml index 1f0c8075cc..a89a97ad9d 100644 --- a/src/main/resources/punishments/nl.yml +++ b/src/main/resources/punishments/nl.yml @@ -71,6 +71,7 @@ Punishments: - "EntityControl" - "NoSlow" - "Place" + - "Break" - "Baritone" - "FastBreak" - "TransactionOrder" diff --git a/src/main/resources/punishments/pt.yml b/src/main/resources/punishments/pt.yml index d6e811564e..8e70ae6488 100644 --- a/src/main/resources/punishments/pt.yml +++ b/src/main/resources/punishments/pt.yml @@ -71,6 +71,7 @@ Punishments: - "EntityControl" - "NoSlow" - "Place" + - "Break" - "Baritone" - "FastBreak" - "TransactionOrder" diff --git a/src/main/resources/punishments/ru.yml b/src/main/resources/punishments/ru.yml index 1486066fe0..173928e3f4 100644 --- a/src/main/resources/punishments/ru.yml +++ b/src/main/resources/punishments/ru.yml @@ -71,6 +71,7 @@ Punishments: - "EntityControl" - "NoSlow" - "Place" + - "Break" - "Baritone" - "FastBreak" - "TransactionOrder" diff --git a/src/main/resources/punishments/zh.yml b/src/main/resources/punishments/zh.yml index 76b60092be..dfb12264b0 100644 --- a/src/main/resources/punishments/zh.yml +++ b/src/main/resources/punishments/zh.yml @@ -71,6 +71,7 @@ Punishments: - "EntityControl" - "NoSlow" - "Place" + - "Break" - "Baritone" - "FastBreak" - "TransactionOrder"