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 index 55ae149704..e3a5d3c1ce 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsX.java +++ b/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsX.java @@ -5,14 +5,13 @@ 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 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; -import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging; @CheckData(name = "BadPacketsX") public class BadPacketsX extends Check implements PacketCheck { @@ -27,26 +26,26 @@ public BadPacketsX(GrimPlayer 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) + public final void handle(BlockBreak blockBreak) { + if (blockBreak.action != DiggingAction.START_DIGGING && blockBreak.action != DiggingAction.FINISHED_DIGGING) return; + final var 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. 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()) + && lastBreakLoc.equals(blockBreak.position) && !didLastFlag && lastBlockType.getHardness() == 0.0F && lastBlockType.getBlastResistance() == 0.0F && block == StateTypes.WATER - ) { - return; - } + ) return; lastTick = newTick; - lastBreakLoc = dig.getBlockPosition(); + lastBreakLoc = blockBreak.position; lastBlockType = block; // the block does not have a hitbox @@ -56,14 +55,13 @@ public final void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDiggin || block == StateTypes.LAVA || block == StateTypes.BUBBLE_COLUMN || block == StateTypes.MOVING_PISTON - || (block == StateTypes.FIRE && noFireHitbox) + || block == StateTypes.FIRE && noFireHitbox // or the client claims to have broken an unbreakable block - || block.getHardness() == -1.0f && dig.getAction() == DiggingAction.FINISHED_DIGGING; + || block.getHardness() == -1.0f && blockBreak.action == DiggingAction.FINISHED_DIGGING; - if (invalid && flagAndAlert("block=" + block.getName() + ", type=" + dig.getAction()) && shouldModifyPackets()) { + if (invalid && flagAndAlert("block=" + block.getName() + ", type=" + blockBreak.action) && shouldModifyPackets()) { didLastFlag = true; - event.setCancelled(true); - player.onPacketCancel(); + blockBreak.cancel(); } else { didLastFlag = false; } 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 index 94bf89ca67..8f8e1b820b 100644 --- a/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsZ.java +++ b/src/main/java/ac/grim/grimac/checks/impl/badpackets/BadPacketsZ.java @@ -5,13 +5,12 @@ 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; @@ -40,9 +39,9 @@ private boolean shouldExempt(final Vector3i pos) { 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(); + public void handle(BlockBreak blockBreak) { + if (blockBreak.action == DiggingAction.START_DIGGING) { + final var pos = blockBreak.position; lastBlockWasInstantBreak = getBlockDamage(player, pos) >= 1; lastCancelledBlock = null; @@ -50,8 +49,8 @@ public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging dig) lastBlock = pos; } - if (dig.getAction() == DiggingAction.CANCELLED_DIGGING) { - final Vector3i pos = dig.getBlockPosition(); + if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) { + final var pos = blockBreak.position; if (shouldExempt(pos)) { lastCancelledBlock = pos; @@ -65,8 +64,7 @@ public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging dig) 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(); + blockBreak.cancel(); resyncPosition(player, pos); } } @@ -79,15 +77,14 @@ public void handle(PacketReceiveEvent event, WrapperPlayClientPlayerDigging dig) return; } - if (dig.getAction() == DiggingAction.FINISHED_DIGGING) { - final Vector3i pos = dig.getBlockPosition(); + if (blockBreak.action == DiggingAction.FINISHED_DIGGING) { + final var pos = blockBreak.position; // 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(); + blockBreak.cancel(); resyncPosition(player, pos); } } 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 b48ff4ef3e..a18757ea14 100644 --- a/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java +++ b/src/main/java/ac/grim/grimac/events/packets/CheckManagerListener.java @@ -371,9 +371,7 @@ private boolean isMojangStupid(GrimPlayer player, WrapperPlayClientPlayerFlying } // Manual filter on FINISH_DIGGING to prevent clients setting non-breakable blocks to air - private static final Function BREAKABLE = type -> { - return !type.isAir() && type.getHardness() != -1.0f && type != StateTypes.WATER && type != StateTypes.LAVA; - }; + private static final Function BREAKABLE = type -> !type.isAir() && type.getHardness() != -1.0f && type != StateTypes.WATER && type != StateTypes.LAVA; @Override public void onPacketReceive(PacketReceiveEvent event) { @@ -455,43 +453,43 @@ public void onPacketReceive(PacketReceiveEvent event) { } if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) { - WrapperPlayClientPlayerDigging dig = new WrapperPlayClientPlayerDigging(event); - final Vector3i digPosition = dig.getBlockPosition(); - WrappedBlockState block = player.compensatedWorld.getWrappedBlockStateAt(digPosition); - final StateType type = block.getType(); + final var packet = new WrapperPlayClientPlayerDigging(event); + final var blockBreak = new BlockBreak(packet, player); - player.checkManager.getPacketCheck(BadPacketsX.class).handle(event, dig, type); - player.checkManager.getPacketCheck(BadPacketsZ.class).handle(event, dig); + player.checkManager.getPacketCheck(BadPacketsX.class).handle(blockBreak); + player.checkManager.getPacketCheck(BadPacketsZ.class).handle(blockBreak); - if (dig.getAction() == DiggingAction.FINISHED_DIGGING) { - // Not unbreakable - if (BREAKABLE.apply(type) && !event.isCancelled()) { + if (blockBreak.isCancelled()) { + event.setCancelled(true); + player.onPacketCancel(); + } + + if (!event.isCancelled()) { + if (blockBreak.action == DiggingAction.FINISHED_DIGGING && BREAKABLE.apply(blockBreak.block.getType())) { player.compensatedWorld.startPredicting(); - player.compensatedWorld.updateBlock(digPosition.getX(), digPosition.getY(), digPosition.getZ(), 0); - player.compensatedWorld.stopPredicting(dig); + player.compensatedWorld.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0); + player.compensatedWorld.stopPredicting(packet); } - } - if (dig.getAction() == DiggingAction.START_DIGGING && !event.isCancelled()) { - double damage = BlockBreakSpeed.getBlockDamage(player, digPosition); + if (blockBreak.action == DiggingAction.START_DIGGING) { + double damage = BlockBreakSpeed.getBlockDamage(player, blockBreak.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(digPosition, StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion)); - } else { - player.compensatedWorld.updateBlock(digPosition.getX(), digPosition.getY(), digPosition.getZ(), 0); + // 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(), blockBreak.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(blockBreak.position, StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion)); + } else { + player.compensatedWorld.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0); + } + player.compensatedWorld.stopPredicting(packet); } - 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); + if (blockBreak.action == DiggingAction.START_DIGGING || blockBreak.action == DiggingAction.FINISHED_DIGGING || blockBreak.action == DiggingAction.CANCELLED_DIGGING) { + player.compensatedWorld.handleBlockBreakPrediction(packet); } } } 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..883453117d --- /dev/null +++ b/src/main/java/ac/grim/grimac/utils/anticheat/update/BlockBreak.java @@ -0,0 +1,34 @@ +package ac.grim.grimac.utils.anticheat.update; + +import ac.grim.grimac.player.GrimPlayer; +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 com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientPlayerDigging; +import lombok.Getter; + +public class BlockBreak { + public final Vector3i position; + public final BlockFace face; + public final DiggingAction action; + @Getter + private boolean cancelled; + + public final WrappedBlockState block; + + public BlockBreak(WrapperPlayClientPlayerDigging packet, GrimPlayer player) { + this(packet.getBlockPosition(), packet.getBlockFace(), packet.getAction(), player.compensatedWorld.getWrappedBlockStateAt(packet.getBlockPosition())); + } + + public BlockBreak(Vector3i position, BlockFace face, DiggingAction action, WrappedBlockState block) { + this.position = position; + this.face = face; + this.action = action; + this.block = block; + } + + public void cancel() { + this.cancelled = true; + } +}