Skip to content

Commit

Permalink
Merge pull request #1847 from ManInMyVan/blockbreakcheck
Browse files Browse the repository at this point in the history
BlockBreakCheck
  • Loading branch information
AoElite authored Dec 9, 2024
2 parents ff1c304 + 227458a commit c744501
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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.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;
Expand All @@ -14,7 +14,7 @@
import com.github.retrooper.packetevents.util.Vector3i;

@CheckData(name = "BadPacketsX")
public class BadPacketsX extends Check implements PacketCheck {
public class BadPacketsX extends Check implements BlockBreakCheck {
public BadPacketsX(GrimPlayer player) {
super(player);
}
Expand All @@ -26,7 +26,8 @@ public BadPacketsX(GrimPlayer player) {

public final boolean noFireHitbox = player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_15_2);

public final void handle(BlockBreak blockBreak) {
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action != DiggingAction.START_DIGGING && blockBreak.action != DiggingAction.FINISHED_DIGGING)
return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
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;
Expand All @@ -12,11 +12,10 @@
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.util.Vector3i;

import static ac.grim.grimac.events.packets.patch.ResyncWorldUtil.resyncPosition;
import static ac.grim.grimac.utils.nmsutil.BlockBreakSpeed.getBlockDamage;

@CheckData(name = "BadPacketsZ")
public class BadPacketsZ extends Check implements PacketCheck {
public class BadPacketsZ extends Check implements BlockBreakCheck {
public BadPacketsZ(final GrimPlayer player) {
super(player);
}
Expand All @@ -39,7 +38,8 @@ private boolean shouldExempt(final Vector3i pos) {
return player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || getBlockDamage(player, pos) < 1;
}

public void handle(BlockBreak blockBreak) {
@Override
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action == DiggingAction.START_DIGGING) {
final Vector3i pos = blockBreak.position;

Expand All @@ -65,7 +65,6 @@ public void handle(BlockBreak blockBreak) {
if (flagAndAlert("action=CANCELLED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
resyncPosition(player, pos);
}
}
}
Expand All @@ -85,7 +84,6 @@ public void handle(BlockBreak blockBreak) {
if (flagAndAlert("action=FINISHED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
resyncPosition(player, pos);
}
}
}
Expand Down
143 changes: 50 additions & 93 deletions src/main/java/ac/grim/grimac/checks/impl/misc/FastBreak.java
Original file line number Diff line number Diff line change
@@ -1,36 +1,26 @@
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.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.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 {
@CheckData(name = "FastBreak")
public class FastBreak extends Check implements BlockBreakCheck {
public FastBreak(GrimPlayer playerData) {
super(playerData);
}
Expand All @@ -50,100 +40,67 @@ public FastBreak(GrimPlayer playerData) {
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();
public void onBlockBreak(BlockBreak blockBreak) {
if (blockBreak.action == DiggingAction.START_DIGGING) {
WrappedBlockState block = blockBreak.block;

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);
// Exempt all blocks that do not exist in the player version
if (WrappedBlockState.getDefaultState(player.getClientVersion(), block.getType()).getType() == StateTypes.AIR) {
return;
}

double breakDelay = System.currentTimeMillis() - lastFinishBreak;
startBreak = System.currentTimeMillis() - (targetBlock == null ? 50 : 0); // ???
targetBlock = blockBreak.position;

if (breakDelay >= 275) { // Reduce buffer if "close enough"
blockDelayBalance *= 0.9;
} else { // Otherwise, increase buffer
blockDelayBalance += 300 - breakDelay;
}
maximumBlockDamage = BlockBreakSpeed.getBlockDamage(player, targetBlock);

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();
}
}
double breakDelay = System.currentTimeMillis() - lastFinishBreak;

clampBalance();
if (breakDelay >= 275) { // Reduce buffer if "close enough"
blockDelayBalance *= 0.9;
} else { // Otherwise, increase buffer
blockDelayBalance += 300 - breakDelay;
}

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 (blockDelayBalance > 1000) { // If more than a second of advantage
flagAndAlert("Delay=" + breakDelay);
if (shouldModifyPackets()) {
blockBreak.cancel();
}
}

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;
clampBalance();
}

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();
}
if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlock != null) {
double predictedTime = Math.ceil(1 / maximumBlockDamage) * 50;
double realTime = System.currentTimeMillis() - startBreak;
double diff = predictedTime - realTime;

player.user.sendPacket(new WrapperPlayServerBlockChange(blockPosition, blockId));
clampBalance();

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 (diff < 25) { // Reduce buffer if "close enough"
blockBreakBalance *= 0.9;
} else { // Otherwise, increase buffer
blockBreakBalance += diff;
}

if (flagAndAlert("Diff=" + diff + ",Balance=" + blockBreakBalance) && shouldModifyPackets()) {
event.setCancelled(true);
player.onPacketCancel();
}
if (blockBreakBalance > 1000) { // If more than a second of advantage
if (flagAndAlert("Diff=" + diff + ",Balance=" + blockBreakBalance) && shouldModifyPackets()) {
blockBreak.cancel();
}

lastFinishBreak = System.currentTimeMillis();
}

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 ((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));
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/ac/grim/grimac/checks/type/BlockBreakCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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) {}
}
Original file line number Diff line number Diff line change
@@ -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.*;
Expand Down Expand Up @@ -454,41 +452,41 @@ public void onPacketReceive(PacketReceiveEvent event) {

if (event.getPacketType() == PacketType.Play.Client.PLAYER_DIGGING) {
final WrapperPlayClientPlayerDigging packet = new WrapperPlayClientPlayerDigging(event);
final BlockBreak blockBreak = new BlockBreak(packet, player);
final DiggingAction action = packet.getAction();

player.checkManager.getPacketCheck(BadPacketsX.class).handle(blockBreak);
player.checkManager.getPacketCheck(BadPacketsZ.class).handle(blockBreak);
if (action == DiggingAction.START_DIGGING || action == DiggingAction.FINISHED_DIGGING || action == DiggingAction.CANCELLED_DIGGING) {
final BlockBreak blockBreak = new BlockBreak(packet.getBlockPosition(), packet.getBlockFace(), action, player.compensatedWorld.getWrappedBlockStateAt(packet.getBlockPosition()));

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(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0);
player.compensatedWorld.stopPredicting(packet);
}
player.checkManager.onBlockBreak(blockBreak);

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) {
if (blockBreak.isCancelled()) {
event.setCancelled(true);
player.onPacketCancel();
ResyncWorldUtil.resyncPosition(player, blockBreak.position, packet.getSequence());
} else {
if (action == DiggingAction.FINISHED_DIGGING && BREAKABLE.apply(blockBreak.block.getType())) {
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.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0);
player.compensatedWorld.stopPredicting(packet);
}
}

if (blockBreak.action == DiggingAction.START_DIGGING || blockBreak.action == DiggingAction.FINISHED_DIGGING || blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
if (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(), 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.handleBlockBreakPrediction(packet);
}
}
Expand Down
Loading

0 comments on commit c744501

Please sign in to comment.