Skip to content

Commit

Permalink
Merge pull request #1830 from ManInMyVan/blockbreak-class
Browse files Browse the repository at this point in the history
add BlockBreak class
  • Loading branch information
SamB440 authored Dec 1, 2024
2 parents c1858e4 + 1036563 commit 61af1d1
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -40,18 +39,18 @@ 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;
lastLastBlock = lastBlock;
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;
Expand All @@ -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);
}
}
Expand All @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<StateType, Boolean> BREAKABLE = type -> {
return !type.isAir() && type.getHardness() != -1.0f && type != StateTypes.WATER && type != StateTypes.LAVA;
};
private static final Function<StateType, Boolean> BREAKABLE = type -> !type.isAir() && type.getHardness() != -1.0f && type != StateTypes.WATER && type != StateTypes.LAVA;

@Override
public void onPacketReceive(PacketReceiveEvent event) {
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 61af1d1

Please sign in to comment.