diff --git a/API/pom.xml b/API/pom.xml new file mode 100644 index 000000000..3bfe20108 --- /dev/null +++ b/API/pom.xml @@ -0,0 +1,28 @@ + + + + Kauri + dev.brighten.anticheat + 2.9.2 + + 4.0.0 + + API + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 8 + 8 + + + + + + \ No newline at end of file diff --git a/API/src/main/java/dev/brighten/api/KauriAPI.java b/API/src/main/java/dev/brighten/api/KauriAPI.java new file mode 100644 index 000000000..3a07be67e --- /dev/null +++ b/API/src/main/java/dev/brighten/api/KauriAPI.java @@ -0,0 +1,20 @@ +package dev.brighten.api; + +import dev.brighten.api.handlers.ExemptHandler; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +public class KauriAPI { + + public static KauriAPI INSTANCE; + + public ExemptHandler exemptHandler; + public ScheduledExecutorService service; + + public KauriAPI() { + INSTANCE = this; + exemptHandler = new ExemptHandler(); + service = Executors.newSingleThreadScheduledExecutor(); + } +} diff --git a/API/src/main/java/dev/brighten/api/check/CheckType.java b/API/src/main/java/dev/brighten/api/check/CheckType.java new file mode 100644 index 000000000..711b915a3 --- /dev/null +++ b/API/src/main/java/dev/brighten/api/check/CheckType.java @@ -0,0 +1,6 @@ +package dev.brighten.api.check; + +public enum CheckType { + AIM, HITBOX, AUTOCLICKER, BADPACKETS, KILLAURA, SPEED, FLIGHT, VELOCITY, GENERAL, HAND, NOFALL, BLOCK, + EXPLOIT, INVENTORY +} diff --git a/API/src/main/java/dev/brighten/api/check/KauriCheck.java b/API/src/main/java/dev/brighten/api/check/KauriCheck.java new file mode 100644 index 000000000..f4fc41b7a --- /dev/null +++ b/API/src/main/java/dev/brighten/api/check/KauriCheck.java @@ -0,0 +1,16 @@ +package dev.brighten.api.check; + +public interface KauriCheck { + String getName(); + String getDescription(); + boolean isEnabled(); + boolean isExecutable(); + boolean isDeveloper(); + CheckType getCheckType(); + float getVl(); + float getPunishVl(); + void setEnabled(boolean enabled); + void setExecutable(boolean executable); + void setVl(float vl); + void setPunishVl(float pvl); +} diff --git a/API/src/main/java/dev/brighten/api/handlers/ExemptHandler.java b/API/src/main/java/dev/brighten/api/handlers/ExemptHandler.java new file mode 100644 index 000000000..bc90f9318 --- /dev/null +++ b/API/src/main/java/dev/brighten/api/handlers/ExemptHandler.java @@ -0,0 +1,63 @@ +package dev.brighten.api.handlers; + +import dev.brighten.api.KauriAPI; +import dev.brighten.api.check.CheckType; +import dev.brighten.api.check.KauriCheck; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class ExemptHandler { + + private static List exemptList = new ArrayList<>(); + + public Exemption addExemption(UUID uuid, KauriCheck... checks) { + Exemption exemption = exemptList.stream() + .filter(exempt -> exempt.uuid.equals(uuid)) + .findFirst().orElseGet(() -> new Exemption(uuid, checks)); + + exemption.addChecks(checks); + + exemptList.add(exemption); + + return exemption; + } + + //Adds temporary exception. + public Exemption addExemption(UUID uuid, long millisLater, Consumer onRemove, KauriCheck... checks) { + Exemption exemption = addExemption(uuid, checks); + + KauriAPI.INSTANCE.service.schedule(() -> { + if(exemption.getChecks().size() == checks.length) { + exemptList.remove(exemption); + } else Arrays.stream(checks) + .filter(check -> exemption.getChecks().contains(check)) + .forEach(check -> exemption.getChecks().remove(check)); + + onRemove.accept(exemption); + }, millisLater, TimeUnit.MILLISECONDS); + + return exemption; + } + + public boolean isExempt(UUID uuid, KauriCheck... checks) { + Exemption exemption = getExemption(uuid); + + return Arrays.stream(checks).anyMatch(check -> exemption.getChecks().contains(check)); + } + + public boolean isExempt(UUID uuid, CheckType... types) { + return getExemption(uuid).getChecks().stream(). + anyMatch(check -> + Arrays.stream(types).anyMatch(type -> check.getCheckType().equals(type))); + } + + public Exemption getExemption(UUID uuid) { + return exemptList.stream().filter(exempt -> exempt.uuid.equals(uuid)).findFirst() + .orElseGet(() -> addExemption(uuid)); + } +} diff --git a/API/src/main/java/dev/brighten/api/handlers/Exemption.java b/API/src/main/java/dev/brighten/api/handlers/Exemption.java new file mode 100644 index 000000000..999575a23 --- /dev/null +++ b/API/src/main/java/dev/brighten/api/handlers/Exemption.java @@ -0,0 +1,29 @@ +package dev.brighten.api.handlers; + +import dev.brighten.api.check.KauriCheck; +import lombok.Getter; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +public class Exemption { + public final UUID uuid; + @Getter + private final Set checks; + + public Exemption(UUID uuid, KauriCheck... checks) { + this.uuid = uuid; + this.checks = new HashSet<>(Arrays.asList(checks)); + } + + public Exemption(UUID uuid) { + this.uuid = uuid; + checks = new HashSet<>(); + } + + public void addChecks(KauriCheck... checks) { + this.checks.addAll(Arrays.asList(checks)); + } +} diff --git a/API/src/main/java/dev/brighten/api/listener/KauriFlagEvent.java b/API/src/main/java/dev/brighten/api/listener/KauriFlagEvent.java new file mode 100644 index 000000000..494994cbf --- /dev/null +++ b/API/src/main/java/dev/brighten/api/listener/KauriFlagEvent.java @@ -0,0 +1,20 @@ +package dev.brighten.api.listener; + +import cc.funkemunky.api.events.AtlasEvent; +import cc.funkemunky.api.events.Cancellable; +import dev.brighten.api.check.KauriCheck; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.bukkit.entity.Player; + +@RequiredArgsConstructor +public class KauriFlagEvent extends AtlasEvent implements Cancellable { + + @Getter + @Setter + private boolean cancelled; + public final Player player; + public final KauriCheck check; + public final String information; +} diff --git a/Free/pom.xml b/Free/pom.xml new file mode 100644 index 000000000..dc293228e --- /dev/null +++ b/Free/pom.xml @@ -0,0 +1,60 @@ + + + + Kauri + dev.brighten.anticheat + 2.9.2 + + 4.0.0 + + Free + ${parent.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 8 + 8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + dev.brighten.anticheat + Impl + ${parent.version} + provided + + + + \ No newline at end of file diff --git a/Free/src/main/java/dev/brighten/anticheat/check/FreeChecks.java b/Free/src/main/java/dev/brighten/anticheat/check/FreeChecks.java new file mode 100644 index 000000000..8250ebc00 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/FreeChecks.java @@ -0,0 +1,42 @@ +package dev.brighten.anticheat.check; + +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.Priority; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckRegister; +import dev.brighten.anticheat.check.impl.combat.autoclicker.AutoclickerA; +import dev.brighten.anticheat.check.impl.combat.hitbox.ReachA; +import dev.brighten.anticheat.check.impl.movement.fly.FlyA; +import dev.brighten.anticheat.check.impl.movement.nofall.NoFallA; +import dev.brighten.anticheat.check.impl.movement.speed.SpeedA; +import dev.brighten.anticheat.check.impl.packets.badpackets.*; + +@Init(priority = Priority.LOWEST) +public class FreeChecks implements CheckRegister { + + public FreeChecks() { + registerChecks(); + } + + @Override + public void registerChecks() { + Check.register(new AutoclickerA()); + Check.register(new FlyA()); + Check.register(new NoFallA()); + Check.register(new ReachA()); + Check.register(new SpeedA()); + Check.register(new BadPacketsA()); + Check.register(new BadPacketsB()); + Check.register(new BadPacketsC()); + Check.register(new BadPacketsD()); + Check.register(new BadPacketsE()); + Check.register(new BadPacketsF()); + Check.register(new BadPacketsG()); + Check.register(new BadPacketsH()); + Check.register(new BadPacketsI()); + Check.register(new BadPacketsK()); + Check.register(new BadPacketsL()); + Check.register(new BadPacketsM()); + //Check.register(new BadPacketsN()); + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/combat/autoclicker/AutoclickerA.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/combat/autoclicker/AutoclickerA.java new file mode 100644 index 000000000..2833b871e --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/combat/autoclicker/AutoclickerA.java @@ -0,0 +1,44 @@ +package dev.brighten.anticheat.check.impl.combat.autoclicker; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInArmAnimationPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import dev.brighten.anticheat.check.api.*; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "Autoclicker (A)", description = "A fast click check.", checkType = CheckType.AUTOCLICKER, + punishVL = 2) +@Cancellable(cancelType = CancelType.INTERACT) +public class AutoclickerA extends Check { + + private int flyingTicks, cps; + + @Setting(name = "cpsToFlag") + private static int cpsToFlag = 22; + + @Setting(name = "cpsToBan") + private static int cpsToBan = 28; + + @Packet + public void onFlying(WrappedInFlyingPacket packet, long timeStamp) { + flyingTicks++; + if(flyingTicks >= 20) { + if(cps > cpsToFlag) { + if(cps > cpsToBan) vl++; + flag("cps=%v", cps); + } + debug("cps=%v", cps); + + flyingTicks = cps = 0; + } + } + + @Packet + public void onArmAnimation(WrappedInArmAnimationPacket packet) { + if(!data.playerInfo.breakingBlock + && data.playerInfo.lastBrokenBlock.hasPassed(5) + && data.playerInfo.lastBlockPlace.hasPassed(2)) + cps++; + debug("breaking=%v lastBroken=%v", data.playerInfo.breakingBlock, + data.playerInfo.lastBrokenBlock.getPassed()); + } +} \ No newline at end of file diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/combat/hitbox/ReachA.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/combat/hitbox/ReachA.java new file mode 100644 index 000000000..038bcce26 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/combat/hitbox/ReachA.java @@ -0,0 +1,70 @@ +package dev.brighten.anticheat.check.impl.combat.hitbox; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInUseEntityPacket; +import cc.funkemunky.api.utils.KLocation; +import cc.funkemunky.api.utils.world.CollisionBox; +import cc.funkemunky.api.utils.world.EntityData; +import cc.funkemunky.api.utils.world.types.SimpleCollisionBox; +import dev.brighten.anticheat.check.api.*; +import dev.brighten.api.check.CheckType; +import lombok.val; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@CheckInfo(name = "Reach (A)", checkType = CheckType.HITBOX, punishVL = 5, description = "A simple distance check.") +@Cancellable(cancelType = CancelType.ATTACK) +public class ReachA extends Check { + + private long lastUse; + private LivingEntity target; + private double buffer; + + @Packet + public void onFlying(WrappedInUseEntityPacket packet, long timeStamp) { + if(data.playerInfo.creative || data.targetPastLocation.previousLocations.size() < 10) return; + + List targetBoxes = data.targetPastLocation + .getEstimatedLocation(timeStamp, (data.lagInfo.transPing + 3) * 50, 100L) + .stream().map(loc -> getHitbox(target, loc)).collect(Collectors.toList()); + + double distance = 69; + + val bounds = getHitbox(target, new KLocation(0,0,0)); + + if(bounds == null) return; + for (SimpleCollisionBox target : targetBoxes) { + distance = Math.min(distance, data.box.distance(target)); + //target.draw(WrappedEnumParticle.FLAME, Collections.singleton(data.getPlayer())); + } + + if(data.lagInfo.lastPacketDrop.hasPassed(3)) { + if (distance > 3.15 && distance != 69) { + if (++buffer > 6) { + vl++; + flag("distance=%v.2 buffer=%v", distance, buffer); + } + } else buffer -= buffer > 0 ? 0.1 : 0; + } else buffer-= buffer > 0 ? 0.02 : 0; + + debug("distance=%v.3 boxes=%v buffer=%v", distance, targetBoxes.size(), buffer); + } + + @Packet + public void onUse(WrappedInUseEntityPacket packet, long timeStamp) { + if (packet.getAction().equals(WrappedInUseEntityPacket.EnumEntityUseAction.ATTACK) + && packet.getEntity() instanceof LivingEntity) { + lastUse = timeStamp; + target = (LivingEntity) packet.getEntity(); + } + } + + private static SimpleCollisionBox getHitbox(Entity entity, KLocation loc) { + CollisionBox box = EntityData.getEntityBox(loc, entity); + return box instanceof SimpleCollisionBox ? (SimpleCollisionBox) box : null; + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/movement/fly/FlyA.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/movement/fly/FlyA.java new file mode 100644 index 000000000..357bf55d3 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/movement/fly/FlyA.java @@ -0,0 +1,87 @@ +package dev.brighten.anticheat.check.impl.movement.fly; + +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.MathUtils; +import dev.brighten.anticheat.check.api.Cancellable; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; +import net.minecraft.server.v1_8_R3.BlockClay; + +@CheckInfo(name = "Fly (A)", description = "Simple fly check.", punishVL = 10, + checkType = CheckType.FLIGHT, vlToFlag = 1, developer = true) +@Cancellable +public class FlyA extends Check { + + private static double GROUND = 1 / 64d, CHUNK_LOAD = -0.1 * 0.98D; + + @Packet + public void onFlying(WrappedInFlyingPacket packet, long timeStamp) { + if(!packet.isPos() || data.playerInfo.lastTeleportTimer.hasNotPassed(1) + || data.playerInfo.flightCancel + || timeStamp - data.playerInfo.lastVelocityTimestamp <= 200L + || data.playerInfo.lastVelocity.hasNotPassed(2) + || data.playerInfo.blockAboveTimer.hasNotPassed(3) + || data.playerInfo.lastRespawnTimer.hasNotPassed(5)) return; + + long start = System.nanoTime(); + + boolean hitHead = (packet.getY() + 1.8f) % GROUND <= 1E-8; + + if(Math.abs(data.playerInfo.deltaY - CHUNK_LOAD) < 1E-5) { + debug("chunk isn't loaded"); + return; + } + + boolean ground = data.playerInfo.clientGround; + boolean lground = data.playerInfo.lClientGround; + + double lDeltaY = data.playerInfo.lClientGround ? 0 : data.playerInfo.lDeltaY; + + long end = -1; + if(!ground && !hitHead) { + double predicted = (lDeltaY - 0.08) * (double)0.98f; + + if(lground) { + if(data.playerInfo.deltaY > 0) { + predicted = data.playerInfo.blockAboveTimer.hasNotPassed(3) + ? Math.min(data.playerInfo.deltaY, data.playerInfo.jumpHeight) + : data.playerInfo.jumpHeight; + } else { + double toCheck = (predicted - 0.08) * (double)0.98f; + + if(Math.abs(toCheck - data.playerInfo.deltaY) + < Math.abs(predicted - data.playerInfo.deltaY)) { + predicted = toCheck; + } + } + } + + if(Math.abs(predicted) < 0.005) { + if(data.playerVersion.isBelow(ProtocolVersion.V1_9)) + predicted = 0; + double last = predicted; + predicted-= 0.08; + predicted*= (double)0.98f; + if(Math.abs(data.playerInfo.deltaY - predicted) > Math.abs(last - data.playerInfo.deltaY)) + predicted = last; + } + + double check = Math.abs(data.playerInfo.deltaY - predicted); + + if(check > 0.016 && data.playerInfo.lastHalfBlock.hasPassed(5) + && data.playerInfo.lastVelocity.hasPassed(4)) { + vl++; + flag("deltaY=%v.4 predicted=%v.4", data.playerInfo.deltaY, predicted); + } else if(vl > 0) vl-= 0.1; + end = System.nanoTime() - start; + + debug(Color.Green + "deltaY=%v difference=%v", data.playerInfo.deltaY, check); + } + + debug("ground=%v fground=%v hitHead=%v time=%v", ground, lground, hitHead, end); + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/movement/nofall/NoFallA.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/movement/nofall/NoFallA.java new file mode 100644 index 000000000..d4fb5298d --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/movement/nofall/NoFallA.java @@ -0,0 +1,61 @@ +package dev.brighten.anticheat.check.impl.movement.nofall; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.utils.MathUtils; +import cc.funkemunky.api.utils.world.types.SimpleCollisionBox; +import dev.brighten.anticheat.check.api.Cancellable; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "NoFall (A)", description = "Checks to make sure the ground packet from the client is legit", + checkType = CheckType.NOFALL, punishVL = 20, vlToFlag = 3, executable = false) +@Cancellable +public class NoFallA extends Check { + + @Packet + public void onPacket(WrappedInFlyingPacket packet, long timeStamp) { + + boolean flag = data.playerInfo.clientGround + ? data.playerInfo.deltaY != 0 + && (Math.abs(data.playerInfo.deltaY) >= Math.abs(data.playerInfo.lDeltaY)) + && !data.playerInfo.serverGround + && data.playerInfo.lastBlockPlace.hasPassed(10) + : data.playerInfo.deltaY == 0 && data.playerInfo.lDeltaY == 0; + + if(data.playerInfo.deltaY < 0 && data.playerInfo.clientGround && flag) { + for (SimpleCollisionBox sBox : data.blockInfo.belowCollisions) { + double minDelta = sBox.yMax - data.playerInfo.from.y; + + if(MathUtils.getDelta(data.playerInfo.deltaY, minDelta) < 1E-7) { + flag = false; + break; + } + } + } + + if(!data.playerInfo.flightCancel + && data.playerInfo.lastHalfBlock.hasPassed(4) + && !data.blockInfo.onSlime + && !data.blockInfo.blocksAbove + && data.playerInfo.lastTeleportTimer.hasPassed(1) + && data.playerInfo.lastBlockPlace.hasPassed(8) + && data.playerInfo.lastVelocity.hasPassed(4) + && (data.playerInfo.deltaY != 0 || data.playerInfo.deltaXZ > 0) + && data.playerInfo.blockAboveTimer.hasPassed(10) + && flag) { + vl+= data.lagInfo.lagging + || data.playerInfo.nearGround + || data.blockInfo.blocksNear + ? 1 : data.playerInfo.clientGround ? 2 : 3; + + if(vl > 2) { + flag("ground=" + data.playerInfo.clientGround + " deltaY=" + data.playerInfo.deltaY); + } + } else vl-= vl > 0 ? 0.2f : 0; + + debug("ground=" + data.playerInfo.clientGround + " collides=" + data.blockInfo.collidesVertically + + " deltaY=" + data.playerInfo.deltaY + " vl=" + vl); + } +} \ No newline at end of file diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/movement/speed/SpeedA.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/movement/speed/SpeedA.java new file mode 100644 index 000000000..31727ff51 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/movement/speed/SpeedA.java @@ -0,0 +1,90 @@ +package dev.brighten.anticheat.check.impl.movement.speed; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInUseEntityPacket; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutVelocityPacket; +import cc.funkemunky.api.utils.PlayerUtils; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.anticheat.processing.TagsBuilder; +import dev.brighten.api.check.CheckType; +import org.bukkit.entity.EntityType; +import org.bukkit.potion.PotionEffectType; + +@CheckInfo(name = "Speed (A)", description = "Minecraft code speed acceleration check.", + checkType = CheckType.SPEED, developer = true) +public class SpeedA extends Check { + + private double ldxz = .12f; + private float friction = 0.91f; + private float buffer; + + @Packet + public void onVelocity(WrappedOutVelocityPacket packet) { + if(packet.getId() == data.getPlayer().getEntityId()) { + data.runKeepaliveAction(ka -> { + ldxz = Math.hypot(packet.getX(), packet.getZ()); + debug("set velocity: %v.3", ldxz); + }); + } + } + + @Packet + public void onFlying(WrappedInFlyingPacket packet) { + checkProccesing: + { + if (!packet.isPos() + || (data.playerInfo.deltaY == 0 && data.playerInfo.deltaXZ == 0) + || data.playerInfo.serverPos) { + break checkProccesing; + } + float drag = friction; + + TagsBuilder tags = new TagsBuilder(); + double moveFactor = data.getPlayer().getWalkSpeed() / 2f; + + moveFactor+= moveFactor * 0.3f; + + if(data.potionProcessor.hasPotionEffect(PotionEffectType.SPEED)) + moveFactor += (PlayerUtils.getPotionEffectLevel(data.getPlayer(), PotionEffectType.SPEED) + * (0.20000000298023224D)) * moveFactor; + + if(data.potionProcessor.hasPotionEffect(PotionEffectType.SLOW)) + moveFactor += (PlayerUtils.getPotionEffectLevel(data.getPlayer(), PotionEffectType.SLOW) + * (-0.15000000596046448D)) * moveFactor; + + if (data.playerInfo.lClientGround) { + tags.addTag("ground"); + drag *= 0.91f; + moveFactor *= 0.16277136 / (drag * drag * drag); + + if (data.playerInfo.jumped) { + tags.addTag("jumped"); + moveFactor += 0.2; + } + } else { + tags.addTag("air"); + drag = 0.91f; + moveFactor = 0.026f; + } + + double ratio = (data.playerInfo.deltaXZ - ldxz) / moveFactor * 100; + + if (ratio > 100.8 && data.playerInfo.lastBrokenBlock.hasPassed(data.lagInfo.transPing + 1) + && !data.playerInfo.generalCancel && data.playerInfo.lastVelocity.hasPassed(2)) { + buffer+= ratio > 1000 ? 3 : 1; + + if(buffer > 2) { + vl++; + flag("p=%v.1% dxz=%v.3 aimove=%v.3 tags=%v", + ratio, data.playerInfo.deltaXZ, data.predictionService.aiMoveSpeed, tags.build()); + } + } else if(buffer > 0) buffer-= 0.2f; + debug("ratio=%v.1 tags=%v", ratio, tags.build()); + + ldxz = data.playerInfo.deltaXZ * drag; + } + friction = data.blockInfo.currentFriction; + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsA.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsA.java new file mode 100644 index 000000000..e94ab037a --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsA.java @@ -0,0 +1,31 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInBlockDigPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInBlockPlacePacket; +import cc.funkemunky.api.utils.BlockUtils; +import dev.brighten.anticheat.check.api.*; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (A)", description = "Checks for blockDig and blockPlace times.", + checkType = CheckType.BADPACKETS, punishVL = 12) +@Cancellable(cancelType = CancelType.INTERACT) +public class BadPacketsA extends Check { + + private long lastBlockPlace; + + @Packet + public void onDig(WrappedInBlockDigPacket packet, long timeStamp) { + if(timeStamp - lastBlockPlace < 5 && !data.lagInfo.lagging + && data.lagInfo.lastPacketDrop.hasPassed(5)) { + if(vl++ > 4) { + flag("unblocked and blocked in same tick."); + } + } else vl-= vl > 0 ? 0.5 : 0; + } + + @Packet + public void onPlace(WrappedInBlockPlacePacket packet, long timeStamp) { + if(packet.getPlayer().getItemInHand() != null + && BlockUtils.isTool(packet.getPlayer().getItemInHand())) lastBlockPlace = timeStamp; + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsB.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsB.java new file mode 100644 index 000000000..e99d93c56 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsB.java @@ -0,0 +1,32 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInEntityActionPacket; +import cc.funkemunky.api.utils.math.cond.MaxInteger; +import dev.brighten.anticheat.check.api.Cancellable; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (B)", description = "Checks for the spamming of sneak changes.", + checkType = CheckType.BADPACKETS, punishVL = 40) +@Cancellable +public class BadPacketsB extends Check { + + private long lastSneak; + private MaxInteger ticks = new MaxInteger(Integer.MAX_VALUE); + @Packet + public void onPlace(WrappedInEntityActionPacket action, long timeStamp) { + if(action.getAction().name().contains("SNEAK")) { + if(timeStamp - lastSneak <= 10) { + ticks.add(); + if(ticks.value() > 80) { + vl++; + flag("ticks=%v ping=%p tps=%t", ticks.value()); + } + } else ticks.subtract(ticks.value() > 40 ? 8 : 4); + lastSneak = timeStamp; + } + debug(action.getAction().name()); + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsC.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsC.java new file mode 100644 index 000000000..6ffc3915b --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsC.java @@ -0,0 +1,35 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInHeldItemSlotPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInTransactionPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInUseEntityPacket; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Cancellable; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (C)", description = "Checks for players who send slot packets at the same time as flying.", + checkType = CheckType.BADPACKETS, punishVL = 20) +@Cancellable +public class BadPacketsC extends Check { + + private long lastFlying; + + @Packet + public void use(WrappedInHeldItemSlotPacket packet, long current) { + if(current - lastFlying < 10) { + vl++; + if(vl > 11) { + flag("delta=%v", current - lastFlying); + } + } else if(vl > 0) vl--; + } + + @Packet + public void flying(WrappedInFlyingPacket packet, long current) { + lastFlying = current; + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsD.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsD.java new file mode 100644 index 000000000..a11ed6ca1 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsD.java @@ -0,0 +1,63 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInAbilitiesPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutAbilitiesPacket; +import cc.funkemunky.api.utils.RunUtils; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Cancellable; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; +import org.bukkit.GameMode; + +@CheckInfo(name = "BadPackets (D)", + description = "Checks for clients spoofing flight permissions.", + checkType = CheckType.BADPACKETS, punishVL = 10) +@Cancellable +public class BadPacketsD extends Check { + + boolean serverAllowed, clientAllowed; + + @Packet + public void server(WrappedOutAbilitiesPacket packet) { + if(packet.isAllowedFlight()) { + serverAllowed = true; + } else if(!clientAllowed) { + serverAllowed = false; + } + } + + @Packet + public void client(WrappedInAbilitiesPacket packet) { + if(packet.isAllowedFlight()) { + clientAllowed = true; + } else if(!serverAllowed) { + clientAllowed = false; + } + } + + @Packet + public void flying(WrappedInFlyingPacket packet, long timeStamp) { + if(timeStamp - data.creation < 1000L) { + serverAllowed = data.getPlayer().getAllowFlight(); + clientAllowed = data.getPlayer().getAllowFlight(); + RunUtils.task(() -> { + data.getPlayer().setAllowFlight(true); + data.getPlayer().setAllowFlight(false); + data.getPlayer().setGameMode(GameMode.SURVIVAL); + }, Kauri.INSTANCE); + } else { + if(!serverAllowed && clientAllowed) { + if(vl++ > 1) { + flag("server=" + serverAllowed + " client=" + clientAllowed); + RunUtils.task(() -> { + data.getPlayer().setFlying(false); + data.getPlayer().setAllowFlight(false); + }, Kauri.INSTANCE); + } + } else vl = 0; + } + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsE.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsE.java new file mode 100644 index 000000000..b780927f3 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsE.java @@ -0,0 +1,41 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInArmAnimationPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInUseEntityPacket; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.utils.TickTimer; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (E)", description = "Checks for a player not swinging their arm.", + checkType = CheckType.BADPACKETS, punishVL = 10, developer = true) +public class BadPacketsE extends Check { + + private TickTimer lastSwing; + private int attackTicks; + + @Override + public void setData(ObjectData data) { + super.setData(data); + lastSwing = new TickTimer( 8); + } + + @Packet + public void onFlying(WrappedInUseEntityPacket packet) { + if(!packet.getAction().equals(WrappedInUseEntityPacket.EnumEntityUseAction.ATTACK)) return; + + if(attackTicks++ > 3 && lastSwing.hasNotPassed(3) + && !data.lagInfo.lagging && data.lagInfo.lastPacketDrop.hasPassed(5)) { + vl++; + flag("has not swung since %v ticks", lastSwing.getPassed()); + } + } + + @Packet + public void onFlying(WrappedInArmAnimationPacket packet) { + lastSwing.reset(); + attackTicks = 0; + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsF.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsF.java new file mode 100644 index 000000000..e1a2d2947 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsF.java @@ -0,0 +1,24 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Event; +import dev.brighten.api.check.CheckType; +import org.bukkit.event.block.BlockPlaceEvent; + +@CheckInfo(name = "BadPackets (F)", description = "Checks if the block placed is the item in the player's hand.", + checkType = CheckType.BADPACKETS, punishVL = 2) +public class BadPacketsF extends Check { + + @Event + public void onBlockPlace(BlockPlaceEvent event) { + if(!event.getBlockPlaced().getType().isBlock()) return; + + boolean isNull = event.getItemInHand() == null; + if(isNull || !event.getItemInHand().getType().equals(event.getBlockPlaced().getType())) { + vl++; + flag("blockType=%v itemStack=%v", event.getBlockPlaced().getType().name(), + isNull ? "null" : event.getItemInHand().getType().name()); + } + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsG.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsG.java new file mode 100644 index 000000000..6589c58dd --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsG.java @@ -0,0 +1,30 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInBlockDigPacket; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (G)", description = "Looks for impossible dropping of items", + checkType = CheckType.BADPACKETS, + punishVL = 10) +public class BadPacketsG extends Check { + + private long lastItemDrop; + private int verbose; + @Packet + public void onPacket(WrappedInBlockDigPacket packet, long timeStamp) { + if(packet.getAction().equals(WrappedInBlockDigPacket.EnumPlayerDigType.DROP_ITEM)) { + long delta = timeStamp - lastItemDrop; + + if(delta < 35) { + if(verbose++ > 5) { + vl++; + flag("delta=" + delta); + } + } else verbose-= verbose > 0 ? 1 : 0; + lastItemDrop = timeStamp; + } + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsH.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsH.java new file mode 100644 index 000000000..aacbd60db --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsH.java @@ -0,0 +1,27 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInWindowClickPacket; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (I)", description = "Checks for clicking in inventory while moving.", + checkType = CheckType.BADPACKETS) +public class BadPacketsH extends Check { + + @Packet + public void windowClick(WrappedInWindowClickPacket packet, long timeStamp) { + if(data.playerInfo.deltaXZ > 0.1 + && !data.playerInfo.serverPos + && timeStamp - data.playerInfo.lastVelocityTimestamp > 3000L + && data.playerInfo.serverGround + && !data.blockInfo.inLiquid + && !data.blockInfo.onIce + && !data.playerInfo.flying) { + if(vl++ > 4) { + flag("clicked in window while moving"); + } + } else vl-= vl > 0 ? 0.5 : 0; + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsI.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsI.java new file mode 100644 index 000000000..a8652e52f --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsI.java @@ -0,0 +1,20 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInAbilitiesPacket; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (I)", checkType = CheckType.BADPACKETS, + description = "Checks if the player is sending isFlying while not have allowedFlight.", punishVL = 1) +public class BadPacketsI extends Check { + + @Packet + public void onFlying(WrappedInAbilitiesPacket packet) { + if(packet.isFlying() && !packet.isAllowedFlight() && !data.lagInfo.lagging) { + vl+= 2; + flag("isFlying=" + packet.isFlying() + " allowed=" + packet.isAllowedFlight()); + } + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsJ.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsJ.java new file mode 100644 index 000000000..91752b940 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsJ.java @@ -0,0 +1,8 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import dev.brighten.anticheat.check.api.Check; + +//TODO Make new check with this type. +public class BadPacketsJ extends Check { + +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsK.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsK.java new file mode 100644 index 000000000..98b848d7e --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsK.java @@ -0,0 +1,26 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInHeldItemSlotPacket; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (K)", description = "Checks for duplicate held item packets", + punishVL = 1, checkType = CheckType.BADPACKETS) +public class BadPacketsK extends Check { + + private int lastSlot = -1, buffer; + + @Packet + public void onHeld(WrappedInHeldItemSlotPacket packet) { + if(lastSlot != -1 && lastSlot == packet.getSlot() && data.lagInfo.lastPacketDrop.hasPassed(2)) { + if(++buffer > 3) { + vl++; + flag("current=%v;last=%v", packet.getSlot(), lastSlot); + } + } else if(buffer > 0) buffer--; + debug("slot=%v lastslot=%v", packet.getSlot(), lastSlot); + lastSlot = packet.getSlot(); + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsL.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsL.java new file mode 100644 index 000000000..aed71cdfc --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsL.java @@ -0,0 +1,27 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInBlockPlacePacket; +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; +import lombok.val; + +@CheckInfo(name = "BadPackets (L)", description = "Player sends block place packets without any item in hand", + checkType = CheckType.BADPACKETS, developer = true, maxVersion = ProtocolVersion.V1_8_9) +public class BadPacketsL extends Check { + + @Packet + public void onPlace(WrappedInBlockPlacePacket packet) { + val pos = packet.getPosition(); + if(packet.getItemStack() == null + || packet.getItemStack().getType().equals((XMaterial.AIR.parseMaterial())) + && (pos == null || (pos.getX() == -1 && pos.getY() == -1 && pos.getZ() == -1))) { + //TODO check if sends if player just right clicks block. + vl++; + flag("type=AIR"); + } else debug(packet.getItemStack().getType().name()); + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsM.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsM.java new file mode 100644 index 000000000..a3c85603f --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsM.java @@ -0,0 +1,21 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.utils.MathUtils; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (M)", description = "Checks for impossible pitch positions.", + checkType = CheckType.BADPACKETS, punishVL = 1) +public class BadPacketsM extends Check { + + @Packet + public void onFlying(WrappedInFlyingPacket packet) { + if(packet.isLook() && Math.abs(packet.getPitch()) > 90) { + vl++; + flag("pitch=%v", MathUtils.round(packet.getPitch(), 2)); + } + } +} diff --git a/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsN.java b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsN.java new file mode 100644 index 000000000..2274f8b06 --- /dev/null +++ b/Free/src/main/java/dev/brighten/anticheat/check/impl/packets/badpackets/BadPacketsN.java @@ -0,0 +1,40 @@ +package dev.brighten.anticheat.check.impl.packets.badpackets; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; +import dev.brighten.anticheat.check.api.Packet; +import dev.brighten.api.check.CheckType; + +@CheckInfo(name = "BadPackets (N)", description = "Checks for improper y setting.", + developer = true, checkType = CheckType.BADPACKETS) +public class BadPacketsN extends Check { + + @Packet + public void onPacket(WrappedInFlyingPacket packet) { + double predicted = getY(data.playerInfo.deltaY); + + if(predicted < 0) return; + + double delta = Math.abs(data.playerInfo.deltaY - predicted); + if(data.playerInfo.deltaY > predicted && delta > 1E-14 + && data.playerInfo.liquidTimer.hasPassed(20) + && data.playerInfo.slimeTimer.hasPassed(8) + && !data.playerInfo.flightCancel) { + vl++; + flag("delta=%v y=%v", delta, data.playerInfo.deltaY); + } + debug("%v, %v, %v", data.playerInfo.deltaY, predicted, delta); + } + + private double getY(double deltaY) { + double currentPredictor = data.playerInfo.jumpHeight; + + while(Math.abs(currentPredictor - deltaY) > 0.065 && currentPredictor > 0) { + currentPredictor-= 0.08; + currentPredictor*= 0.98f; + } + + return currentPredictor; + } +} diff --git a/Impl/pom.xml b/Impl/pom.xml new file mode 100644 index 000000000..8175cf001 --- /dev/null +++ b/Impl/pom.xml @@ -0,0 +1,106 @@ + + + + Kauri + dev.brighten.anticheat + 2.9.2 + + 4.0.0 + + Impl + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 8 + 8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + false + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + dev.brighten.anticheat + API + ${project.parent.version} + compile + + + org.github.spigot + 1.8.8 + 1.8.8 + provided + + + gg.manny + lunar-client-api + 1.1 + provided + + + org.github.spigot + 1.13.2 + 1.13.2 + provided + + + org.github.spigot + 1.12.2 + 1.12.2 + provided + + + org.github.spigot + 1.7.10 + 1.7.10 + provided + + + + + \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/Kauri.java b/Impl/src/main/java/dev/brighten/anticheat/Kauri.java new file mode 100644 index 000000000..c249a8bdd --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/Kauri.java @@ -0,0 +1,197 @@ +package dev.brighten.anticheat; + +import cc.funkemunky.api.Atlas; +import cc.funkemunky.api.commands.ancmd.CommandManager; +import cc.funkemunky.api.config.MessageHandler; +import cc.funkemunky.api.profiling.ToggleableProfiler; +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.api.TinyProtocolHandler; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutTransaction; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.MiscUtils; +import cc.funkemunky.api.utils.RunUtils; +import cc.funkemunky.api.utils.math.RollingAverageDouble; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.data.DataManager; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.logs.LoggerManager; +import dev.brighten.anticheat.processing.EntityProcessor; +import dev.brighten.anticheat.processing.PacketProcessor; +import dev.brighten.anticheat.processing.keepalive.KeepaliveProcessor; +import dev.brighten.anticheat.utils.TickTimer; +import dev.brighten.api.KauriAPI; +import org.bukkit.Bukkit; +import org.bukkit.event.HandlerList; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class Kauri extends JavaPlugin { + + public static Kauri INSTANCE; + + public PacketProcessor packetProcessor; + public DataManager dataManager; + public LoggerManager loggerManager; + public KeepaliveProcessor keepaliveProcessor; + public EntityProcessor entityProcessor; + + //Lag Information + public RollingAverageDouble tps = new RollingAverageDouble(4, 20); + public TickTimer lastTickLag; + public long lastTick; + public CommandManager commandManager; + + public ScheduledExecutorService executor; + public ScheduledExecutorService loggingThread; + public ToggleableProfiler profiler; + public String LINK = ""; + + public boolean enabled = false; + public TickTimer lastEnabled; + + public MessageHandler msgHandler; + public KauriAPI kauriAPI; + + public boolean isNewer; + + public List onReload = new ArrayList<>(); + + public void onEnable() { + MiscUtils.printToConsole(Color.Red + "Starting Kauri " + getDescription().getVersion() + "..."); + INSTANCE = this; + + load(); //Everything in one method so we can use it in other places like when reloading. + } + + public void onDisable() { + unload(false); + } + + public void unload(boolean reload) { + reload = false; + enabled = reload; + MiscUtils.printToConsole("&7Unregistering Kauri API..."); + kauriAPI.service.shutdown(); + loggingThread.shutdown(); + + MiscUtils.printToConsole("Unregistering processors..."); + keepaliveProcessor.stop(); + keepaliveProcessor = null; + + + if(!reload) { + kauriAPI = null; + MiscUtils.printToConsole("&7Unregistering Atlas and Bukkit listeners..."); + HandlerList.unregisterAll(this); //Unregistering Bukkit listeners. + Atlas.getInstance().getEventManager().unregisterAll(this); //Unregistering Atlas listeners. + MiscUtils.printToConsole("&7Unregistering commands..."); + //Unregister all commands starting with the arg "Kauri" + Atlas.getInstance().getCommandManager(Kauri.INSTANCE).unregisterCommands(); + Atlas.getInstance().getCommandManager(Kauri.INSTANCE).unregisterCommand("kauri"); + MiscUtils.printToConsole("&7Shutting down all Bukkit tasks..."); + Bukkit.getScheduler().cancelTasks(this); //Cancelling all Bukkit tasks for this plugin. + } + + MiscUtils.printToConsole("&7Unloading DataManager..."); + //Clearing the dataManager. + dataManager.dataMap.values().forEach(ObjectData::onLogout); + dataManager.dataMap.clear(); + + MiscUtils.printToConsole("&7Stopping log process..."); + loggerManager.storage.shutdown(); + loggerManager.storage = null; + loggerManager = null; + + MiscUtils.printToConsole("&7Nullifying entries so plugin unloads from RAM completely..."); + //Clearing the checks. + Check.checkClasses.clear(); + Check.checkSettings.clear(); + profiler.setEnabled(false); + profiler = null; + packetProcessor = null; + + MiscUtils.printToConsole("&7Clearing checks and cached entity information..."); + entityProcessor.vehicles.clear(); //Clearing all registered vehicles. + entityProcessor.task.cancel(); + + entityProcessor = null; + + MiscUtils.printToConsole("&7Finshing up nullification..."); + Atlas.getInstance().getPluginCommandManagers().remove(this.getName()); + msgHandler = null; + dataManager = null; + onReload.clear(); + onReload = null; + executor.shutdown(); //Shutting down threads. + + INSTANCE = null; + MiscUtils.printToConsole("&aCompleted shutdown process."); + } + + public void reload() { + Kauri.INSTANCE.reloadConfig(); + + Check.checkClasses.clear(); + Check.checkSettings.clear(); + dataManager.dataMap.clear(); + entityProcessor.vehicles.clear(); + + Atlas.getInstance().initializeScanner(this, false, false); + + loggerManager = new LoggerManager(); + + for (Runnable runnable : onReload) { + runnable.run(); + onReload.remove(runnable); + } + + Bukkit.getOnlinePlayers().forEach(dataManager::createData); + } + + public void load() { + isNewer = ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_13); + Load.load(); + } + + public void runTpsTask() { + lastTickLag = new TickTimer(6); + AtomicInteger ticks = new AtomicInteger(); + AtomicLong lastTimeStamp = new AtomicLong(0); + RunUtils.taskTimer(() -> { + ticks.getAndIncrement(); + long currentTime = System.currentTimeMillis(); + + if(currentTime - lastTick > 120) { + lastTickLag.reset(); + } + if(ticks.get() >= 10) { + ticks.set(0); + tps.add(500D / (currentTime - lastTimeStamp.get()) * 20); + lastTimeStamp.set(currentTime); + } + lastTick = currentTime; + Kauri.INSTANCE.lastTick = currentTime; + }, this, 1L, 1L); + + WrappedOutTransaction transaction =new WrappedOutTransaction(0, (short)69, false); + RunUtils.taskTimerAsync(() -> + Bukkit.getOnlinePlayers().forEach(player -> + TinyProtocolHandler.sendPacket(player, transaction)), 40L, 40L); + } + + public double getTps() { + return tps.getAverage(); + } + + public void onReload(Runnable runnable) { + onReload.add(runnable); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/Load.java b/Impl/src/main/java/dev/brighten/anticheat/Load.java new file mode 100644 index 000000000..796375b40 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/Load.java @@ -0,0 +1,161 @@ +package dev.brighten.anticheat; + +import cc.funkemunky.api.Atlas; +import cc.funkemunky.api.commands.ancmd.CommandManager; +import cc.funkemunky.api.config.MessageHandler; +import cc.funkemunky.api.profiling.ToggleableProfiler; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.MiscUtils; +import cc.funkemunky.api.utils.RunUtils; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.Config; +import dev.brighten.anticheat.classloader.KauriClassLoader; +import dev.brighten.anticheat.data.DataManager; +import dev.brighten.anticheat.logs.LoggerManager; +import dev.brighten.anticheat.processing.EntityProcessor; +import dev.brighten.anticheat.processing.PacketProcessor; +import dev.brighten.anticheat.processing.keepalive.KeepaliveProcessor; +import dev.brighten.anticheat.utils.SystemUtil; +import dev.brighten.anticheat.utils.TickTimer; +import dev.brighten.anticheat.utils.file.FileDownloader; +import dev.brighten.api.KauriAPI; +import org.bukkit.Bukkit; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Optional; +import java.util.concurrent.Executors; + +public class Load { + + public static void load() { + register("Kicking players online..."); + //Bukkit.getOnlinePlayers().forEach(player -> player.kickPlayer("Starting up...")); + register("Starting thread pool..."); + Kauri.INSTANCE.executor = Executors.newScheduledThreadPool(3); + Kauri.INSTANCE.loggingThread = Executors.newScheduledThreadPool(2); + + register("Loading config..."); + Kauri.INSTANCE.saveDefaultConfig(); + + register("Loading commands..."); + Kauri.INSTANCE.commandManager = new CommandManager(Kauri.INSTANCE); + + register("Loading messages..."); + Kauri.INSTANCE.msgHandler = new MessageHandler(Kauri.INSTANCE); + + register("Registering processors..."); + Kauri.INSTANCE.packetProcessor = new PacketProcessor(); + Kauri.INSTANCE.dataManager = new DataManager(); + Kauri.INSTANCE.keepaliveProcessor = new KeepaliveProcessor(); + Kauri.INSTANCE.entityProcessor = EntityProcessor.start(); + + register("Running scanner..."); + Atlas.getInstance().initializeScanner(Kauri.INSTANCE, true, true); + + register("Registering logging..."); + Kauri.INSTANCE.loggerManager = new LoggerManager(); + + if(Config.initChecks) { + register("Initializing checks..."); + Optional.ofNullable(Bukkit.getPluginManager().getPlugin("KauriLoader")).ifPresent(plugin -> { + Config.license = plugin.getConfig().getString("license"); + }); + + try { + Kauri.INSTANCE.LINK = "https://funkemunky.cc/download?name=Kauri_New&license=" + + URLEncoder.encode(Config.license, "UTF-8") + + "&version=" + URLEncoder.encode(Kauri.INSTANCE.getDescription().getVersion(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + startClassLoader(); + } + + register("Setting the language to " + Color.Yellow + Config.language); + Kauri.INSTANCE.msgHandler.setCurrentLang(Config.language); + + register("Loading API..."); + Kauri.INSTANCE.kauriAPI = new KauriAPI(); + + register("Registering checks..."); + Check.registerChecks(); + + register("Running tps task..."); + Kauri.INSTANCE.runTpsTask(); + register("Starting profiler..."); + Kauri.INSTANCE.profiler = new ToggleableProfiler(); + Kauri.INSTANCE.profiler.setEnabled(true); + + if(Bukkit.getOnlinePlayers().size() > 0) { + RunUtils.taskLater(() -> { + MiscUtils.printToConsole(Color.Gray + "Detected players! Creating data objects..."); + Bukkit.getOnlinePlayers().forEach(Kauri.INSTANCE.dataManager::createData); + }, Kauri.INSTANCE, 6L); + } + Kauri.INSTANCE.lastEnabled = new TickTimer(20); + Kauri.INSTANCE.enabled = true; + Kauri.INSTANCE.lastEnabled.reset(); + + Bukkit.getWorlds().forEach(world -> Kauri.INSTANCE.entityProcessor.vehicles.put(world.getUID(), new ArrayList<>())); + } + + private static void register(String string) { + MiscUtils.printToConsole(Color.Gray + string); + } + + private static void startClassLoader() { + //don't fucking modify or i will snap ur neck + for (int i = 0; i < 100; i++) { + SystemUtil.CRC_32.update(("GzB@aRC1$^JEKQxGmSBAQ%%WohM7LZnuC*pVhf0%B6VyZMyOvU" + i).getBytes(StandardCharsets.UTF_8)); + } + + loadVersion(Kauri.INSTANCE.LINK); + } + + private static String regular = "dev.brighten.anticheat.check.RegularChecks", + free = "dev.brighten.anticheat.check.FreeChecks", premium = "dev.brighten.anticheat.premium.PremiumChecks"; + + private static void loadVersion(String url) { + + FileDownloader fileDownloader = new FileDownloader(url); + File downloadedFile = fileDownloader.download(); + + if (downloadedFile.exists()) { + try { + KauriClassLoader kauriClassLoader = new KauriClassLoader(downloadedFile.toURI().toURL(), Kauri.INSTANCE.getClass().getClassLoader()); + + Optional.ofNullable(kauriClassLoader.loadClass(free)).ifPresent(clazz -> { + try { + clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + }); + Optional.ofNullable(kauriClassLoader.loadClass(regular)).ifPresent(clazz -> { + try { + clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + }); + Optional.ofNullable(kauriClassLoader.loadClass(premium)).ifPresent(clazz -> { + try { + clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + } + }); + downloadedFile.delete(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/CancelType.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/CancelType.java new file mode 100644 index 000000000..980e09eb4 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/CancelType.java @@ -0,0 +1,5 @@ +package dev.brighten.anticheat.check.api; + +public enum CancelType { + ATTACK, MOVEMENT, INTERACT, PLACE, BREAK, INVENTORY +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/Cancellable.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/Cancellable.java new file mode 100644 index 000000000..8030d6c66 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/Cancellable.java @@ -0,0 +1,12 @@ +package dev.brighten.anticheat.check.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface Cancellable { + CancelType cancelType() default CancelType.MOVEMENT; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/Check.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/Check.java new file mode 100644 index 000000000..e406c0ad3 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/Check.java @@ -0,0 +1,399 @@ +package dev.brighten.anticheat.check.api; + +import cc.funkemunky.api.Atlas; +import cc.funkemunky.api.bungee.BungeeAPI; +import cc.funkemunky.api.reflections.types.WrappedClass; +import cc.funkemunky.api.reflections.types.WrappedMethod; +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.api.TinyProtocolHandler; +import cc.funkemunky.api.tinyprotocol.api.packets.channelhandler.TinyProtocol1_7; +import cc.funkemunky.api.tinyprotocol.api.packets.channelhandler.TinyProtocol1_8; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutCloseWindowPacket; +import cc.funkemunky.api.utils.*; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.api.KauriAPI; +import dev.brighten.api.check.CheckType; +import dev.brighten.api.check.KauriCheck; +import dev.brighten.api.listener.KauriFlagEvent; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.val; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@NoArgsConstructor +public class Check implements KauriCheck { + + public static Map checkClasses = new ConcurrentHashMap<>(); + public static Map checkSettings = new ConcurrentHashMap<>(); + + private static WrappedClass protocolClass = ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8) + ? new WrappedClass(TinyProtocol1_7.class) : new WrappedClass(TinyProtocol1_8.class); + private static WrappedMethod getChannel = protocolClass.getMethod("getChannel", Player.class); + + + public ObjectData data; + @Getter + public String name, description; + @Getter + @Setter + public boolean enabled, executable, cancellable; + @Getter + public boolean developer; + @Getter + @Setter + public float vl, punishVl, vlToFlag; + public ProtocolVersion minVersion, maxVersion; + @Getter + public CheckType checkType; + + public CancelType cancelMode; + + public boolean exempt, banExempt; + private TickTimer lastExemptCheck = new TickTimer(20); + + private TickTimer lastAlert = new TickTimer(MathUtils.millisToTicks(Config.alertsDelay)); + + public void setData(ObjectData data) { + this.data = data; + } + + public static void register(Check check) { + if(!check.getClass().isAnnotationPresent(CheckInfo.class)) { + MiscUtils.printToConsole("Could not register " + check.getClass().getSimpleName() + + " because @CheckInfo was not present."); + return; + } + CheckInfo info = check.getClass().getAnnotation(CheckInfo.class); + MiscUtils.printToConsole("Registering... " + info.name()); + WrappedClass checkClass = new WrappedClass(check.getClass()); + String name = info.name(); + + CancelType type = null; + if(check.getClass().isAnnotationPresent(Cancellable.class)) + type = check.getClass().getAnnotation(Cancellable.class).cancelType(); + CheckSettings settings = new CheckSettings(info.name(), info.description(), info.checkType(), type, + info.punishVL(), info.vlToFlag(), info.minVersion(), info.maxVersion()); + + String path = "checks." + name; + settings.enabled = new ConfigDefault<>(info.enabled(), + path + ".enabled", Kauri.INSTANCE).get(); + settings.executable = new ConfigDefault<>(info.executable(), + path + ".executable", Kauri.INSTANCE).get(); + settings.cancellable = new ConfigDefault<>(info.cancellable(), + path + ".cancellable", Kauri.INSTANCE).get(); + + final String spath = path + ".settings."; + checkClass.getFields(field -> Modifier.isStatic(field.getModifiers()) + && field.isAnnotationPresent(Setting.class)) + .forEach(field -> { + Setting setting = field.getAnnotation(Setting.class); + + MiscUtils.printToConsole("Found setting " + setting.name() + "! Processing..."); + field.set(null, new ConfigDefault<>(field.get(null), + spath + setting.name(), Kauri.INSTANCE).get()); + }); + + checkSettings.put(checkClass, settings); + checkClasses.put(checkClass, info); + MiscUtils.printToConsole("Registered check " + info.name()); + } + + public void flag(String information, Object... variables) { + flag(false, information, variables); + } + + public void flag(boolean devAlerts, String information, Object... variables) { + flag(devAlerts, Integer.MAX_VALUE, information, variables); + } + + public void flag(int resetVLTime, String information, Object... variables) { + flag(false, resetVLTime, information, variables); + } + + private long lastFlagRun = 0L; + + public synchronized void flag(boolean devAlerts, int resetVLTime, String information, Object... variables) { + if(Kauri.INSTANCE.getTps() < 18) devAlerts = true; + if(lastExemptCheck.hasPassed()) exempt = KauriAPI.INSTANCE.exemptHandler.isExempt(data.uuid, this); + if(exempt) return; + if(System.currentTimeMillis() - lastFlagRun < 50L) return; + lastFlagRun = System.currentTimeMillis(); + if(variables.length > 0 && information.contains("%v")) { + String[] splitInfo = information.split("%v"); + + for (int i = 0; i < splitInfo.length; i++) { + String split = splitInfo[i]; + + if(variables.length > i) { + if ((variables[i] instanceof Double || variables[i] instanceof Float) + && splitInfo.length > i + 1 && splitInfo[i + 1].startsWith(".")) { + String split2 = splitInfo[i + 1]; + + if (split2.length() >= 2) { + int parsed = -1; + for (int l = split2.length(); l > 1; l--) { + try { + parsed = Integer.parseInt(split2.substring(1, l)); + break; + } catch (NumberFormatException ignored) { + } + } + + if (parsed < 0) { + splitInfo[i] = split + variables[i]; + } else if(variables[i] instanceof Float) { + splitInfo[i + 1] = split2.replace("." + parsed, ""); + float var = (float) variables[i]; + + if(!Float.isNaN(var) && !Float.isInfinite(var)) { + splitInfo[i] = split + MathUtils.round(var, parsed); + } else splitInfo[i] = split + MathUtils.round(var, parsed); + } else if(variables[i] instanceof Double) { + splitInfo[i + 1] = split2.replace("." + parsed, ""); + double var = (double) variables[i]; + if(!Double.isNaN(var) && !Double.isInfinite(var)) { + splitInfo[i] = split + MathUtils.round(var, parsed); + } else splitInfo[i] = split + var; + } + } + } else splitInfo[i] = split + variables[i]; + } + } + information = String.join("", splitInfo); + } + final String finalInformation = information; + KauriFlagEvent event = new KauriFlagEvent(data.getPlayer(), this, finalInformation); + + event.setCancelled(!Config.alertDev); + + if(cancellable && cancelMode != null && vl > vlToFlag && data.lagInfo.lastPacketDrop.hasPassed(8)) { + switch(cancelMode) { + case ATTACK: { + for(int i = 0 ; i < 2 ; i++) data.typesToCancel.add(cancelMode); + break; + } + case INVENTORY: { + TinyProtocolHandler.sendPacket(data.getPlayer(), + new WrappedOutCloseWindowPacket(data.playerInfo.inventoryId)); + break; + } + default: { + data.typesToCancel.add(cancelMode); + break; + } + } + } + + Atlas.getInstance().getEventManager().callEvent(event); + boolean dev = devAlerts || (developer || vl <= vlToFlag); + Kauri.INSTANCE.executor.execute(() -> { + if(!event.isCancelled()) { + if(lastAlert.hasPassed(resetVLTime)) vl = 0; + final String info = finalInformation + .replace("%p", String.valueOf(data.lagInfo.transPing)) + .replace("%t", String.valueOf(MathUtils.round(Kauri.INSTANCE.getTps(), 2))); + if (Kauri.INSTANCE.lastTickLag.hasPassed() && (data.lagInfo.lastPacketDrop.hasPassed(5) + || data.lagInfo.lastPingDrop.hasPassed(20)) + && System.currentTimeMillis() - Kauri.INSTANCE.lastTick < 100L) { + if(vl > 0) Kauri.INSTANCE.loggerManager.addLog(data, this, info); + + if (lastAlert.hasPassed(MathUtils.millisToTicks(Config.alertsDelay))) { + List components = new ArrayList<>(); + + if(dev) { + components.add(new TextComponent(createTxt("&8[&cDev&8] "))); + } + val text = createTxt(Kauri.INSTANCE.msgHandler.getLanguage().msg("cheat-alert", + "&8[&6&lKauri&8] &f%player% &7flagged &f%check%" + + " &8(&ex%vl%&8) %experimental%"), info); + + text.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new TextComponent[] { + createTxt(Kauri.INSTANCE.msgHandler.getLanguage().msg("cheat-alert-hover", + "&eDescription&8: &f%desc%" + + "\n&eInfo: &f%info%\n&r\n&7&oClick to teleport to player."), info)})); + text.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, + formatAlert("/" + Config.alertCommand, info))); + + components.add(text); + + TextComponent[] toSend = components.toArray(new TextComponent[0]); + + if(Config.testMode && (dev ? !data.devAlerts : !data.alerts)) + data.getPlayer().spigot().sendMessage(toSend); + + if(Config.alertsConsole) MiscUtils.printToConsole(new TextComponent(toSend).toPlainText()); + if(!dev) + Kauri.INSTANCE.dataManager.hasAlerts + .forEach(data -> data.getPlayer().spigot().sendMessage(toSend)); + else Kauri.INSTANCE.dataManager.devAlerts + .forEach(data -> data.getPlayer().spigot().sendMessage(toSend)); + lastAlert.reset(); + } + + punish(); + + if (Config.bungeeAlerts) { + try { + Atlas.getInstance().getBungeeManager() + .sendObjects("override", data.getPlayer().getUniqueId(), name, + MathUtils.round(vl, 2), info); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + }); + } + + private TextComponent createTxt(String txt) { + return new TextComponent(formatAlert(Color.translate(txt), "")); + } + private TextComponent createTxt(String txt, String info) { + return new TextComponent(formatAlert(Color.translate(txt), info)); + } + + public boolean isPosition(WrappedInFlyingPacket packet) { + return packet.isPos() && (data.playerInfo.deltaXZ > 0 || data.playerInfo.deltaY != 0); + } + + private String formatAlert(String toFormat, String info) { + return Color.translate(toFormat.replace("%desc%", String.join("\n", + MiscUtils + .splitIntoLine(description, 20))) + .replace("%player%", data.getPlayer().getName()) + .replace("%check%", name) + .replace("%info%", info) + .replace("%vl%", String.valueOf(MathUtils.round(vl, 1))) + .replace("%experimental%", developer ? "&c(Experimental)" : "")); + } + + public void punish() { + if(banExempt || developer || !executable || punishVl == -1 || vl <= punishVl + || System.currentTimeMillis() - Kauri.INSTANCE.lastTick > 200L) return; + + + Kauri.INSTANCE.loggerManager.addPunishment(data, this); + if(!data.banned) { + if(!Config.broadcastMessage.equalsIgnoreCase("off")) { + if (!Config.bungeeBroadcast) { + RunUtils.task(() -> { + if (!Config.broadcastMessage.equalsIgnoreCase("off")) { + Bukkit.broadcastMessage(Color.translate(Config.broadcastMessage + .replace("%name%", data.getPlayer().getName()))); + } + }, Kauri.INSTANCE); + } else { + BungeeAPI.broadcastMessage(Color.translate(Config.broadcastMessage + .replace("%name%", data.getPlayer().getName()))); + } + } + if(!Config.bungeePunishments) { + RunUtils.task(() -> { + ConsoleCommandSender sender = Bukkit.getConsoleSender(); + Config.punishCommands. + forEach(cmd -> Bukkit.dispatchCommand( + sender, + cmd.replace("%name%", data.getPlayer().getName()))); + vl = 0; + }, Kauri.INSTANCE); + } else { + Config.punishCommands. + forEach(cmd -> BungeeAPI.sendCommand(cmd.replace("%name%", data.getPlayer().getName()))); + } + data.banned = true; + } + } + + public void debug(String information, Object... variables) { + if(Kauri.INSTANCE.dataManager.debugging.size() == 0) return; + if(variables.length > 0 && information.contains("%v")) { + String[] splitInfo = information.split("%v"); + + for (int i = 0; i < splitInfo.length; i++) { + String split = splitInfo[i]; + + if(variables.length > i) { + if ((variables[i] instanceof Double || variables[i] instanceof Float) + && splitInfo.length > i + 1 && splitInfo[i + 1].startsWith(".")) { + String split2 = splitInfo[i + 1]; + + if (split2.length() >= 2) { + int parsed = -1; + for (int l = split2.length(); l > 1; l--) { + try { + parsed = Integer.parseInt(split2.substring(1, l)); + break; + } catch (NumberFormatException ignored) { + } + } + + if (parsed < 0) { + splitInfo[i] = split + variables[i]; + } else if(variables[i] instanceof Float) { + splitInfo[i + 1] = split2.replace("." + parsed, ""); + float var = (float) variables[i]; + if(!Float.isNaN(var) && !Float.isInfinite(var)) + splitInfo[i] = split + MathUtils.round(var, parsed); + else splitInfo[i] = split + var; + } else if(variables[i] instanceof Double) { + splitInfo[i + 1] = split2.replace("." + parsed, ""); + double var = (double) variables[i]; + if(!Double.isNaN(var) && !Double.isInfinite(var)) + splitInfo[i] = split + MathUtils.round(var, parsed); + else splitInfo[i] = split + var; + } + } + } else splitInfo[i] = split + variables[i]; + } + } + information = String.join("", splitInfo); + } + final String finalInformation = information; + Kauri.INSTANCE.dataManager.debugging.stream() + .filter(data -> data.debugged.equals(this.data.uuid) && data.debugging.equalsIgnoreCase(name)) + .forEach(data -> data.getPlayer() + .sendMessage(Color.translate("&8[&c&lDEBUG&8] &7" + finalInformation))); + } + + public static void registerChecks() { + + } + + public static boolean isCheck(String name) { + return checkClasses.values().stream().anyMatch(val -> val.name().equalsIgnoreCase(name)); + } + + public static CheckInfo getCheckInfo(String name) { + return checkClasses.values().stream().filter(val -> val.name().equalsIgnoreCase(name)) + .findFirst().orElse(null); + } + + public static CheckSettings getCheckSettings(String name) { + return checkSettings.values().stream().filter(val -> val.name.equalsIgnoreCase(name)) + .findFirst().orElse(null); + } + + public void kickPlayer() { + val channel = getChannel.invoke(TinyProtocolHandler.getInstance(), data.getPlayer()); + + val wrapped = new WrappedClass(channel.getClass()); + + wrapped.getMethod("close").invoke(channel); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/CheckInfo.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/CheckInfo.java new file mode 100644 index 000000000..30fdab141 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/CheckInfo.java @@ -0,0 +1,23 @@ +package dev.brighten.anticheat.check.api; + +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import dev.brighten.api.check.CheckType; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface CheckInfo { + String name() default "yourmom"; + String description() default "No description provided."; + boolean enabled() default true; + boolean executable() default true; + boolean cancellable() default false; + boolean developer() default false; + int punishVL() default 100; + int vlToFlag() default -1; + CheckType checkType() default CheckType.SPEED; + ProtocolVersion minVersion() default ProtocolVersion.V1_7; + ProtocolVersion maxVersion() default ProtocolVersion.v1_15_2; + +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/CheckRegister.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/CheckRegister.java new file mode 100644 index 000000000..f37de6dfc --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/CheckRegister.java @@ -0,0 +1,6 @@ +package dev.brighten.anticheat.check.api; + +public interface CheckRegister { + + void registerChecks(); +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/CheckSettings.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/CheckSettings.java new file mode 100644 index 000000000..6f3856104 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/CheckSettings.java @@ -0,0 +1,17 @@ +package dev.brighten.anticheat.check.api; + +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import dev.brighten.api.check.CheckType; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; + +@AllArgsConstructor +@RequiredArgsConstructor +public class CheckSettings { + public boolean enabled, executable, cancellable; + public final String name, description; + public final CheckType type; + public final CancelType cancelMode; + public final int punishVl, vlToFlag; + public final ProtocolVersion minVersion, maxVersion; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/Config.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/Config.java new file mode 100644 index 000000000..f4ebc8736 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/Config.java @@ -0,0 +1,64 @@ +package dev.brighten.anticheat.check.api; + +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.ConfigSetting; +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.MiscUtils; + +import java.util.Arrays; +import java.util.List; + +@Init +public class Config { + + @ConfigSetting(path = "punishments", name = "commands") + static List punishCommands = Arrays.asList("kick %name% [Kauri] Unfair Advantage -s"); + + @ConfigSetting(name = "license") + public static String license = "insert license here"; + + @ConfigSetting(name = "initChecks") + public static boolean initChecks = true; + + @ConfigSetting(path = "punishments", name = "broadcast", + comment = "Set string to \"none\" if you want to disable broadcast.") + static String broadcastMessage = MiscUtils.line(Color.Dark_Gray) + + "\n&e%name% &7was removed by &6Kauri &7because of an &fUnfair Advantage&7." + + MiscUtils.line(Color.Dark_Gray); + + @ConfigSetting(path = "punishments", name = "bungeeCommand") + static boolean bungeePunishments = false; + + @ConfigSetting(path = "general", name = "kickForLunar18") + public static boolean kickForLunar18 = true; + + @ConfigSetting(path = "punishments", name = "bungeeBroadcast") + static boolean bungeeBroadcast = false; + + @ConfigSetting(path = "alerts", name = "bungee", comment = "Sends alerts across servers.") + public static boolean bungeeAlerts = false; + + @ConfigSetting(path = "alerts", name = "delay", comment = "The delay between alerts sending.") + public static long alertsDelay = 500; + + @ConfigSetting(path = "alerts", name = "toConsole", comment = "Send alert messages to console.") + public static boolean alertsConsole = true; + + @ConfigSetting(path = "general", name = "language") + public static String language = "english"; + + @ConfigSetting(path = "alerts", name = "cancelCheats") + public static boolean cancelCheats = true; + + @ConfigSetting(path = "alerts", name = "testMode") + public static boolean testMode = false; + + @ConfigSetting(path = "alerts", name = "clickCommand") + public static String alertCommand = "teleport %player%"; + + @ConfigSetting(path = "alerts", name = "dev", comment = "Alert for experimental checks.") + public static boolean alertDev = true; + + @ConfigSetting(name = "bypassPerm") + public static boolean bypassPermission = true; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/Event.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/Event.java new file mode 100644 index 000000000..f65131604 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/Event.java @@ -0,0 +1,15 @@ +package dev.brighten.anticheat.check.api; + +import cc.funkemunky.api.utils.Priority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface Event { + Priority priority() default Priority.NORMAL; + boolean stopChecksOnFalse() default false; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/Packet.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/Packet.java new file mode 100644 index 000000000..f9eaf3b88 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/Packet.java @@ -0,0 +1,15 @@ +package dev.brighten.anticheat.check.api; + +import cc.funkemunky.api.utils.Priority; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface Packet { + Priority priority() default Priority.NORMAL; + boolean stopChecksOnFalse() default false; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/check/api/Setting.java b/Impl/src/main/java/dev/brighten/anticheat/check/api/Setting.java new file mode 100644 index 000000000..db7702876 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/check/api/Setting.java @@ -0,0 +1,12 @@ +package dev.brighten.anticheat.check.api; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +public @interface Setting { + String name(); +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/classloader/KauriClassLoader.java b/Impl/src/main/java/dev/brighten/anticheat/classloader/KauriClassLoader.java new file mode 100644 index 000000000..0a0a5ea5b --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/classloader/KauriClassLoader.java @@ -0,0 +1,103 @@ +package dev.brighten.anticheat.classloader; + +import dev.brighten.anticheat.utils.SystemUtil; +import dev.brighten.anticheat.utils.file.JarUtil; +import lombok.Getter; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Created on 28/08/2020 Package dev.brighten.anticheat.classloader + */ +public class KauriClassLoader extends URLClassLoader { + + private Map classBytes; + + public KauriClassLoader(URL url, java.lang.ClassLoader parent) { + super(new URL[]{url}, parent); + + File jarFile = new File(url.getPath()); + + if (!jarFile.exists()) { + return; + } + + //Load the class bytes from the encrypted file + classBytes = JarUtil.loadNonClassEntries(jarFile); + + + Map encryptedClasses = JarUtil.loadJar(jarFile); + + if (encryptedClasses == null) { + return; + } + + //Decrypt the bytes using our CRC32 method + encryptedClasses.forEach((name, bytes) -> { + + String realName = name.replaceAll("/", ".").replaceAll("\\.class", ""); + + + byte[] nBytes = new byte[bytes.length]; + + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + b ^= SystemUtil.CRC_32.getValue(); + b ^= SystemUtil.CRC_32.getValue() / 2; + b ^= SystemUtil.CRC_32.getValue() / 3; + b ^= SystemUtil.CRC_32.getValue() / 4; + b ^= SystemUtil.CRC_32.getValue() / 5; + nBytes[i] = b; + } + + classBytes.put(realName, nBytes); + }); + + while (!jarFile.delete()) { + } + } + + @Override + public URL getResource(String name) { + return super.getResource(name); + } + + @Override + public Class> loadClass(String name) { + Class> aClass; + try { + aClass = super.loadClass(name); + } catch (ClassNotFoundException e) { + return loadClassFromMemory(name); + } + return aClass; + } + + @Override + protected Class> findClass(String name) { + Class> aClass; + try { + aClass = super.findClass(name); + } catch (ClassNotFoundException e) { + return loadClassFromMemory(name); + } + return aClass; + } + + private Class> loadClassFromMemory(String name) { + byte[] bytes = classBytes.get(name); + if (bytes != null) { + // remove the encrypted class from the pool so it cant be dumped from the class pool + classBytes.remove(name); + + // define the class in memory + return defineClass(name, bytes, 0, bytes.length); + } + return null; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/AlertsCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/AlertsCommand.java new file mode 100644 index 000000000..0b8a095ec --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/AlertsCommand.java @@ -0,0 +1,50 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; + +@Init(commands = true) +public class AlertsCommand { + + @Command(name = "kauri.alerts", description = "toggle on/off cheat alerts.", aliases = {"alerts"}, + display = "alerts", playerOnly = true, permission = "kauri.command.alerts") + public void onCommand(CommandAdapter cmd) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(cmd.getPlayer()); + + if(data != null) { + if(data.alerts = !data.alerts) { + Kauri.INSTANCE.dataManager.hasAlerts.add(data); + cmd.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage().msg("alerts-on", + "&aYou are now viewing cheat alerts.")); + } else { + Kauri.INSTANCE.dataManager.hasAlerts.remove(data); + cmd.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage().msg("alerts-none", + "&cYou are no longer viewing cheat alerts.")); + } + } else cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage().msg("data-error", + "&cThere was an error trying to find your data.")); + } + + @Command(name = "kauri.alerts.dev", description = "toggle on/off dev cheat alerts.", aliases = {"alerts.dev"}, + display = "alerts dev", playerOnly = true, permission = "kauri.command.alerts.dev") + public void onAlertsDevCommand(CommandAdapter cmd) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(cmd.getPlayer()); + + if(data != null) { + if(data.devAlerts = !data.devAlerts) { + Kauri.INSTANCE.dataManager.devAlerts.add(data); + cmd.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage().msg("dev-alerts-on", + "&aYou are now viewing developer cheat alerts.")); + } else { + Kauri.INSTANCE.dataManager.devAlerts.remove(data); + cmd.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage().msg("dev-alerts-none", + "&cYou are no longer viewing developer cheat alerts.")); + } + } else cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage().msg("data-error", + "&cThere was an error trying to find your data.")); + } + +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/BungeeCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/BungeeCommand.java new file mode 100644 index 000000000..fc93580bc --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/BungeeCommand.java @@ -0,0 +1,30 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.bungee.BungeeAPI; +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.ConfigSetting; +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.Priority; + +@Init(commands = true,priority = Priority.LOW) +public class BungeeCommand { + + @ConfigSetting(path = "general", name = "bungeeCmd") + private static boolean bungeeCmd = true; + + @Command(name = "bungeecmd", display = "bungee [args]", description = "send command to bungee", + permission = "kauri.command.bungee") + public void onCommand(CommandAdapter cmd) { + if(!bungeeCmd) { + cmd.getPlayer().sendMessage(Color.Red + "Bungee command is disabled!"); + return; + } + if(cmd.getArgs().length == 0) { + cmd.getPlayer().sendMessage(Color.Red + "Invalid arguments."); + return; + } + BungeeAPI.sendCommand(String.join(" ", cmd.getArgs())); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/DebugCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/DebugCommand.java new file mode 100644 index 000000000..071539051 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/DebugCommand.java @@ -0,0 +1,189 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.reflections.types.WrappedClass; +import cc.funkemunky.api.tinyprotocol.api.TinyProtocolHandler; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutHeldItemSlot; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.Materials; +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.utils.MiscUtils; +import dev.brighten.anticheat.utils.Pastebin; +import lombok.val; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Comparator; +import java.util.UUID; + +@Init(commands = true) +public class DebugCommand { + + @Command(name = "kauri.setslot", permission = "kauri.command.debug", usage = "/ ", + display = "setslot ", description = "Set your slot.", playerOnly = true) + public void onSlot(CommandAdapter cmd) { + if(cmd.getArgs().length > 0) { + if(!MiscUtils.isInteger(cmd.getArgs()[0])) { + cmd.getSender().sendMessage(Color.Red + "Argument provided is not a slot."); + return; + } + + int slot = Integer.parseInt(cmd.getArgs()[0]); + + if(slot < 0 || slot > 8) { + cmd.getSender().sendMessage(Color.Red + "You must provide a number between 0 and 8."); + return; + } + + WrappedOutHeldItemSlot packet = new WrappedOutHeldItemSlot(slot); + TinyProtocolHandler.sendPacket(cmd.getPlayer(), packet); + + cmd.getSender().sendMessage(Color.Green + "Set your item slot to slot " + slot); + } else cmd.getSender().sendMessage(Color.Red + "Invalid arguments."); + } + + @Command(name = "kauri.debug", aliases = {"debug"}, permission = "kauri.command.debug", + usage = "/ [player]", display = "debug", description = "debug a check", playerOnly = true) + public void onCommand(CommandAdapter cmd) { + if(cmd.getArgs().length > 0) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(cmd.getPlayer()); + + if(data == null) { + cmd.getPlayer().sendMessage(Color.Red + "There was an error trying to find your data object."); + return; + } + UUID target = cmd.getArgs().length > 1 + ? Bukkit.getOfflinePlayer(cmd.getArgs()[1]).getUniqueId() : cmd.getPlayer().getUniqueId(); + + if(target != null) { + if(cmd.getArgs()[0].equalsIgnoreCase("sniff")) { + Player targetPl; + if((targetPl = Bukkit.getPlayer(target)) != null) { + val targetData = Kauri.INSTANCE.dataManager.getData(targetPl); + if(!targetData.sniffing) { + cmd.getSender().sendMessage("Sniffing + " + targetPl.getName()); + targetData.sniffing = true; + } else { + cmd.getSender().sendMessage("Stopped sniff. Pasting..."); + targetData.sniffing = false; + try { + cmd.getSender().sendMessage("Paste: " + Pastebin.makePaste( + String.join("\n", targetData.sniffedPackets.stream().toArray(String[]::new)), + "Sniffed from " + targetPl.getName(), Pastebin.Privacy.UNLISTED)); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + targetData.sniffedPackets.clear(); + } + } + } else { + if(Check.isCheck(cmd.getArgs()[0].replace("_", " "))) { + data.debugging = cmd.getArgs()[0].replace("_", " "); + data.debugged = target; + + cmd.getPlayer().sendMessage(Color.Green + "You are now debugging " + data.debugging + + " on target " + Bukkit.getOfflinePlayer(target).getName() + "!"); + } else cmd.getPlayer() + .sendMessage(Color.Red + "The argument input \"" + cmd.getArgs()[0] + "\" is not a check."); + } + } else cmd.getPlayer().sendMessage(Color.Red + "Could not find a target to debug."); + } else cmd.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("error-invalid-args", "&cInvalid arguments! Check the help page.")); + } + + @Command(name = "kauri.block", description = "Check the material type information.", + display = "block [id,name]", permission = "kauri.command.block") + public void onBlock(CommandAdapter cmd) { + Material material; + if(cmd.getArgs().length > 0) { + if(MiscUtils.isInteger(cmd.getArgs()[0])) { + material = Material.getMaterial(Integer.parseInt(cmd.getArgs()[0])); + } else material = Arrays.stream(Material.values()) + .filter(mat -> mat.name().equalsIgnoreCase(cmd.getArgs()[0])).findFirst() + .orElse((XMaterial.AIR.parseMaterial())); + } else if(cmd.getSender() instanceof Player) { + if(cmd.getPlayer().getItemInHand() != null) { + material = cmd.getPlayer().getItemInHand().getType(); + } else { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("block-no-item-in-hand", + "&cPlease hold an item in your hand or use the proper arguments.")); + return; + } + } else { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("error-invalid-args", "&cInvalid arguments! Check the help page.")); + return; + } + + if(material != null) { + cmd.getSender().sendMessage(cc.funkemunky.api.utils.MiscUtils.line(Color.Dark_Gray)); + cmd.getSender().sendMessage(Color.Gold + Color.Bold + material.name() + Color.Gray + ":"); + cmd.getSender().sendMessage(""); + cmd.getSender().sendMessage(Color.translate("&eBitmask&7: &f" + Materials.getBitmask(material))); + WrappedClass wrapped = new WrappedClass(Materials.class); + + wrapped.getFields(field -> field.getType().equals(int.class) && Modifier.isStatic(field.getModifiers())) + .stream().sorted(Comparator.comparing(field -> field.getField().getName())) + .forEach(field -> { + int bitMask = field.get(null); + + cmd.getSender().sendMessage(Color.translate("&e" + field.getField().getName() + + "&7: &f" + Materials.checkFlag(material, bitMask))); + }); + cmd.getSender().sendMessage(cc.funkemunky.api.utils.MiscUtils.line(Color.Dark_Gray)); + } else cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("block-no-material", "&cNo material was found. Please check your arguments.")); + } + + @Command(name = "kauri.debug.none", aliases = {"debug.none"}, permission = "kauri.command.debug", usage = "/", + playerOnly = true, display = "debug none", description = "turn off debugging") + public void onDebugOff(CommandAdapter cmd) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(cmd.getPlayer()); + + if(data == null) { + cmd.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("error-data-object", "&cThere was an error trying to find your data object.")); + return; + } + + data.debugging = null; + data.debugged = null; + Kauri.INSTANCE.dataManager.debugging.remove(data); + + Kauri.INSTANCE.dataManager.dataMap.values().stream() + .filter(d -> d.boxDebuggers.contains(cmd.getPlayer())) + .forEach(d -> d.boxDebuggers.remove(cmd.getPlayer())); + cmd.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("debug-off", "&aTurned off your debugging.")); + } + + @Command(name = "kauri.debug.box", aliases = {"debug.box"}, permission = "kauri.command.debug", usage = "/", + playerOnly = true, display = "debug box [player...]", description = "debug the collisions of players.") + public void onDebugBox(CommandAdapter cmd) { + String[] debuggingPlayers; + ObjectData.debugBoxes(false, cmd.getPlayer()); + if(cmd.getArgs().length == 0) { + ObjectData.debugBoxes(true, cmd.getPlayer(), cmd.getPlayer().getUniqueId()); + debuggingPlayers = new String[] {cmd.getPlayer().getName()}; + } else { + ObjectData.debugBoxes(true, cmd.getPlayer(), + debuggingPlayers = cmd.getArgs()); + } + + cmd.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("debug-boxes", "&aYou are now debugging the collisions of %players%.") + .replace("%players%", String.join(", ", debuggingPlayers))); + + } + +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/DelayCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/DelayCommand.java new file mode 100644 index 000000000..0e4969e9b --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/DelayCommand.java @@ -0,0 +1,34 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Config; + +@Init(commands = true) +public class DelayCommand { + + + @Command(name = "kauri.delay", description = "change the delay between alerts.", display = "delay [ms]", + permission = "kauri.command.delay", aliases = {"delay"}, usage = "/ ") + public void onCommand(CommandAdapter cmd) { + if(cmd.getArgs().length > 0) { + try { + long delay = Long.parseLong(cmd.getArgs()[0]); + cmd.getSender().sendMessage(Color.Gray + "Setting delay to " + + Color.White + delay + "ms" + Color.Gray + "..."); + + Config.alertsDelay = delay; + Kauri.INSTANCE.getConfig().set("alerts.delay", delay); + Kauri.INSTANCE.saveConfig(); + cmd.getSender().sendMessage(Color.Green + "Delay set!"); + } catch(NumberFormatException e) { + cmd.getSender().sendMessage(Color.Red + "The argument \"" + cmd.getArgs()[0] + + "\" provided is not a long."); + } + } else cmd.getSender() + .sendMessage(Color.Red + "Invalid arguments. Check the help page for more information."); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/ExemptCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/ExemptCommand.java new file mode 100644 index 000000000..004b73e87 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/ExemptCommand.java @@ -0,0 +1,14 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; + +@Init +public class ExemptCommand extends Check { + + @CheckInfo + public void onExempt() { + + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/ForcebanCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/ForcebanCommand.java new file mode 100644 index 000000000..2a3ee5e30 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/ForcebanCommand.java @@ -0,0 +1,34 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +@Init(commands = true) +public class ForcebanCommand { + + @Command(name = "kauri.forceban", display = "ban [player]", description = "Force bans a player.", + permission = "kauri.command.forceban") + public void onForceBan(CommandAdapter cmd) { + if (cmd.getArgs().length > 0) { + Player target; + + if((target = Bukkit.getPlayer(cmd.getArgs()[0])) != null) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(target); + + data.checkManager.checks.values().stream().filter(check -> !check.isDeveloper() && check.isExecutable()) + .findFirst().ifPresent(check -> { + check.vl = check.punishVl + 69; + check.punish(); + }); + cmd.getSender().sendMessage(Color.Green + "Force banned the player."); + } else cmd.getSender().sendMessage(Color.Red + "Player not found."); + } else cmd.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("error-invalid-args", "&cInvalid arguments! Check the help page.")); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/KauriCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/KauriCommand.java new file mode 100644 index 000000000..291174eb4 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/KauriCommand.java @@ -0,0 +1,75 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.Atlas; +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.reflections.types.WrappedClass; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Init(commands = true) +public class KauriCommand { + + private static List testers = new ArrayList<>(); + + @Command(name = "kauri", description = "The Kauri main command.", display = "Kauri", aliases = {"anticheat"}, + permission = "kauri.command", + noPermissionMessage = "&cThis server is running Kauri by funkemunky, Elevated, and Abigail.") + public void onCommand(CommandAdapter cmd) { + Atlas.getInstance().getCommandManager(Kauri.INSTANCE).runHelpMessage(cmd, + cmd.getSender(), + Atlas.getInstance().getCommandManager(Kauri.INSTANCE).getDefaultScheme()); + } + + @Command(name = "kauri.test", description = "Add yourself to test messaging.", + permission = "kauri.command.test", display = "test", playerOnly = true) + public void onTest(CommandAdapter cmd) { + if(testers.contains(cmd.getPlayer())) { + if(testers.remove(cmd.getPlayer())) { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("tester-remove-success", "&cRemoved you from test messaging for developers.")); + } else cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("tester-remove-error", "&cThere was an error removing you from test messaging.")); + } else { + testers.add(cmd.getPlayer()); + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("testers-added", "&aYou have been added to the test messaging list for developers.")); + } + } + + @Command(name = "kauri.updatecmds") + public void onUpdate(CommandAdapter cmd) { + new WrappedClass(cmd.getPlayer().getClass()).getMethod("updateCommands").invoke(cmd.getPlayer()); + cmd.getSender().sendMessage("updated"); + } + + /* @Command(name = "kchecksum", permission = "kauri.command.admin.checksum") + public void onChecksum(CommandAdapter cmd) { + Method c = J; + String className = c.getName(); + String classAsPath = ; + InputStream stream = c.getClassLoader().getResourceAsStream(classAsPath); + + try { + byte[] array = MiscUtils.toByteArray(stream); + + String hash = GeneralHash.getSHAHash(array, GeneralHash.SHAType.SHA1); + + cmd.getSender().sendMessage("Checksum: " + hash); + System.out.println("checksum: " + hash); + } catch (IOException e) { + e.printStackTrace(); + } + }*/ + + public static List getTesters() { + testers.stream().filter(Objects::isNull).forEach(testers::remove); + + return testers; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/LagCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/LagCommand.java new file mode 100644 index 000000000..9c74144cb --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/LagCommand.java @@ -0,0 +1,97 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.profiling.ResultsType; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.MathUtils; +import cc.funkemunky.api.utils.MiscUtils; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.concurrent.atomic.AtomicLong; + +@Init(commands = true) +public class LagCommand { + + @Command(name = "kauri.lag", description = "view important lag information", display = "lag", + aliases = {"lag", "klag"}, permission = "kauri.command.lag") + public void onCommand(CommandAdapter cmd) { + cmd.getSender().sendMessage(MiscUtils.line(Color.Dark_Gray)); + cmd.getSender().sendMessage(Color.Gold + Color.Bold + "Server Lag Information"); + cmd.getSender().sendMessage(""); + cmd.getSender().sendMessage(Color.translate("&eTPS&8: &f" + + MathUtils.round(Kauri.INSTANCE.getTps(), 2))); + AtomicLong chunkCount = new AtomicLong(0); + Bukkit.getWorlds().forEach(world -> chunkCount.addAndGet(world.getLoadedChunks().length)); + cmd.getSender().sendMessage(Color.translate("&eChunks&8: &f" + chunkCount.get())); + double totalMem = MathUtils.round(Runtime.getRuntime().totalMemory() / 1E9, 2); + double freeMem = MathUtils.round(Runtime.getRuntime().freeMemory() / 1E9, 2); + double allocated = MathUtils.round(Runtime.getRuntime().maxMemory() / 1E9, 2); + cmd.getSender().sendMessage(Color.translate("&eMemory (GB)&8: &f" + + freeMem + "&7/&f" + totalMem + "&7/&f" + allocated)); + cmd.getSender().sendMessage(Color.translate("&eKauri CPU Usage&8: &f" + + MathUtils.round(Kauri.INSTANCE.profiler.results(ResultsType.TICK).values() + .stream() + .mapToDouble(val -> val.two / 1000000D) + .filter(val -> !Double.isNaN(val) && !Double.isInfinite(val)) + .sum() / 50D * 100, 1)) + "%"); + cmd.getSender().sendMessage(MiscUtils.line(Color.Dark_Gray)); + } + + @Command(name = "kauri.lag.gc", description = "run a garbage collector.", display = "lag gc", + aliases = {"lag.gc", "klag.gc"}, permission = "kauri.command.lag.gc") + public void onLagGc(CommandAdapter cmd) { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("start-gc", "&7Starting garbage collector...")); + + long stamp = System.nanoTime(); + double time; + Runtime.getRuntime().gc(); + time = (System.nanoTime() - stamp) / 1E6D; + + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage().msg("gc-complete", + "&aCompleted garbage collection in %ms%ms!") + .replace("%ms%", String.valueOf(MathUtils.round(time, 2)))); + } + + @Command(name = "kauri.lag.player", description = "view player lag", display = "lag player [player]", + aliases = {"lag.player", "klag.player"}, permission = "kauri.command.lag.player") + public void onLagPlayer(CommandAdapter cmd) { + Player target; + if(cmd.getArgs().length == 0) { + if(cmd.getSender() instanceof Player) { + target = cmd.getPlayer(); + } else { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("provide-player", "&cYou must provide a player.")); + return; + } + } else { + target = Bukkit.getPlayer(cmd.getArgs()[0]); + + if(target == null) { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("player-not-online", "&cThe player provided is not online!")); + return; + } + } + + ObjectData data = Kauri.INSTANCE.dataManager.getData(target); + + if(data != null) { + cmd.getSender().sendMessage(MiscUtils.line(Color.Dark_Gray)); + cmd.getSender().sendMessage(Color.Gold + Color.Bold + target.getName() + "'s Lag Information"); + cmd.getSender().sendMessage(""); + cmd.getSender().sendMessage(Color.translate("&ePing&7: &f" + + data.lagInfo.ping + "&7/&f" + data.lagInfo.transPing)); + cmd.getSender().sendMessage(Color.translate("&eLast Skip&7: &f" + data.lagInfo.lastPacketDrop.getPassed())); + cmd.getSender().sendMessage(Color.translate("&eLagging&7:&f" + data.lagInfo.lagging)); + cmd.getSender().sendMessage(MiscUtils.line(Color.Dark_Gray)); + } else cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage().msg("data-error", + "&cThere was an error trying to find your data.")); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/LogCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/LogCommand.java new file mode 100644 index 000000000..d55dacd2d --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/LogCommand.java @@ -0,0 +1,145 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.MathUtils; +import cc.funkemunky.api.utils.RunUtils; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.logs.objects.Log; +import dev.brighten.anticheat.logs.objects.Punishment; +import dev.brighten.anticheat.menu.LogsGUI; +import dev.brighten.anticheat.utils.Pastebin; +import dev.brighten.anticheat.utils.menu.preset.ConfirmationMenu; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; + +import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.stream.Collectors; + +@Init(commands = true) +public class LogCommand { + + @Command(name = "kauri.logs", description = "View the logs of a user.", display = "logs [player]", + usage = "/ [player]", aliases = {"logs"}, permission = "kauri.command.logs") + public void onCommand(CommandAdapter cmd) { + Kauri.INSTANCE.executor.execute(() -> { + if(cmd.getArgs().length == 0) { + if(cmd.getPlayer() != null) { + LogsGUI gui = new LogsGUI(cmd.getPlayer()); + RunUtils.task(() -> { + gui.showMenu(cmd.getPlayer()); + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("opened-menu", "&aOpened menu.")); + cmd.getSender().sendMessage(Color.Green + "Opened menu."); + }, Kauri.INSTANCE); + } else cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("no-console-logs", + "&cYou cannot view your own logs since you are not a player.")); + } else { + OfflinePlayer player = Bukkit.getOfflinePlayer(cmd.getArgs()[0]); + + if(player == null) { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("offline-player-not-found", "&cSomehow, out of hundreds of millions of" + + "Minecraft accounts, you found one that doesn't exist.")); + return; + } + + if(cmd.getPlayer() != null) { + LogsGUI gui = new LogsGUI(player); + RunUtils.task(() -> { + gui.showMenu(cmd.getPlayer()); + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("opened-menu", "&aOpened menu.")); + }, Kauri.INSTANCE); + } else cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("logs-pastebin", + "&aLogs: %pastebin%".replace("%pastebin%", + getLogsFromUUID(player.getUniqueId())))); + } + }); + } + + @Command(name = "kauri.logs.clear", display = "logs clear [player]", description = "Clear logs of a player", + usage = "/ [playerName]", permission = "kauri.command.logs.clear") + public void onLogsClear(CommandAdapter cmd) { + if(cmd.getArgs().length > 0) { + OfflinePlayer player = Bukkit.getOfflinePlayer(cmd.getArgs()[0]); + + if(player == null) { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("offline-player-not-found", "&cSomehow, out of hundreds of millions of" + + "Minecraft accounts, you found one that doesn't exist.")); + return; + } + + if(cmd.getPlayer() != null) { + ConfirmationMenu menu = new ConfirmationMenu( + "Clear " + player.getName() + "'s logs?", + (pl, confirmed) -> { + if(confirmed) { + Kauri.INSTANCE.executor.execute(() -> { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("clearing-logs", "&7Clearing logs from %player%...") + .replace("%player%", player.getName())); + Kauri.INSTANCE.loggerManager.clearLogs(player.getUniqueId()); + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("clear-logs-success", "&aLogs cleared!")); + }); + } + }); + menu.showMenu(cmd.getPlayer()); + } else { + Kauri.INSTANCE.executor.execute(() -> { + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("clearing-logs", "&7Clearing logs from %player%...") + .replace("%player%", player.getName())); + Kauri.INSTANCE.loggerManager.clearLogs(player.getUniqueId()); + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("clear-logs-success", "&aLogs cleared!")); + }); + } + } else cmd.getSender().sendMessage(Color.Red + "You must provide the name of a player."); + } + + public static String getLogsFromUUID(UUID uuid) { + List logs = Kauri.INSTANCE.loggerManager.getLogs(uuid); + List punishments = Kauri.INSTANCE.loggerManager.getPunishments(uuid); + + if(logs.size() == 0) return "No Logs"; + + String body; + + SimpleDateFormat format = new SimpleDateFormat("MM/dd/YYYY hh:mm"); + format.setTimeZone(TimeZone.getTimeZone("US/Eastern")); + + OfflinePlayer pl = Bukkit.getOfflinePlayer(uuid); + + SortedMap eventsByStamp = new TreeMap<>(Comparator.comparing(key -> key, Comparator.naturalOrder())); + + logs.forEach(log -> { + String built = "(" + format.format(new Date(log.timeStamp)) + "): " + pl.getName() + " failed " + + log.checkName + " at VL: [" + MathUtils.round(log.vl, 2) + + "] (tps=" + MathUtils.round(log.tps, 4) + " ping=" + log.ping + " info=[" + log.info + "])"; + eventsByStamp.put(log.timeStamp, built); + }); + punishments.forEach(punishment -> { + String built = "Punishment applied @ (" + format.format(new Date(punishment.timeStamp)) + ") from check " + + punishment.checkName; + eventsByStamp.put(punishment.timeStamp, built); + }); + + body = eventsByStamp.keySet().stream().map(key -> eventsByStamp.get(key) + "\n").collect(Collectors.joining()); + + try { + return Pastebin.makePaste(body, pl.getName() + "'s Log", Pastebin.Privacy.UNLISTED); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return "Error"; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/MenuCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/MenuCommand.java new file mode 100644 index 000000000..7849f5584 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/MenuCommand.java @@ -0,0 +1,388 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.*; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckSettings; +import dev.brighten.anticheat.logs.objects.Log; +import dev.brighten.anticheat.menu.LogsGUI; +import dev.brighten.anticheat.utils.Pastebin; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.button.ClickAction; +import dev.brighten.anticheat.utils.menu.preset.button.FillerButton; +import dev.brighten.anticheat.utils.menu.type.impl.ChestMenu; +import dev.brighten.anticheat.utils.mojang.MojangAPI; +import dev.brighten.api.check.CheckType; +import lombok.val; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.event.inventory.ClickType; + +import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Init(commands = true) +public class MenuCommand { + + private ChestMenu main, categoryMenu; + public MenuCommand() { + main = getMainMenu(); + categoryMenu = getChecksCategoryMenu(); + } + + private static Button createButton(Material material, int amount, String name, ClickAction action, String... lore) { + return new Button(false, MiscUtils.createItem(material, amount, name, lore), action); + } + + @Command(name = "kauri.menu", description = "Open the Kauri menu.", display = "menu", usage = "/", + aliases = {"kauri.gui"}, playerOnly = true, permission = "kauri.command.menu") + public void onCommand(CommandAdapter cmd) { + main.showMenu(cmd.getPlayer()); + categoryMenu = getChecksCategoryMenu(); + cmd.getPlayer().sendMessage(Color.Green + "Opened main menu."); + } + + private ChestMenu getMainMenu() { + ChestMenu menu = new ChestMenu(Color.Gold + "Kauri Menu", 3); + + menu.setItem(11, createButton(XMaterial.ANVIL.parseMaterial(), 1, "&cEdit Checks", + (player, info) -> categoryMenu.showMenu(player), + "", "&7Toggle Kauri checks on or off.")); + menu.setItem(13, createButton(XMaterial.ENCHANTED_BOOK.parseMaterial(), 1, "&cKauri Anticheat", + (player, info) -> { + if (info.getClickType().equals(ClickType.RIGHT) + || info.getClickType().equals(ClickType.SHIFT_RIGHT)) { + menu.setParent(null); + menu.close(player); + player.sendMessage(MiscUtils.line(Color.Dark_Gray)); + player.sendMessage(Color.translate("&6Discord: &fhttps://discord.me/Brighten")); + player.sendMessage(Color.translate("&6Website: &fhttps://funkemunky.cc/contact")); + player.sendMessage(MiscUtils.line(Color.Dark_Gray)); + } + }, + "", "&7You are using &6Kauri Anticheat v" + + Kauri.INSTANCE.getDescription().getVersion(), "&e&oRight Click &7&oclick to get support.")); + menu.setItem(15, createButton(XMaterial.PAPER.parseMaterial(), 1, "&cView Recent Violators", + (player, info) -> { + Kauri.INSTANCE.executor.execute(() -> { + player.sendMessage(Color.Gray + "Loading menu..."); + getRecentViolatorsMenu().showMenu(player); + }); + }, "", "&7View players who flagged checks recently.")); + return menu; + } + + private ChestMenu getChecksCategoryMenu() { + ChestMenu menu = new ChestMenu(Color.Gold + "Check Categories", 3); + + menu.setParent(main); + + AtomicInteger amt = new AtomicInteger(0); + Arrays.stream(CheckType.values()) + .sorted(Comparator.comparing(Enum::name)) + .forEach(type -> { + + AtomicInteger amount = new AtomicInteger(0), + enabled = new AtomicInteger(0), + executable = new AtomicInteger(0), + cancellable = new AtomicInteger(0), + totalCancellable = new AtomicInteger(0); + + Check.checkSettings.values() + .stream() + .filter(ci-> ci.type.equals(type)) + .forEach(test -> { + amount.incrementAndGet(); + if(test.enabled) enabled.incrementAndGet(); + if(test.executable) executable.incrementAndGet(); + if(test.cancellable) cancellable.incrementAndGet(); + if(test.cancelMode != null) totalCancellable.incrementAndGet(); + }); + + Button button = new Button(false, + new ItemBuilder(XMaterial.BOOK.parseMaterial()) + .amount(Math.max(1, amount.get())) + .name("&e" + type.name()) + .lore("", "&aEnabled&8: &f" + enabled + "&7/&f" + amount, + "&aExecutable&8: &f" + executable + "&7/&f" + amount, + "&aCancellable&8: &f" + cancellable + "&7/&f" + totalCancellable, + "", "&7&oClick to configure in this category.") + .build(), + (player, info) -> { + menu.setParent(null); + menu.close(player); + getChecksMenu(type).showMenu(player); + menu.setParent(main); + }); + amt.incrementAndGet(); + menu.addItem(button); + }); + + menu.fill(new FillerButton()); + + return menu; + } + + private ChestMenu getChecksMenu(CheckType type) { + ChestMenu menu = new ChestMenu(Color.Gold + "Checks", 6); + + menu.setParent(categoryMenu); + + List values = Check.checkSettings.values() + .stream() + .filter(settings -> settings.type.equals(type)) + .sorted(Comparator.comparing(val -> val.name)) + .collect(Collectors.toList()); + + for (int i = 0; i < values.size(); i++) { + CheckSettings val = values.get(i); + + String enabled = "checks." + val.name + ".enabled"; + String executable = "checks." + val.name + ".executable"; + String cancellable = "checks." + val.name + ".cancellable"; + + List lore = new ArrayList<>(Arrays.asList("&7", + "&eEnabled&7: &f" + val.enabled, + "&eExecutable&7: &f" + val.executable, + "&eCancellable&7: &f" + val.cancellable, + "&eDescription&7: &f")); + + List description = Arrays.asList(MiscUtils + .splitIntoLine(val.description, 35)); + + lore.addAll(description); + + lore.add(""); + lore.add("&f&oLeft Click &7to toggle check &fon/off&7."); + lore.add("&f&oMiddle Click &7to toggle check &fcancellable&7."); + lore.add("&f&oRight Click &7to toggle check &fexecutable&7."); + + + Button button = createButton( + val.enabled ? (val.executable ? XMaterial.FILLED_MAP.parseMaterial() + : XMaterial.MAP.parseMaterial()) + : XMaterial.PAPER.parseMaterial(), + 1, + (val.enabled ? "&a" : "&c") + val.name, + (player, info) -> { + switch (info.getClickType()) { + case LEFT: + case SHIFT_LEFT: { + CheckSettings settings = Check.getCheckSettings(val.name); + settings.enabled = !settings.enabled; + Kauri.INSTANCE.getConfig().set(enabled, settings.enabled); + Kauri.INSTANCE.saveConfig(); + + ItemBuilder builder = new ItemBuilder(info.getButton().getStack()); + if (!settings.enabled) { + builder.type(XMaterial.PAPER.parseMaterial()); + } else { + builder.type(settings.executable ? XMaterial.FILLED_MAP.parseMaterial() + : XMaterial.MAP.parseMaterial()); + if(settings.cancellable) { + builder.enchantment(Enchantment.DURABILITY, 1); + } + } + + List lore2 = new ArrayList<>(Arrays.asList("&7", + "&eEnabled&7: &f" + val.enabled, + "&eExecutable&7: &f" + val.executable, + "&eCancellable&7: &f" + val.cancellable, + "&eDescription&7: &f")); + lore2.addAll(description); + lore2.add(""); + lore2.add("&f&oLeft Click &7to toggle check &fon/off&7."); + lore2.add("&f&oMiddle Click &7to toggle check &fcancellable&7."); + lore2.add("&f&oRight Click &7to toggle check &fexecutable&7."); + + builder.lore(lore2.stream().map(Color::translate).toArray(String[]::new)); + builder.name((settings.enabled ? "&a" : "&c") + val.name); + info.getButton().setStack(builder.build()); + menu.buildInventory(false); + Kauri.INSTANCE.executor.execute(() -> Kauri.INSTANCE.dataManager.dataMap.values() + .forEach(data -> { + data.checkManager.checks.clear(); + data.checkManager.checkMethods.clear(); + data.checkManager.addChecks(); + data.creation = System.currentTimeMillis(); + })); + break; + } + case RIGHT: + case SHIFT_RIGHT: { + CheckSettings settings = Check.getCheckSettings(val.name); + settings.executable = !settings.executable; + Kauri.INSTANCE.getConfig().set(executable, settings.executable); + Kauri.INSTANCE.saveConfig(); + + ItemBuilder builder = new ItemBuilder(info.getButton().getStack()); + if (settings.enabled) { + builder.type(settings.executable ? XMaterial.FILLED_MAP.parseMaterial() + : XMaterial.MAP.parseMaterial()); + } + + List lore2 = new ArrayList<>(Arrays.asList("&7", + "&eEnabled&7: &f" + val.enabled, + "&eExecutable&7: &f" + val.executable, + "&eCancellable&7: &f" + val.cancellable, + "&eDescription&7: &f")); + lore2.addAll(description); + lore2.add(""); + lore2.add("&f&oLeft Click &7to toggle check &fon/off&7."); + lore2.add("&f&oMiddle Click &7to toggle check &fcancellable&7."); + lore2.add("&f&oRight Click &7to toggle check &fexecutable&7."); + + builder.lore(lore2.stream().map(Color::translate).toArray(String[]::new)); + info.getButton().setStack(builder.build()); + menu.buildInventory(false); + Kauri.INSTANCE.executor.execute(() -> Kauri.INSTANCE.dataManager.dataMap.values() + .forEach(data -> { + data.checkManager.checks.clear(); + data.checkManager.checkMethods.clear(); + data.checkManager.addChecks(); + data.creation = System.currentTimeMillis(); + })); + break; + } + case MIDDLE: { + CheckSettings settings = Check.getCheckSettings(val.name); + settings.cancellable = !settings.cancellable; + Kauri.INSTANCE.getConfig().set(cancellable, settings.cancellable); + Kauri.INSTANCE.saveConfig(); + + ItemBuilder builder = new ItemBuilder(info.getButton().getStack()); + if (settings.enabled) { + builder.clearEnchantments(); + if(settings.cancellable) { + builder.enchantment(Enchantment.DURABILITY, 1); + } + } + + List lore2 = new ArrayList<>(Arrays.asList("&7", + "&eEnabled&7: &f" + val.enabled, + "&eExecutable&7: &f" + val.executable, + "&eCancellable&7: &f" + val.cancellable, + "&eDescription&7: &f")); + lore2.addAll(description); + lore2.add(""); + lore2.add("&f&oLeft Click &7to toggle check &fon/off&7."); + lore2.add("&f&oMiddle Click &7to toggle check &fcancellable&7."); + lore2.add("&f&oRight Click &7to toggle check &fexecutable&7."); + + builder.lore(lore2.stream().map(Color::translate).toArray(String[]::new)); + info.getButton().setStack(builder.build()); + menu.buildInventory(false); + Kauri.INSTANCE.executor.execute(() -> Kauri.INSTANCE.dataManager.dataMap.values() + .forEach(data -> { + data.checkManager.checks.clear(); + data.checkManager.checkMethods.clear(); + data.checkManager.addChecks(); + data.creation = System.currentTimeMillis(); + })); + break; + } + } + ((ChestMenu)info.getMenu()).setParent(categoryMenu = getChecksCategoryMenu()); + }, lore.toArray(new String[]{})); + + if (val.enabled) { + ItemBuilder builder = new ItemBuilder(button.getStack()); + builder.clearEnchantments(); + if(val.cancellable) { + builder.enchantment(Enchantment.DURABILITY, 1); + } + button.setStack(builder.build()); + } + menu.addItem(button); + } + return menu; + } + + private ChestMenu getRecentViolatorsMenu() { + ChestMenu menu = new ChestMenu(Color.Gold + "Recent Violators", 6); + menu.setParent(main); + try { + Map> logs = Kauri.INSTANCE.loggerManager + .getLogsWithinTimeFrame(TimeUnit.HOURS.toMillis(2)); + + List sortedIds = logs.keySet().stream() + .sorted(Comparator.comparing(key -> { + val logsList = logs.get(key); + return logsList.get(logsList.size() - 1).timeStamp; + })) + .collect(Collectors.toList()); + + for (int i = 0; i < Math.min(45, sortedIds.size()); i++) { + UUID uuid = sortedIds.get(i); + String name = MojangAPI.getUsername(uuid); + if(name == null) name = "null"; + Log vl = logs.get(uuid).get(0); + + ItemBuilder builder = new ItemBuilder(XMaterial.SKULL_ITEM.parseMaterial()); + + builder.amount(1); + builder.durability(3); + builder.owner(name); + builder.name(Color.Green + name); + builder.lore("", "&eCheck&7: &f" + vl.checkName, "&eVL&7: &f" + vl.vl, "&ePing&7: &f" + vl.ping, + "&eTPS&7: &f" + MathUtils.round(vl.tps, 2), "", + "&f&oShift-Left Click &7&oto view logs."); + menu.addItem(new Button(false, builder.build(), + (target, info) -> { + if(info.getClickType().equals(ClickType.SHIFT_LEFT) + && target.hasPermission("kauri.command.logs")) { + LogsGUI gui = new LogsGUI(Bukkit.getOfflinePlayer(uuid)); + menu.setParent(null); + menu.close(target); + gui.setParent(info.getMenu()); + menu.setParent(main); + gui.showMenu(target); + } + })); + } + return menu; + } catch(Exception e) { + e.printStackTrace(); + } + + return menu; + } + + private static String getLogsFromUUID(UUID uuid) { + Kauri.INSTANCE.profiler.start("cmd:logs"); + List logs = Kauri.INSTANCE.loggerManager.getLogs(uuid); + Kauri.INSTANCE.profiler.stop("cmd:logs"); + + if(logs.size() == 0) return "No Logs"; + + StringBuilder body = new StringBuilder(); + + SimpleDateFormat format = new SimpleDateFormat("MM/dd/YYYY hh:mm"); + format.setTimeZone(TimeZone.getTimeZone("US/Eastern")); + + String name = MojangAPI.getUsername(uuid); + + if(name == null) name = "null"; + for (Log log : logs) { + body.append("(").append(format.format(new Date(log.timeStamp))).append("): ").append(name) + .append(" failed ").append(log.checkName).append(" at VL ").append(log.vl) + .append(" (tps=").append(MathUtils.round(log.tps, 4)).append(" ping=").append(log.ping) + .append(")").append("\n"); + } + + try { + return Pastebin.makePaste(body.toString(), name + "'s Log", Pastebin.Privacy.UNLISTED); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return "Error"; + } + +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/PlayerInfoCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/PlayerInfoCommand.java new file mode 100644 index 000000000..8d079dee0 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/PlayerInfoCommand.java @@ -0,0 +1,40 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.menu.PlayerInformationGUI; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +@Init(commands = true) +public class PlayerInfoCommand { + + @Command(name = "kauri.info", description = "get the information of a player", display = "info [player]", + aliases = {"playerinfo", "pi", "kauri.pi", "kauri.playerinfo"}, playerOnly = true, + permission = "kauri.command.info") + public void onCommand(CommandAdapter cmd) { + Kauri.INSTANCE.executor.execute(() -> { + if(cmd.getArgs().length > 0) { + Player player = Bukkit.getPlayer(cmd.getArgs()[0]); + + if(player != null) { + ObjectData targetData = Kauri.INSTANCE.dataManager.getData(player); + + if(targetData != null) { + PlayerInformationGUI info = new PlayerInformationGUI(targetData); + + info.showMenu(cmd.getPlayer()); + cmd.getPlayer().sendMessage(Color.Green + "Opened menu."); + } else cmd.getSender() + .sendMessage(Kauri.INSTANCE.msgHandler.getLanguage().msg("data-error", + "&cThere was an error trying to find your data.")); + } else cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("player-not-online", "&cThe player provided is not online!")); + } else cmd.getSender().sendMessage(Color.Red + "Invalid arguments."); + }); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/ProfilerCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/ProfilerCommand.java new file mode 100644 index 000000000..68646f9bb --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/ProfilerCommand.java @@ -0,0 +1,287 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.Atlas; +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.profiling.ResultsType; +import cc.funkemunky.api.profiling.Timing; +import cc.funkemunky.api.utils.*; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.utils.Helper; +import dev.brighten.anticheat.utils.Pastebin; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.preset.button.FillerButton; +import dev.brighten.anticheat.utils.menu.type.impl.ChestMenu; +import lombok.val; +import org.apache.commons.lang.time.DateFormatUtils; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.scheduler.BukkitTask; + +import java.io.UnsupportedEncodingException; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Init(commands = true) +public class ProfilerCommand { + + private Map taskMap = new HashMap<>(); + + @Command(name = "kauri.profile", display = "profile", description = "run a profile on Kauri.", + permission = {"kauri.profile"}) + public void onCommand(CommandAdapter cmd) { + if (cmd.getSender() instanceof Player) { + int page = 1; + if (cmd.getArgs().length > 1) { + try { + page = Integer.parseInt(cmd.getArgs()[1]); + } catch (NumberFormatException e) { + cmd.getSender().sendMessage(Color.Red + "The page number provided must be an integer."); + return; + } + } + + final int finalPage = page; + + ChestMenu menu = new ChestMenu("Profile Page: " + page, 6); + menu.buildInventory(true); + + BukkitTask task = RunUtils.taskTimerAsync(() -> { + menu.fill(new FillerButton()); + final List> buttons = new ArrayList<>(); + + + + val map = Kauri.INSTANCE.profiler.results(ResultsType.TICK); + + long total = map.keySet() + .stream() + .filter(key -> !key.contains("check:")) + .mapToLong(key -> Math.round(map.get(key).two)) + .sum(); + + AtomicReference samples = new AtomicReference<>((double) 0); + + map.forEach((key, result) -> { + if(!key.contains("check:")) { + Timing timing = Kauri.INSTANCE.profiler.getTimingsMap().get(key); + Button button = new Button(false, new ItemBuilder(XMaterial.REDSTONE.parseMaterial()) + .amount(1) + .name(Color.Gold + key).lore("", + "&7Weighted Usage: " + Helper + .drawUsage(total, result.two), + "&7MS: &f" + Helper + .format(timing.total / 1000000D, 3), + "&7Samples: &f" + Helper + .format(result.two / 1000000D, 3), + "&7Deviation: &f" + Helper + .format(timing.stdDev / 1000000D, 3)).build()); + + buttons.add(new Tuple<>(result.two, button)); + samples.updateAndGet(v -> (v + result.two / 1000000D)); + } + }); + + buttons.sort(Comparator.comparing(tuple -> tuple.one, Comparator.reverseOrder())); + + double totalMs = total / 1000000D; + if (finalPage > 1) { + menu.setItem(48, new Button(false, + new ItemBuilder(XMaterial.BOOK.parseMaterial()).amount(1).name("&cBack").build(), + (player, info) -> Bukkit.dispatchCommand(player, + "kauri profile testGUI " + (finalPage - 1)))); + } + menu.setItem(49, new Button(false, + new ItemBuilder(XMaterial.REDSTONE.parseMaterial()) + .amount(1) + .name(Color.Gold + "Total").lore("", + "&7Usage: " + Helper.drawUsage(50, + Helper.format(samples.get(), 3)), + "&7Total: &f" + Helper.format(totalMs, 3), + "&7Samples: &f" + Helper.format(samples.get(), 3), + "", + "&7&oRight click to reset data.") + .build(), + (player, info) -> { + if (info.getClickType().equals(ClickType.RIGHT)) { + Kauri.INSTANCE.profiler.reset(); + } + })); + menu.setItem(50, new Button(false, + new ItemBuilder(XMaterial.BOOK.parseMaterial()).amount(1).name("&cNext").build(), + (player, info) -> Bukkit.dispatchCommand(player, + "kauri profile testGUI " + (finalPage + 1)))); + for (int i = (finalPage - 1) * 45; i < Math.min(finalPage * 45, buttons.size()); i++) { + menu.setItem(i, buttons.get(i).two); + } + + menu.buildInventory(false); + }, Kauri.INSTANCE, 0L, 4L); + + menu.setCloseHandler((player, menuConsumer) -> task.cancel()); + + menu.showMenu(cmd.getPlayer()); + + } else { + cmd.getSender().sendMessage("-------------------------------------------------"); + Map sorted, toSort = new HashMap<>(); + + Kauri.INSTANCE.profiler.getTimingsMap().forEach((name, timings) -> { + toSort.put(name, timings.total); + }); + + sorted = dev.brighten.anticheat.utils.MiscUtils.sortByValue(toSort); + int size = sorted.size(); + AtomicLong total = new AtomicLong(); + List> entries = new ArrayList<>(sorted.entrySet()); + IntStream.range(size - Math.min(size - 10, 10), size).mapToObj(entries::get) + .filter(entry -> !entry.getKey().contains("check:")) + .forEach(entry -> { + String name = entry.getKey(); + Long time = entry.getValue(); + Timing timing = Kauri.INSTANCE.profiler.getTimingsMap().get(name); + total.addAndGet(time); + cmd.getSender().sendMessage(Helper + .drawUsage(total.get(), time) + + " §c" + name + + "§7: " + Helper.format(time / 1000000D, 3) + + ", " + Helper + .format(timing.call / 1000000D, 3) + + ", " + Helper + .format(timing.stdDev / 1000000D, 3)); + }); + double totalMs = total.get() / 1000000D; + long totalTime = Kauri.INSTANCE.profiler.totalCalls * 50; + cmd.getSender().sendMessage(Helper + .drawUsage(total.get(), Helper + .format(totalMs / totalTime, 3)) + + " §cTotal§7: " + Helper.format(totalMs, 3) + + " §f- §c" + Helper + .format(totalMs / totalTime, 3) + "%"); + cmd.getSender().sendMessage("-------------------------------------------------"); + } + } + + @Command(name = "kauri.profile.chat", display = "profile chat", description = "send updated profiling in chat.", + permission = "kauri.command.profile") + public void onChatCmd(CommandAdapter cmd) { + if (taskMap.containsKey(cmd.getSender().getName())) { + taskMap.get(cmd.getSender().getName()).cancel(); + taskMap.remove(cmd.getSender().getName()); + cmd.getSender().sendMessage(Color.Red + "Removed from profiler task."); + } else { + ResultsType type; + if(cmd.getArgs().length > 0) { + type = Arrays.stream(ResultsType.values()) + .filter(t -> t.name().equalsIgnoreCase(cmd.getArgs()[0])).findFirst().orElse(ResultsType.TICK); + } else type = ResultsType.TICK; + cmd.getSender().sendMessage(Color.Green + "Added to profiler task using ResultsType: " + type.name()); + val task = RunUtils.taskTimerAsync(() -> { + if (taskMap.containsKey(cmd.getSender().getName())) { + cmd.getSender().sendMessage("-------------------------------------------------"); + Map sorted, toSort = new HashMap<>(); + + Kauri.INSTANCE.profiler.getTimingsMap().forEach((name, timings) -> { + toSort.put(name, timings.total); + }); + + sorted = dev.brighten.anticheat.utils.MiscUtils.sortByValue(toSort); + val map = Kauri.INSTANCE.profiler.results(ResultsType.TICK); + + long total = map.keySet() + .stream() + .mapToLong(key -> Math.round(map.get(key).two)) + .sum(); + List> entries = new ArrayList<>(sorted.entrySet()); + AtomicReference samples = new AtomicReference<>((double) 0); + map.keySet().stream().filter(key -> !key.contains("check:")).forEach(key -> { + val entry = map.get(key); + Timing timing = Kauri.INSTANCE.profiler.getTimingsMap().get(key); + double time = entry.two / 1000000D; + samples.updateAndGet(v -> v + time); + cmd.getSender().sendMessage(Helper.drawUsage(50, + time) + + " §c" + key + + "§7: " + Helper.format(time / 1000000D, 3) + + ", " + Helper + .format(timing.call / 1000000D, 3) + + ", " + Helper + .format(timing.stdDev / 1000000D, 3)); + }); + double totalMs = total / 1000000D; + cmd.getSender().sendMessage(Helper + .drawUsage(50, Helper.format(totalMs, 3)) + + " §cTotal§7: " + Helper.format(totalMs, 3) + + " §f- §c" + Helper.format(samples.get(), 3) + "%"); + cmd.getSender().sendMessage("-------------------------------------------------"); + } + }, Kauri.INSTANCE, 30L, 3L); + taskMap.put(cmd.getSender().getName(), task); + } + } + + @Command(name = "kauri.profile.paste", display = "profile paste [type]", + description = "make a detailed profile with pastebin.", permission = "kauri.command.profile.paste") + public void onPaste(CommandAdapter cmd) { + ResultsType type = cmd.getArgs().length > 0 ? Arrays.stream(ResultsType.values()) + .filter(rt -> rt.name().equalsIgnoreCase(cmd.getArgs()[0])).findFirst().orElse(ResultsType.TOTAL) + : ResultsType.TOTAL; + + makePaste(cmd.getSender(), type); + } + + @Command(name = "kauri.profile.reset", display = "profile reset", + description = "reset the Kauri profiler.", permission = "kauri.command.profile.reset") + public void onReset(CommandAdapter cmd) { + Kauri.INSTANCE.profiler.reset(); + cmd.getSender().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("profile-reset", "&aReset the Kauri profiler!")); + } + + private void makePaste(CommandSender sender, ResultsType type) { + List body = new ArrayList<>(); + body.add(MiscUtils.lineNoStrike()); + double total = 0; + Map> results = Kauri.INSTANCE.profiler.results(type); + + + for (String key : results.keySet() + .stream() + .sorted(Comparator.comparing(key -> results.get(key).two, Comparator.reverseOrder())) + .collect(Collectors.toList())) { + //Converting nanoseconds to millis to be more readable. + double amount = results.get(key).two / 1000000D; + + Timing timing = Kauri.INSTANCE.profiler.getTimingsMap().get(key); + total += amount; + body.add(key + ": " + amount + "ms (" + results.get(key).one + " calls, std=" + + timing.stdDev + ")"); + } + body.add(" "); + body.add("Total: " + total + "ms"); + body.add("Total Calls: " + Kauri.INSTANCE.profiler.totalCalls); + body.add("Current Ticks: " + Atlas.getInstance().getCurrentTicks()); + StringBuilder builder = new StringBuilder(); + body.forEach(aBody -> builder.append(aBody).append(";")); + + builder.deleteCharAt(body.size() - 1); + + String bodyString = builder.toString().replaceAll(";", "\n"); + + try { + sender.sendMessage(Color.Green + "Results: " + Pastebin.makePaste(bodyString, + "Kauri Profile: " + + DateFormatUtils.format(System.currentTimeMillis(), + ", ", + TimeZone.getTimeZone("604")), + Pastebin.Privacy.UNLISTED)); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/ReloadCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/ReloadCommand.java new file mode 100644 index 000000000..22ea61445 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/ReloadCommand.java @@ -0,0 +1,18 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; + +@Init(commands = true) +public class ReloadCommand { + + @Command(name = "kauri.reload", description = "reload the plugin.", display = "reload", permission = "kauri.command.reload") + public void onCommand(CommandAdapter cmd) { + cmd.getSender().sendMessage(Color.Red + "Reloading Kauri..."); + Kauri.INSTANCE.reload(); + cmd.getSender().sendMessage(Color.Green + "Completed!"); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/ToggleCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/ToggleCommand.java new file mode 100644 index 000000000..ed20b8d81 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/ToggleCommand.java @@ -0,0 +1,38 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.CheckInfo; + +@Init(commands = true) +public class ToggleCommand { + + @Command(name = "kauri.toggle", description = "Toggle a check on or off.", display = "toggle", + usage = "/", aliases = {"toggleCheck", "tCheck", "kauri.t"}, permission = "kauri.command.toggle") + public void onCommand(CommandAdapter cmd) { + if(cmd.getArgs().length > 0) { + if(Check.isCheck(cmd.getArgs()[0])) { + CheckInfo check = Check.getCheckInfo(cmd.getArgs()[0].replace("_", " ")); + + String path = "checks." + check.name() + ".enabled"; + + boolean toggleState = !Kauri.INSTANCE.getConfig().getBoolean(path); + + cmd.getSender().sendMessage(Color.Gray + "Setting check state to " + + (toggleState ? Color.Green : Color.Red) + toggleState + Color.Gray + "..."); + cmd.getSender().sendMessage(Color.Red + "Setting in config..."); + Kauri.INSTANCE.getConfig().set(path, toggleState); + Kauri.INSTANCE.saveConfig(); + + cmd.getSender().sendMessage(Color.Red + "Refreshing data objects with updated information..."); + Kauri.INSTANCE.dataManager.dataMap.values().parallelStream() + .forEach(data -> data.checkManager.checks.get(check.name()).enabled = toggleState); + cmd.getSender().sendMessage(Color.Green + "Completed!"); + } else cmd.getSender().sendMessage(Color.Red + "\"" + cmd.getArgs()[0].replace("_", " ") + "\" is not a check."); + } else cmd.getSender().sendMessage(Color.Red + "Invalid arguments. Usage: /" + cmd.getLabel()); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/UsersCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/UsersCommand.java new file mode 100644 index 000000000..ccb5304db --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/UsersCommand.java @@ -0,0 +1,40 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.MiscUtils; +import dev.brighten.anticheat.Kauri; + +import java.util.stream.Collectors; + +@Init(commands = true) +public class UsersCommand { + + @Command(name = "kauri.users", description = "Shows the online users.", display = "users", + permission = "kauri.command.users") + public void onCommand(CommandAdapter cmd) { + Kauri.INSTANCE.executor.execute(() -> { + cmd.getSender().sendMessage(MiscUtils.line(Color.Dark_Gray)); + cmd.getSender().sendMessage(Color.Yellow + "Forge Users:"); + cmd.getSender().sendMessage(Kauri.INSTANCE.dataManager.dataMap.values().stream() + .filter(data -> data.modData != null) + .map(data -> data.getPlayer().getName()) + .collect(Collectors.joining(Color.Gray + ", " + Color.White))); + cmd.getSender().sendMessage(""); + cmd.getSender().sendMessage(Color.Yellow + "Lunar Client Users:"); + cmd.getSender().sendMessage(Kauri.INSTANCE.dataManager.dataMap.values().stream() + .filter(data -> data.usingLunar) + .map(data -> data.getPlayer().getName()) + .collect(Collectors.joining(Color.Gray + ", " + Color.White))); + cmd.getSender().sendMessage(""); + cmd.getSender().sendMessage(Color.Yellow + "Misc Users:"); + cmd.getSender().sendMessage(Kauri.INSTANCE.dataManager.dataMap.values().stream() + .filter(data -> data.modData == null && !data.usingLunar) + .map(data -> data.getPlayer().getName()) + .collect(Collectors.joining(Color.Gray + ", " + Color.White))); + cmd.getSender().sendMessage(MiscUtils.line(Color.Dark_Gray)); + }); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/commands/WandCommand.java b/Impl/src/main/java/dev/brighten/anticheat/commands/WandCommand.java new file mode 100644 index 000000000..f26310ab9 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/commands/WandCommand.java @@ -0,0 +1,27 @@ +package dev.brighten.anticheat.commands; + +import cc.funkemunky.api.commands.ancmd.Command; +import cc.funkemunky.api.commands.ancmd.CommandAdapter; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.listeners.generalChecks.BukkitListener; + +import java.util.Arrays; + +@Init(commands = true) +public class WandCommand { + + @Command(name = "kauri.wand", description = "view boundingBox debugs.", display = "wand", playerOnly = true, permission = "kauri.command.wand") + public void onCommand(CommandAdapter cmd) { + if(Arrays.stream(cmd.getPlayer().getInventory().getContents()) + .anyMatch(item -> item == null || item.getType().equals(XMaterial.AIR.parseMaterial()))) { + cmd.getPlayer().getInventory().addItem(BukkitListener.MAGIC_WAND); + cmd.getPlayer().updateInventory(); + } else { + cmd.getPlayer().getWorld().dropItemNaturally(cmd.getPlayer().getLocation(), BukkitListener.MAGIC_WAND); + cmd.getPlayer().sendMessage(Color.Red + Color.Italics + "Your inventory was full. Item dropped onto ground."); + } + cmd.getPlayer().sendMessage(Color.Green + "Added a magic wand to your inventory. Use it wisely."); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/data/DataManager.java b/Impl/src/main/java/dev/brighten/anticheat/data/DataManager.java new file mode 100644 index 000000000..75dc4b957 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/data/DataManager.java @@ -0,0 +1,36 @@ +package dev.brighten.anticheat.data; + +import cc.funkemunky.api.utils.RunUtils; +import dev.brighten.anticheat.Kauri; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public class DataManager { + public Map dataMap = new ConcurrentHashMap<>(); + public List hasAlerts = new CopyOnWriteArrayList<>(), devAlerts = new CopyOnWriteArrayList<>(), + debugging = new CopyOnWriteArrayList<>(); + + public DataManager() { + RunUtils.taskTimerAsync(() -> { + hasAlerts.clear(); + dataMap.values().stream().filter(data -> data.alerts).forEach(hasAlerts::add); + debugging.clear(); + dataMap.values().stream().filter(data -> data.debugging != null).forEach(debugging::add); + }, Kauri.INSTANCE, 60L, 30L); + } + + public ObjectData getData(Player player) { + return dataMap.getOrDefault(player.getUniqueId(), null); + } + + public void createData(Player player) { + ObjectData data = new ObjectData(player.getUniqueId()); + + dataMap.put(player.getUniqueId(), data); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/data/ObjectData.java b/Impl/src/main/java/dev/brighten/anticheat/data/ObjectData.java new file mode 100644 index 000000000..d8261befa --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/data/ObjectData.java @@ -0,0 +1,242 @@ +package dev.brighten.anticheat.data; + +import cc.funkemunky.api.handlers.ForgeHandler; +import cc.funkemunky.api.handlers.ModData; +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.api.TinyProtocolHandler; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.KLocation; +import cc.funkemunky.api.utils.RunUtils; +import cc.funkemunky.api.utils.math.RollingAverageLong; +import cc.funkemunky.api.utils.math.cond.MaxInteger; +import cc.funkemunky.api.utils.objects.evicting.ConcurrentEvictingList; +import cc.funkemunky.api.utils.world.types.SimpleCollisionBox; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.CancelType; +import dev.brighten.anticheat.check.api.Config; +import dev.brighten.anticheat.data.classes.BlockInformation; +import dev.brighten.anticheat.data.classes.CheckManager; +import dev.brighten.anticheat.data.classes.PlayerInformation; +import dev.brighten.anticheat.data.classes.PredictionService; +import dev.brighten.anticheat.listeners.PacketListener; +import dev.brighten.anticheat.processing.ClickProcessor; +import dev.brighten.anticheat.processing.MovementProcessor; +import dev.brighten.anticheat.processing.PotionProcessor; +import dev.brighten.anticheat.processing.keepalive.KeepAlive; +import dev.brighten.anticheat.utils.PastLocation; +import dev.brighten.anticheat.utils.TickTimer; +import lombok.AllArgsConstructor; +import lombok.val; +import org.bukkit.Bukkit; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Consumer; + +public class ObjectData { + + public UUID uuid; + private Player player; + public boolean alerts, devAlerts, sniffing, usingLunar; + + //Debugging + public String debugging; + public UUID debugged; + + public long creation, lagTicks, noLagTicks; + public PastLocation pastLocation, targetPastLocation; + public LivingEntity target; + public SimpleCollisionBox box = new SimpleCollisionBox(), targetBounds = new SimpleCollisionBox(); + public ObjectData targetData; + public CheckManager checkManager; + public PlayerInformation playerInfo; + public BlockInformation blockInfo; + public LagInformation lagInfo; + public PredictionService predictionService; + public MovementProcessor moveProcessor; + public PotionProcessor potionProcessor; + public ClickProcessor clickProcessor; + public int hashCode; + public boolean banned; + public ModData modData; + public KLocation targetLoc; + public ProtocolVersion playerVersion = ProtocolVersion.UNKNOWN; + public Set boxDebuggers = new HashSet<>(); + public final List keepAliveStamps = new CopyOnWriteArrayList<>(); + public final Map entityLocations = new HashMap<>(); + public ConcurrentEvictingList typesToCancel = new ConcurrentEvictingList<>(10); + public final Map keepAlives = Collections.synchronizedMap(new HashMap<>()); + public final List sniffedPackets = new CopyOnWriteArrayList<>(); + public BukkitTask task; + private ExecutorService playerThread; + + public ObjectData(UUID uuid) { + this.uuid = uuid; + hashCode = uuid.hashCode(); + + if(PacketListener.expansiveThreading) + playerThread = Executors.newSingleThreadExecutor(); + + if(!Config.testMode) { + if(alerts = getPlayer().hasPermission("kauri.command.alerts")) + Kauri.INSTANCE.dataManager.hasAlerts.add(this); + } + playerInfo = new PlayerInformation(this); + creation = playerInfo.lastRespawn = System.currentTimeMillis(); + blockInfo = new BlockInformation(this); + clickProcessor = new ClickProcessor(this); + pastLocation = new PastLocation(); + lagInfo = new LagInformation(); + targetPastLocation = new PastLocation(); + potionProcessor = new PotionProcessor(this); + checkManager = new CheckManager(this); + checkManager.addChecks(); + + predictionService = new PredictionService(this); + moveProcessor = new MovementProcessor(this); + + modData = ForgeHandler.getMods(getPlayer()); + RunUtils.taskLaterAsync(() -> { + modData = ForgeHandler.getMods(getPlayer()); + }, Kauri.INSTANCE, 100L); + Kauri.INSTANCE.executor.execute(() -> { + playerVersion = TinyProtocolHandler.getProtocolVersion(getPlayer()); + }); + if(task != null) task.cancel(); + task = RunUtils.taskTimerAsync(() -> { + if(getPlayer() == null) { + task.cancel(); + return; + } + + if(Config.kickForLunar18 && usingLunar + && !playerVersion.equals(ProtocolVersion.UNKNOWN) + && playerVersion.isAbove(ProtocolVersion.V1_8)) { + RunUtils.task(() -> getPlayer().kickPlayer(Color.Red + "Lunar Client 1.8.9 is not allowed.\nJoin on 1.7.10 or any other client.")); + } + }, Kauri.INSTANCE, 40L, 40L); + + getPlayer().getActivePotionEffects().forEach(pe -> { + runKeepaliveAction(d -> this.potionProcessor.potionEffects.add(pe)); + }); + } + + public ExecutorService getThread() { + if(PacketListener.expansiveThreading) + return playerThread; + return PacketListener.service; + } + + public int[] getReceived() { + int[] toReturn = new int[] {0, 0}; + val op = Kauri.INSTANCE.keepaliveProcessor.getResponse(this); + + if(op.isPresent()) { + toReturn[0] = op.get().start; + val op2 = op.get().getReceived(uuid); + + op2.ifPresent(kaReceived -> toReturn[1] = kaReceived.stamp); + } + + return toReturn; + } + + public void runTask(Runnable runnable) { + //tasksToRun.add(runnable); + getThread().execute(runnable); + } + + public short getRandomShort(int baseNumber, int bound) { + return (short) getRandomInt(baseNumber, bound); + } + + public int getRandomInt(int baseNumber, int bound) { + return baseNumber + ThreadLocalRandom.current().nextInt(bound); + } + + public long getRandomLong(long baseNumber, long bound) { + return baseNumber + ThreadLocalRandom.current().nextLong(bound); + } + + public int runKeepaliveAction(Consumer action) { + int id = Kauri.INSTANCE.keepaliveProcessor.currentKeepalive.start; + + keepAliveStamps.add(new Action(id, action)); + + return id; + } + + public Player getPlayer() { + if(player == null) { + this.player = Bukkit.getPlayer(uuid); + } + return this.player; + } + + public class LagInformation { + public long lastKeepAlive, lastTrans, lastClientTrans, ping, lastPing, averagePing, + millisPing, lmillisPing, recieved, start; + public int transPing, lastTransPing; + public MaxInteger lagTicks = new MaxInteger(25); + public boolean lagging; + public TickTimer lastPacketDrop = new TickTimer( 10), + lastPingDrop = new TickTimer( 40); + public RollingAverageLong pingAverages = new RollingAverageLong(10, 0); + public long lastFlying = 0; + } + + public void onLogout() { + if(PacketListener.expansiveThreading) + playerThread.shutdownNow(); + task.cancel(); + keepAliveStamps.clear(); + Kauri.INSTANCE.dataManager.hasAlerts.remove(this); + Kauri.INSTANCE.dataManager.debugging.remove(this); + checkManager.checkMethods.clear(); + checkManager.checks.clear(); + checkManager = null; + typesToCancel.clear(); + sniffedPackets.clear(); + keepAliveStamps.clear(); + Kauri.INSTANCE.dataManager.dataMap.remove(uuid); + } + + public static void debugBoxes(boolean debugging, Player debugger) { + debugBoxes(debugging, debugger, new ObjectData[0]); + } + public static void debugBoxes(boolean debugging, Player debugger, ObjectData... targets) { + if(!debugging) { + List toRemove = targets.length == 0 + ? new ArrayList<>(Kauri.INSTANCE.dataManager.dataMap.values()) : Arrays.asList(targets); + + toRemove.stream() + .filter(d -> d.boxDebuggers.contains(debugger)) + .forEach(d -> d.boxDebuggers.remove(debugger)); + } else if(targets.length > 0) { + Arrays.stream(targets).forEach(d -> d.boxDebuggers.add(debugger)); + } + } + + public static void debugBoxes(boolean debugging, Player debugger, UUID... targets) { + debugBoxes(debugging, debugger, Arrays.stream(targets) + .map(uuid -> Kauri.INSTANCE.dataManager.dataMap.get(uuid)).filter(Objects::nonNull) + .toArray(ObjectData[]::new)); + } + + public static void debugBoxes(boolean debugging, Player debugger, String... targets) { + debugBoxes(debugging, debugger, (ObjectData[])Arrays.stream(targets).map(Bukkit::getPlayer) + .map(Kauri.INSTANCE.dataManager::getData).toArray(ObjectData[]::new)); + } + + @AllArgsConstructor + public static class Action { + public int stamp; + public Consumer action; + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/data/classes/BlockInformation.java b/Impl/src/main/java/dev/brighten/anticheat/data/classes/BlockInformation.java new file mode 100644 index 000000000..1b26a1681 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/data/classes/BlockInformation.java @@ -0,0 +1,242 @@ +package dev.brighten.anticheat.data.classes; + +import cc.funkemunky.api.Atlas; +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.packet.types.enums.WrappedEnumParticle; +import cc.funkemunky.api.utils.BlockUtils; +import cc.funkemunky.api.utils.KLocation; +import cc.funkemunky.api.utils.Materials; +import cc.funkemunky.api.utils.XMaterial; +import cc.funkemunky.api.utils.world.BlockData; +import cc.funkemunky.api.utils.world.types.SimpleCollisionBox; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.processing.EntityProcessor; +import dev.brighten.anticheat.utils.CacheList; +import dev.brighten.anticheat.utils.CacheMap; +import dev.brighten.anticheat.utils.CollisionHandler; +import dev.brighten.anticheat.utils.Helper; +import lombok.val; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.EntityType; +import org.bukkit.util.Vector; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class BlockInformation { + private ObjectData objectData; + public boolean onClimbable, onSlab, onStairs, onHalfBlock, inLiquid, inLava, inWater, inWeb, onSlime, onIce, + onSoulSand, blocksAbove, collidesVertically, collidesHorizontally, blocksNear, inBlock, miscNear; + public float currentFriction, fromFriction; + public CollisionHandler + handler = new CollisionHandler(new ArrayList<>(), new ArrayList<>(), new KLocation(0,0,0)); + public List verticalCollisions, horizontalCollisions; + public List aboveCollisions = new ArrayList<>(), belowCollisions = new ArrayList<>(); + public final List blocks = Collections.synchronizedList(new ArrayList<>()); + + public BlockInformation(ObjectData objectData) { + this.objectData = objectData; + } + + public void runCollisionCheck() { + if(!Kauri.INSTANCE.enabled + || Kauri.INSTANCE.lastEnabled.hasNotPassed(6)) return; + synchronized (blocks) { + blocks.clear(); + } + + double dy = Math.abs(objectData.playerInfo.deltaY); + double dh = objectData.playerInfo.deltaXZ; + + if(dy > 3) dy = 3; + if(dh > 3) dh = 3; + + int startX = Location.locToBlock(objectData.playerInfo.to.x - 2 - dh); + int endX = Location.locToBlock(objectData.playerInfo.to.x + 2 + dh); + int startY = Location.locToBlock(objectData.playerInfo.to.y - 2 - dy); + int endY = Location.locToBlock(objectData.playerInfo.to.y + 3 + dy); + int startZ = Location.locToBlock(objectData.playerInfo.to.z - 2 - dh); + int endZ = Location.locToBlock(objectData.playerInfo.to.z + 2 + dh); + + objectData.playerInfo.worldLoaded = true; + synchronized (blocks) { + for(int x = startX ; x < endX ; x++) { + for(int y = startY ; y < endY ; y++) { + for(int z = startZ ; z < endZ ; z++) { + Location loc = new Location(objectData.getPlayer().getWorld(), x, y, z); + + if(loc.getWorld().isChunkLoaded(loc.getBlockX() >> 4, loc.getBlockZ() >> 4)) { + blocks.add(loc.getBlock()); + } else objectData.playerInfo.worldLoaded = false; + } + } + } + } + + blocks.parallelStream().forEach(this::updateBlock); + + if(!objectData.playerInfo.worldLoaded) return; + + SimpleCollisionBox waterBox = objectData.box.copy().expand(0, -.38, 0); + + waterBox.xMin = Math.floor(waterBox.xMin); + waterBox.yMin = Math.floor(waterBox.yMin); + waterBox.zMin = Math.floor(waterBox.zMin); + waterBox.xMax = Math.floor(waterBox.xMax + 1.); + waterBox.yMax = Math.floor(waterBox.yMax + 1.); + waterBox.zMax = Math.floor(waterBox.zMax + 1.); + + SimpleCollisionBox lavaBox = objectData.box.copy().expand(-.1f, -.4f, -.1f); + + waterBox.xMin = Math.floor(waterBox.xMin); + waterBox.yMin = Math.floor(waterBox.yMin); + waterBox.zMin = Math.floor(waterBox.zMin); + waterBox.xMax = Math.floor(waterBox.xMax + 1.); + waterBox.yMax = Math.floor(waterBox.yMax + 1.); + waterBox.zMax = Math.floor(waterBox.zMax + 1.); + + CollisionHandler handler = new CollisionHandler(blocks, + Kauri.INSTANCE.entityProcessor.vehicles.getOrDefault(objectData.getPlayer().getUniqueId(), new ArrayList<>()), + objectData.playerInfo.to); + + //Bukkit.broadcastMessage("chigga4"); + + handler.setSize(0.6f, 0.05f); + + handler.setOffset(-0.1f); + objectData.playerInfo.serverGround = + handler.isCollidedWith(Materials.SOLID) || handler.contains(EntityType.BOAT); + //Bukkit.broadcastMessage("chigga5"); + handler.setOffset(-0.4f); + onSlab = handler.isCollidedWith(Materials.SLABS); + onStairs = handler.isCollidedWith(Materials.STAIRS); + onHalfBlock = onSlab || onStairs; + + handler.setOffset(-0.6f); + handler.setSize(0.8f, 1f); + miscNear = handler.isCollidedWith(XMaterial.CAKE.parseMaterial(), + Material.valueOf(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_13) + ? "CAKE_BLOCK" : "LEGACY_CAKE_BLOCK"), + XMaterial.BREWING_STAND.parseMaterial(), XMaterial.FLOWER_POT.parseMaterial(), + XMaterial.SKULL_ITEM.parseMaterial(), XMaterial.SNOW.parseMaterial(), + XMaterial.WITHER_SKELETON_SKULL.parseMaterial(), XMaterial.SKELETON_WALL_SKULL.parseMaterial(), + XMaterial.WITHER_SKELETON_WALL_SKULL.parseMaterial()); + + if(!onHalfBlock) onHalfBlock = miscNear; + + handler.setSize(0.6f, 1.8f); + handler.setSingle(true); + onIce = handler.isCollidedWith(Materials.ICE); + handler.setOffset(-0.02f); + handler.setSingle(false); + handler.setSize(0.602f, 1.802f); + handler.setOffset(-0.001f); + onSoulSand = handler.isCollidedWith(XMaterial.SOUL_SAND.parseMaterial()); + inWeb = handler.isCollidedWith(XMaterial.COBWEB.parseMaterial()); + onSlime = handler.isCollidedWith(XMaterial.SLIME_BLOCK.parseMaterial()); + + inLava = handler.isCollidedWith(lavaBox, XMaterial.LAVA.parseMaterial(), + XMaterial.STATIONARY_LAVA.parseMaterial()); + inWater = handler.isCollidedWith(waterBox, XMaterial.WATER.parseMaterial(), XMaterial.STATIONARY_WATER.parseMaterial()); + inLiquid = inLava || inWater; + + handler.setOffset(0); + handler.setSize(0.599f, 1.8f); + + inBlock = handler.isCollidedWith(Materials.SOLID); + + if(objectData.playerInfo.deltaY <= 0) { + onClimbable = objectData.playerInfo.blockOnTo != null + && BlockUtils.isClimbableBlock(objectData.playerInfo.blockOnTo); + } else { + handler.setSize(0.64f, 1.8f); + onClimbable = handler.isCollidedWith(Materials.LADDER); + } + + handler.setSize(0.6f, 2.4f); + handler.setOffset(1.25f); + blocksAbove = handler.isCollidedWith(Materials.SOLID); + + handler.setSize(2f, 1.79f); + handler.setOffset(0.01f); + blocksNear = handler.isCollidedWith(Materials.SOLID); + + if(objectData.boxDebuggers.size() > 0) { + handler.setSize(0.62f, 1.81f); + handler.setOffset(-0.01f); + + handler.getBlocks().stream().filter(block -> Materials.checkFlag(block.getType(), Materials.LIQUID)) + .forEach(block -> { + objectData.boxDebuggers.forEach(pl -> { + List boxes = new ArrayList<>(); + BlockData.getData(block.getType()).getBox(block, ProtocolVersion.getGameVersion()).downCast(boxes); + + boxes.forEach(sbox -> { + val max = sbox.max().subtract(block.getLocation().toVector()); + val min = sbox.min().subtract(block.getLocation().toVector()); + + Vector subbed = max.subtract(min); + + pl.sendMessage("x=" + subbed.getX() + " y=" + subbed.getY() + " z=" + subbed.getZ()); + }); + + }); + }); + handler.getCollisionBoxes().forEach(cb -> cb.draw(WrappedEnumParticle.FLAME, objectData.boxDebuggers)); + } + handler.setSize(0.6f, 1.8f); + + handler.setOffset(0f); + + SimpleCollisionBox box = getBox().expand( + Math.abs(objectData.playerInfo.from.x - objectData.playerInfo.to.x) + 0.1f, + -0.01f, + Math.abs(objectData.playerInfo.from.z - objectData.playerInfo.to.z) + 0.1f); + collidesHorizontally = !(horizontalCollisions = blockCollisions(handler.getBlocks(), box)).isEmpty(); + + handler.setSize(0.8f, 2.8f); + handler.setOffset(1f); + + aboveCollisions.clear(); + belowCollisions.clear(); + + handler.getCollisionBoxes().forEach(cb -> cb.downCast(aboveCollisions)); + + handler.setSize(0.8f, 0.8f); + handler.setOffset(-1f); + handler.getCollisionBoxes().forEach(cb -> cb.downCast(belowCollisions)); + + handler.setSize(0.6f, 1.8f); + handler.setOffset(0f); + + box = getBox().expand(0, 0.1f, 0); + collidesVertically = !(verticalCollisions = blockCollisions(handler.getBlocks(), box)).isEmpty(); + + this.handler = handler; + } + + private final List lastUpdates = new CacheList<>(10, TimeUnit.SECONDS); + private synchronized void updateBlock(Block block) { + if(!lastUpdates.contains(block.getLocation())) { + objectData.getPlayer().sendBlockChange(block.getLocation(), block.getType(), block.getData()); + lastUpdates.add(block.getLocation()); + } + } + + public SimpleCollisionBox getBox() { + return new SimpleCollisionBox(objectData.playerInfo.to.toVector(), objectData.playerInfo.to.toVector()) + .expand(0.3, 0,0.3).expandMax(0, 1.8, 0); + } + + private static List blockCollisions(List blocks, SimpleCollisionBox box) { + return blocks.stream() + .filter(b -> Helper.isCollided(box, + BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()))) + .collect(Collectors.toCollection(LinkedList::new)); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/data/classes/CheckManager.java b/Impl/src/main/java/dev/brighten/anticheat/data/classes/CheckManager.java new file mode 100644 index 000000000..61b68401b --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/data/classes/CheckManager.java @@ -0,0 +1,150 @@ +package dev.brighten.anticheat.data.classes; + +import cc.funkemunky.api.events.AtlasEvent; +import cc.funkemunky.api.reflections.types.WrappedClass; +import cc.funkemunky.api.reflections.types.WrappedMethod; +import cc.funkemunky.api.tinyprotocol.api.NMSObject; +import dev.brighten.anticheat.check.api.*; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.utils.MiscUtils; +import lombok.val; +import org.bukkit.event.Event; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class CheckManager { + private ObjectData objectData; + public Map checks = new ConcurrentHashMap<>(); + public final Map, List> checkMethods = Collections.synchronizedMap(new HashMap<>()); + + public CheckManager(ObjectData objectData) { + this.objectData = objectData; + } + + public void runPacket(NMSObject object, long timeStamp) { + synchronized (checkMethods) { + if(!checkMethods.containsKey(object.getClass())) return; + + val methods = checkMethods.get(object.getClass()); + + int currentTick = MiscUtils.currentTick(); + methods.parallelStream() + .forEach(wrapped -> { + if(!wrapped.isBoolean && wrapped.isPacket && wrapped.check.enabled && wrapped.isCompatible()) { + if(wrapped.oneParam) wrapped.method.invoke(wrapped.check, object); + else { + if(wrapped.isTimeStamp) { + wrapped.method.invoke(wrapped.check, object, timeStamp); + } else wrapped.method.invoke(wrapped.check, object, currentTick); + } + } + }); + } + } + + public boolean runPacketCancellable(NMSObject object, long timeStamp) { + if(!checkMethods.containsKey(object.getClass())) return false; + + val methods = checkMethods.get(object.getClass()); + + int currentTick = MiscUtils.currentTick(); + boolean cancelled = false; + for (WrappedCheck wrapped : methods) { + if(!wrapped.isBoolean) continue; + + if(wrapped.isPacket && wrapped.check.enabled && wrapped.isCompatible()) { + if(wrapped.oneParam) { + if(wrapped.method.invoke(wrapped.check, object)) cancelled = true; + } else if(wrapped.isTimeStamp) { + if(wrapped.method.invoke(wrapped.check, object, timeStamp)) cancelled = true; + } else if(wrapped.method.invoke(wrapped.check, object, currentTick)) cancelled = true; + } + } + + return cancelled; + } + + public void runEvent(Event event) { + synchronized (checkMethods) { + if(!checkMethods.containsKey(event.getClass())) return; + + val methods = checkMethods.get(event.getClass()); + + methods.stream().filter(wrapped -> wrapped.isBoolean).sorted() + .forEach(wrapped -> { + if(wrapped.isEvent && wrapped.check.enabled) { + wrapped.method.invoke(wrapped.check, event); + } + }); + } + } + + public void runEvent(AtlasEvent event) { + synchronized (checkMethods) { + if(!checkMethods.containsKey(event.getClass())) return; + + val methods = checkMethods.get(event.getClass()); + + methods.parallelStream() + .forEach(wrapped -> { + if(!wrapped.isPacket && wrapped.check.enabled) { + wrapped.method.invoke(wrapped.check, event); + } + }); + } + } + + public void addChecks() { + if(objectData.getPlayer().hasPermission("kauri.bypass") && Config.bypassPermission) return; + Check.checkClasses.keySet().stream() + .map(clazz -> { + CheckInfo settings = Check.checkClasses.get(clazz); + Check check = clazz.getConstructor().newInstance(); + check.setData(objectData); + CheckSettings checkSettings = Check.checkSettings.get(clazz); + check.enabled = checkSettings.enabled; + check.executable = checkSettings.executable; + check.cancellable = checkSettings.cancellable; + check.cancelMode = checkSettings.cancelMode; + check.developer = settings.developer(); + check.name = settings.name(); + check.description = settings.description(); + check.punishVl = settings.punishVL(); + check.checkType = settings.checkType(); + check.maxVersion = settings.maxVersion(); + check.vlToFlag = settings.vlToFlag(); + check.minVersion = settings.minVersion(); + check.banExempt = objectData.getPlayer().hasPermission("kauri.bypass.ban"); + return check; + }) + .sequential() + .forEach(check -> checks.put(check.name, check)); + + checks.keySet().stream().map(name -> checks.get(name)).forEach(check -> { + WrappedClass checkClass = new WrappedClass(check.getClass()); + + Arrays.stream(check.getClass().getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(Packet.class) + || method.isAnnotationPresent(dev.brighten.anticheat.check.api.Event.class)) + .map(method -> new WrappedMethod(checkClass, method)) + .forEach(method -> { + Class> parameter = method.getParameters().get(0); + List methods = checkMethods.getOrDefault( + parameter, + new ArrayList<>()); + + methods.add(new WrappedCheck(check, method)); + if(method.getMethod().isAnnotationPresent(Packet.class)) { + methods.sort(Comparator.comparing(m -> + m.method.getMethod().getAnnotation(Packet.class).priority().getPriority())); + } else { + methods.sort(Comparator.comparing(m -> + m.method.getMethod().getAnnotation(dev.brighten.anticheat.check.api.Event.class) + .priority().getPriority())); + } + checkMethods.put(parameter, methods); + }); + }); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/data/classes/PlayerInformation.java b/Impl/src/main/java/dev/brighten/anticheat/data/classes/PlayerInformation.java new file mode 100644 index 000000000..069384ca8 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/data/classes/PlayerInformation.java @@ -0,0 +1,88 @@ +package dev.brighten.anticheat.data.classes; + +import cc.funkemunky.api.tinyprotocol.packet.types.enums.WrappedEnumAnimation; +import cc.funkemunky.api.utils.KLocation; +import cc.funkemunky.api.utils.objects.evicting.EvictingList; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.utils.TickTimer; +import lombok.NoArgsConstructor; +import org.bukkit.block.Block; + +@NoArgsConstructor +public class PlayerInformation { + public boolean serverGround, lClientGround, clientGround, nearGround, + collided, insideBlock, phaseFlagged, + onLadder, isClimbing, usingItem, wasOnIce, wasOnSlime, jumped, inAir, lworldLoaded, worldLoaded; + public boolean generalCancel, flightCancel; + public float fallDistance; + public double deltaY, lDeltaY, deltaX, lDeltaX, deltaZ, lDeltaZ, deltaXZ, lDeltaXZ, + jumpHeight, totalHeight, baseSpeed; + public float headYaw, headPitch; + public float deltaYaw, deltaPitch, lDeltaYaw, lDeltaPitch; + public long lastVelocityTimestamp; + public Block blockBelow, blockOnTo; + + public PlayerInformation(ObjectData data) { + + liquidTimer = new TickTimer(50); + webTimer = new TickTimer(40); + climbTimer = new TickTimer(40); + slimeTimer = new TickTimer(75); + iceTimer = new TickTimer(45); + blockAboveTimer = new TickTimer(50); + soulSandTimer = new TickTimer(40); + lastBrokenBlock = new TickTimer(5); + lastVelocity = new TickTimer(20); + lastTargetSwitch = new TickTimer(3); + lastBlockPlace = new TickTimer(10); + lastToggleFlight = new TickTimer(10); + lastChunkUnloaded = new TickTimer(20); + lastWindowClick = new TickTimer(20); + lastInsideBlock = new TickTimer(5); + lastHalfBlock = new TickTimer(20); + lastPlaceLiquid = new TickTimer(20); + lastUseItem = new TickTimer(15); + lastTeleportTimer = new TickTimer(10); + lastGamemodeTimer = new TickTimer(10); + lastRespawnTimer = new TickTimer(20); + lastAttack = new TickTimer(5); + cinematicTimer = new TickTimer(8); + } + + //Cinematic + public boolean cinematicMode; + + //Gcd + public int yawGCD, pitchGCD, lastYawGCD, lastPitchGCD; + + //Server Position + public long lastServerPos, lastRespawn; + public boolean serverPos; + public EvictingList posLocs = new EvictingList<>(5); + + //Attack + public TickTimer lastAttack; + public long lastAttackTimeStamp; + + //actions + public boolean sneaking, sprinting, ridingJump, breakingBlock, flying, canFly, creative, inVehicle, + gliding, riptiding, inventoryOpen, serverAllowedFlight; + public int inventoryId = 0; + + //Keepalives + public int velocityKeepalive, teleportKeepalive; + + //ticks + public int groundTicks, airTicks; + public TickTimer liquidTimer, webTimer, climbTimer, slimeTimer, iceTimer, blockAboveTimer, soulSandTimer; + public TickTimer lastBrokenBlock, lastVelocity, lastTargetSwitch, lastBlockPlace, lastToggleFlight, + lastWindowClick, lastInsideBlock, lastHalfBlock, lastPlaceLiquid, lastUseItem, + lastTeleportTimer, lastGamemodeTimer, lastRespawnTimer, lastChunkUnloaded, cinematicTimer; + + public double velocityX, velocityY, velocityZ; + + public WrappedEnumAnimation animation = WrappedEnumAnimation.NONE; + + public KLocation from = new KLocation(0,0,0), to = new KLocation(0,0,0), + groundLoc; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/data/classes/PredictionService.java b/Impl/src/main/java/dev/brighten/anticheat/data/classes/PredictionService.java new file mode 100644 index 000000000..ff48eec8f --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/data/classes/PredictionService.java @@ -0,0 +1,607 @@ +package dev.brighten.anticheat.data.classes; + +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.utils.*; +import cc.funkemunky.api.utils.world.BlockData; +import cc.funkemunky.api.utils.world.types.SimpleCollisionBox; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.utils.Helper; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.potion.PotionEffectType; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +public class PredictionService { + + private ObjectData data; + public boolean fly, velocity, position, sneak, sprint, useSword, hit, dropItem, inWeb, checkConditions, + lastOnGround, onGround, lastSprint, fMath, fastMath, walkSpecial, lastVelocity; + public double posX, posY, posZ, lPosX, lPosY, lPosZ, rmotionX, rmotionY, rmotionZ, lmotionX, lmotionZ, lmotionY; + public double predX, predZ; + public double aiMoveSpeed; + public boolean flag, isBelowSpecial; + public float walkSpeed = 0.1f, yaw, moveStrafing, moveForward; + + public String key = "Nothing"; + + public PredictionService(ObjectData data) { + this.data = data; + } + + public void onReceive(WrappedInFlyingPacket packet) { + + inWeb = data.blockInfo.inWeb; + + if(packet.isLook()) { + yaw = packet.getYaw(); + } + + if(packet.isPos()) { + posX = packet.getX(); + posY = packet.getY(); + posZ = packet.getZ(); + } else { + posX = 999999999; + posY = 999999999; + posZ = 999999999; + } + + + onGround = packet.isGround(); + + boolean specialBlock = false; + + rmotionX = posX - lPosX; + rmotionY = posY - lPosY; + rmotionZ = posZ - lPosZ; + + Block blockBelow = BlockUtils.getBlock(new Location(data.getPlayer().getWorld(), MathHelper.floor_double(posX), + MathHelper.floor_double(posY - 0.20000000298023224D), MathHelper.floor_double(posZ))); + + if(blockBelow != null) { + isBelowSpecial = XMaterial.SLIME_BLOCK.parseMaterial().equals(blockBelow.getType()) + || XMaterial.SOUL_SAND.parseMaterial().equals(blockBelow.getType()); + } + + //dev.brighten.anticheat.utils.MiscUtils.testMessage(Color.Gray + rmotionX + ", " + rmotionZ); + fMath = fastMath; // if the Player uses Optifine FastMath + + try { + if(velocity) { + lmotionX = data.playerInfo.velocityX; + lmotionZ = data.playerInfo.velocityZ; + } + if(!position && (checkConditions = checkConditions(lastSprint))) { + if (lastSprint && hit) { // If the Player Sprints and Hit a Player he get slowdown + lmotionX *= 0.6D; + lmotionZ *= 0.6D; + } + + double mx = rmotionX - lmotionX; // mx, mz is an Value to calculate the rotation and the Key of the Player + double mz = rmotionZ - lmotionZ; + + calcKey(mx, mz); + + //MiscUtils.testMessage("key: " + key); + calc(true); + } + + specialBlock = checkSpecialBlock(); // If the Player Walks on a Special block like Ice, Slime, Soulsand + } catch (Exception e2) { + e2.printStackTrace(); + } + + if(dropItem) { + useSword = false; + } + dropItem = false; + + if(blockBelow != null + && onGround) { + if(XMaterial.SLIME_BLOCK.parseMaterial().equals(blockBelow.getType())) { + if(Math.abs(data.playerInfo.deltaY) < 0.1 && !data.playerInfo.sneaking) { + double shit = 0.4081 + Math.abs(data.playerInfo.deltaY) * 0.20000000298023224D; + + rmotionZ *= shit; + rmotionX *= shit; + //MiscUtils.testMessage("slime: " + shit + ", " + data.blockInfo.currentFriction); + } + } else if(XMaterial.SOUL_SAND.parseMaterial().equals(blockBelow.getType())) { + double shit = 0.4; + rmotionX *= shit; + rmotionZ *= shit; + //MiscUtils.testMessage("soulsand: " + shit + ", " + data.blockInfo.currentFriction); + } + } + + if(data.blockInfo.inWeb) { + rmotionX *= 0.25; + rmotionZ *= 0.25; + } + + double multiplier = 0.9100000262260437D; // multiplier = is the value that the client multiplies every move + + if(!data.blockInfo.inLiquid) { + if (lastOnGround) { + // multiplier = 0.60000005239967D; + multiplier*= data.blockInfo.fromFriction; + + //MiscUtils.testMessage("friction: " + data.blockInfo.currentFriction); + } + rmotionX *= multiplier; + rmotionZ *= multiplier; + } else { + if (data.blockInfo.inLava) { + rmotionX *= 0.5f; + rmotionZ *= 0.5f; + } else if (data.blockInfo.inWater) { + float f1 = 0.8f; + //float f2 = 0.02f; + float f3 = PlayerUtils.getDepthStriderLevel(data.getPlayer()); + + if (f3 > 0) { + f3 = 3.0f; + } + + if (!lastOnGround) { + f3 *= .5f; + } + + if (f3 > 0) { + f1 += (0.54600006F - f1) * f3 / 3.0F; + } + rmotionX *= f1; + rmotionZ *= f1; + } + } + + if(ProtocolVersion.getGameVersion().isOrBelow(ProtocolVersion.V1_8_9)) { + if (Math.abs(rmotionX) < 0.005D) // the client sets the motionX,Y and Z to 0 if its slower than 0.005D + // because he would never stand still + rmotionX = 0.0D; + if (Math.abs(rmotionY) < 0.005D) + rmotionY = 0.0D; + if (Math.abs(rmotionZ) < 0.005D) + rmotionZ = 0.0D; + } + + // Saves the values for the next MovePacket + + lmotionX = rmotionX; + lmotionY = rmotionY; + lmotionZ = rmotionZ; + + lPosX = posX; + lPosY = posY; + lPosZ = posZ; + + hit = false; + lastVelocity = velocity; + velocity = false; + position = false; + + lastOnGround = onGround; + lastSprint = sprint; + walkSpecial = specialBlock; + fastMath = fMath; + } + + private boolean checkSpecialBlock() { + return (data.playerInfo.iceTimer.hasNotPassed(20) + || data.playerInfo.soulSandTimer.hasNotPassed(20) + || data.playerInfo.climbTimer.hasNotPassed(20) || data.playerInfo.wasOnSlime) + && (onGround || lastOnGround); + } + + private float getMotionYaw(double mx, double mz) { + float motionYaw = (float) (Math.atan2(mz, mx) * 180.0D / Math.PI) - 90.0F; // is the rotationYaw from the Motion + // of the Player + + motionYaw -= yaw; + + while (motionYaw > 360.0F) + motionYaw -= 360.0F; + while (motionYaw < 0.0F) + motionYaw += 360.0F; + + return motionYaw; + } + private void calcKey(float motionYaw, double mx, double mz) { + // of the Player + + int direction = 6; + + motionYaw -= yaw; + + while (motionYaw > 360.0F) + motionYaw -= 360.0F; + while (motionYaw < 0.0F) + motionYaw += 360.0F; + + motionYaw /= 45.0F; // converts the rotationYaw of the Motion to integers to get keys + + float moveS = 0.0F; // is like the ClientSide moveStrafing moveForward + float moveF = 0.0F; + String key = "Nothing"; + + int precision = String.valueOf((int) Math.abs(posX > posZ ? posX : posX)).length(); + precision = 15 - precision; + double preD = 1.2 * Math.pow(10, -Math.max(3, precision - 5)); + + if (Math.abs(Math.abs(mx) + Math.abs(mz)) > preD) { + direction = (int) new BigDecimal(motionYaw).setScale(1, RoundingMode.HALF_UP).doubleValue(); + + if (direction == 1) { + moveF = 1F; + moveS = -1F; + key = "W + D"; + } else if (direction == 2) { + moveS = -1F; + key = "D"; + } else if (direction == 3) { + moveF = -1F; + moveS = -1F; + key = "S + D"; + } else if (direction == 4) { + moveF = -1F; + key = "S"; + } else if (direction == 5) { + moveF = -1F; + moveS = 1F; + key = "S + A"; + } else if (direction == 6) { + moveS = 1F; + key = "A"; + } else if (direction == 7) { + moveF = 1F; + moveS = 1F; + key = "W + A"; + } else if (direction == 8) { + moveF = 1F; + key = "W"; + } else if (direction == 0) { + moveF = 1F; + key = "W"; + } + } + + moveF *= 0.9800000190734863F; + moveS *= 0.9800000190734863F; + + moveStrafing = moveS; + moveForward = moveF; + this.key = key; + } + + private void calcKey(double mx, double mz) { + float motionYaw = getMotionYaw(mx, mz); + + int direction = 6; + + //MiscUtils.testMessage("yaw= " + motionYaw + " mx=" + mx + " mz=" + mz); + + motionYaw /= 45.0F; // converts the rotationYaw of the Motion to integers to get keys + + float moveS = 0.0F; // is like the ClientSide moveStrafing moveForward + float moveF = 0.0F; + String key = "Nothing"; + + int precision = String.valueOf((int) Math.abs(posX > posZ ? posX : posX)).length(); + precision = 15 - precision; + double preD = 1.2 * Math.pow(10, -Math.max(3, precision - 5)); + + if (Math.abs(Math.abs(mx) + Math.abs(mz)) > preD) { + direction = (int) new BigDecimal(motionYaw).setScale(1, RoundingMode.HALF_UP).doubleValue(); + + if (direction == 1) { + moveF = 1F; + moveS = -1F; + key = "W + D"; + } else if (direction == 2) { + moveS = -1F; + key = "D"; + } else if (direction == 3) { + moveF = -1F; + moveS = -1F; + key = "S + D"; + } else if (direction == 4) { + moveF = -1F; + key = "S"; + } else if (direction == 5) { + moveF = -1F; + moveS = 1F; + key = "S + A"; + } else if (direction == 6) { + moveS = 1F; + key = "A"; + } else if (direction == 7) { + moveF = 1F; + moveS = 1F; + key = "W + A"; + } else if (direction == 8) { + moveF = 1F; + key = "W"; + } else if (direction == 0) { + moveF = 1F; + key = "W"; + } + } + + moveF *= 0.9800000190734863F; + moveS *= 0.9800000190734863F; + + moveStrafing = moveS; + moveForward = moveF; + this.key = key; + + /*if(data.getPlayer().getName().equals("Dogeritoz")) { + Bukkit.broadcastMessage("key=" + key + " mx=" + MathUtils.round(mx, 6) + + " mz=" + MathUtils.round(mz, 6) + " myaw=" + MathUtils.round(motionYaw, 4) + + " velocity=" + velocity + " vx=" + data.playerInfo.velocityX + " vz=" + data.playerInfo.velocityZ); + }*/ + } + + private void calc(boolean checkCollisions) { + flag = true; + int precision = String.valueOf((int) Math.abs(posX > posZ ? posX : posX)).length(); + precision = 10 - precision - (isBelowSpecial ? 4 : 0); + double preD = 1.2 * Math.pow(10, -Math.max(3, precision - 5)); // the motion deviates further and further from the coordinates 0 0 0. this value fix this + +// if (openInv) { // i don't have an Event for it +// moveF = 0.0F; +// moveS = 0.0F; +// key = "NIX"; +// } + + // 1337 is an value to see that nothing's changed + String diffString = "-1337"; + double diff = -1337; + double closestdiff = 1337; + + int loops = 0; // how many tries the check needed to calculate the right motion (if i use for + // loops) + + double flagJump = -1; + found: for (int fastLoop = 2; fastLoop > 0; fastLoop--) { // if the Player changes the optifine fastmath + // function + fastMath = (fastLoop == 2) == fMath; + for (int blockLoop = 2; blockLoop > 0; blockLoop--) { // if the Player blocks server side but not client + // side (minecraft glitch) + boolean blocking2 = (blockLoop == 1) != useSword; + if (data.playerInfo.usingItem) + blocking2 = true; + + loops++; + + float moveStrafing = this.moveStrafing; + float moveForward = this.moveForward; + + if (sneak) { + moveForward *= 0.3; + moveStrafing *= 0.3; + } + +// if (openInv) { +// if (sprint) +// return; +// if (sneak) +// return; +// } + + if (blocking2) { // if the player blocks with a sword + moveForward *= 0.2F; + moveStrafing *= 0.2F; + } + + float jumpMovementFactor = 0.02F; + if (lastSprint) { + jumpMovementFactor = 0.025999999F; + } + + float var5; + float var3 = 0.91f; + + if(lastOnGround) { + var3*= data.blockInfo.fromFriction; + } + + aiMoveSpeed = data.getPlayer().getWalkSpeed() / 2f; + + if (sprint) { + aiMoveSpeed+= aiMoveSpeed * 0.30000001192092896D; + } + + if(data.potionProcessor.hasPotionEffect(PotionEffectType.SPEED)) { + aiMoveSpeed += (PlayerUtils.getPotionEffectLevel(data.getPlayer(), PotionEffectType.SPEED) * (0.20000000298023224D)) * aiMoveSpeed; + } + if(data.potionProcessor.hasPotionEffect(PotionEffectType.SLOW)) { + aiMoveSpeed += (PlayerUtils.getPotionEffectLevel(data.getPlayer(), PotionEffectType.SLOW) * (-0.15000000596046448D)) * aiMoveSpeed; + } + + float var4 = 0.16277136F / (var3 * var3 * var3); + + if (lastOnGround) { + var5 = (float)(aiMoveSpeed * var4); + } else { + var5 = jumpMovementFactor; + } + + double motionX = lmotionX, motionZ = lmotionZ; + + float var14 = moveStrafing * moveStrafing + moveForward * moveForward; + if (var14 >= 1.0E-4F) { + var14 = sqrt_double(var14); + if (var14 < 1.0F) + var14 = 1.0F; + var14 = var5 / var14; + moveStrafing *= var14; + moveForward *= var14; + + final float var15 = sin(yaw * (float) Math.PI / 180.0F); // cos, sin = Math function of optifine + final float var16 = cos(yaw * (float) Math.PI / 180.0F); + motionX += (moveStrafing * var16 - moveForward * var15); + motionZ += (moveForward * var16 + moveStrafing * var15); + } + + /*if(checkCollisions) { + if(data.playerInfo.onLadder) { + float f6 = 0.15F; + motionX = MathHelper.clamp_double(motionX, -f6, f6); + motionZ = MathHelper.clamp_double(motionZ, -f6, f6); + + motionY = Math.max(-0.15, motionY); + + if (data.playerInfo.sneaking) motionY = Math.max(0, motionY); + } + + double d3 = motionX; + double d4 = motionY; + double d5 = motionZ; + + //moveEntity + + SimpleCollisionBox box = data.box.copy(); + + List boxes = new ArrayList<>(); + + blockCollisions(data.blockInfo.handler.getBlocks(), + data.box.copy().addCoord(motionX, motionY, motionZ)).stream() + .map(block -> BlockData.getData(block.getType()) + .getBox(block, ProtocolVersion.getGameVersion())) + .forEach(blockBox -> blockBox.downCast(boxes)); + + //System.out.println("size=" + boxes.size()); + + double x = 0, y = 0, z = 0; + for (SimpleCollisionBox axisalignedbb1 : boxes) { + motionY = axisalignedbb1.copy().calculateYOffset(box, data.playerInfo.deltaY); + } + + //box.offset(0.0D, y, 0.0D); + + for (SimpleCollisionBox axisalignedbb2 : boxes) { + motionX = axisalignedbb2.copy().calculateXOffset(box, motionX); + } + + //box.offset(x,0,0); + + for (SimpleCollisionBox axisalignedbb13 : boxes) { + motionZ = axisalignedbb13.copy().calculateZOffset(box, motionZ); + } + + //box.offset(0,0, z); + + //Bukkit.broadcastMessage(x + ", " + y + ", " + z); + + collidedHorizontally= d3 != motionX || d5 != motionZ; + collidedVertically = d4 != motionY; + }*/ + + + predX = motionX; + predZ = motionZ; + + final double diffX = rmotionX - motionX; // difference between the motion from the player and the + // calculated motion + final double diffZ = rmotionZ - motionZ; + + diff = Math.hypot(diffX, diffZ); + + if(Double.isNaN(diff) || Double.isInfinite(diff)) return; + + // if the motion isn't correct this value can get out in flags + diff = new BigDecimal(diff).setScale(precision + 2, RoundingMode.HALF_UP).doubleValue(); + diffString = new BigDecimal(diff).setScale(precision + 2, RoundingMode.HALF_UP).toPlainString(); + + if (diff < preD) { // if the diff is small enough + flag = false; + //MiscUtils.testMessage(Color.Green + "(" + rmotionX + ", " + motionX + "); (" + rmotionZ + ", " + motionZ + ")"); + + //MiscUtils.testMessage(Color.Green + diffString + " loops " + loops + " key: " + key + " sneak=" + sneak + " move=" + moveForward + " ai=" + aiMoveSpeed); + fMath = fastMath; // saves the fastmath option if the player changed it + break found; + } + //MiscUtils.testMessage(Color.Red + "(" + rmotionX + ", " + motionX + "); (" + rmotionZ + ", " + motionZ + ")"); + //MiscUtils.testMessage(Color.Red + diffString + " loops " + loops + " key: " + key + " sneak=" + sneak + // + " move=" + moveForward + " ai=" + aiMoveSpeed + " shit=" + Atlas.getInstance() + // .getBlockBoxManager().getBlockBox().getMovementFactor(data.getPlayer())); + + if (diff < closestdiff) { + closestdiff = diff; + } + } + } + } + + private boolean checkConditions(final boolean lastTickSprint) { + if (lPosX == 0 && lPosY == 0 && lPosZ == 0) { // the position is 0 when a moveFlying or look packet was send + return false; + } + + if (lastOnGround && !onGround && lastTickSprint) // if the Player jumps + return false; + + if (rmotionX == 0 && rmotionZ == 0 && onGround) + return false; + + if (MathUtils.hypot(lmotionX, lmotionZ) > 11) // if something gots wrong this can be helpfull + return false; + if (MathUtils.hypot(posX - lPosX, posZ - lPosZ) > 10) + return false; + + return !fly + && !data.playerInfo.creative; + } + + private static final float[] SIN_TABLE_FAST = new float[4096]; + private static final float[] SIN_TABLE = new float[65536]; + + public float sin(float p_76126_0_) { + return fastMath ? SIN_TABLE_FAST[(int) (p_76126_0_ * 651.8986F) & 4095] + : SIN_TABLE[(int) (p_76126_0_ * 10430.378F) & 65535]; + } + + public float cos(float p_76134_0_) { + return fastMath ? SIN_TABLE_FAST[(int) ((p_76134_0_ + ((float) Math.PI / 2F)) * 651.8986F) & 4095] + : SIN_TABLE[(int) (p_76134_0_ * 10430.378F + 16384.0F) & 65535]; + } + + static { + int i; + + for (i = 0; i < 65536; ++i) { + SIN_TABLE[i] = (float) Math.sin((double) i * Math.PI * 2.0D / 65536.0D); + } + + for (i = 0; i < 4096; ++i) { + SIN_TABLE_FAST[i] = (float) Math.sin(((float) i + 0.5F) / 4096.0F * ((float) Math.PI * 2F)); + } + + for (i = 0; i < 360; i += 90) { + SIN_TABLE_FAST[(int) ((float) i * 11.377778F) & 4095] = (float) Math + .sin((float) i * 0.017453292F); + } + } + + // functions of minecraft MathHelper.java + private static float sqrt_float(float p_76129_0_) { + return (float) Math.sqrt(p_76129_0_); + } + + public static float sqrt_double(double p_76133_0_) { + return (float) Math.sqrt(p_76133_0_); + } + + private static List blockCollisions(List blocks, SimpleCollisionBox box) { + return blocks.stream() + .filter(b -> Helper.isCollided(box, + BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()))) + .collect(Collectors.toCollection(LinkedList::new)); + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/data/classes/WrappedCheck.java b/Impl/src/main/java/dev/brighten/anticheat/data/classes/WrappedCheck.java new file mode 100644 index 000000000..590b9c6af --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/data/classes/WrappedCheck.java @@ -0,0 +1,45 @@ +package dev.brighten.anticheat.data.classes; + +import cc.funkemunky.api.reflections.types.WrappedMethod; +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.check.api.Event; +import dev.brighten.anticheat.check.api.Packet; + +import java.util.List; + +public class WrappedCheck { + public String checkName; + public WrappedMethod method; + public Check check; + public boolean isBoolean, oneParam, isTick, isTimeStamp, isPacket, isEvent; + private boolean canRunWithVersion, didVersionCheck; + public List> parameters; + + public WrappedCheck(Check check, WrappedMethod method) { + this.check = check; + this.checkName = check.getName(); + this.method = method; + isBoolean = method.getMethod().getReturnType().equals(boolean.class); + parameters = method.getParameters(); + oneParam = parameters.size() == 1; + + if(!oneParam) { + isTick = method.getMethod().getParameterTypes()[1] == int.class; + isTimeStamp = !isTick; + } + isPacket = method.getMethod().isAnnotationPresent(Packet.class); + isEvent = method.getMethod().isAnnotationPresent(Event.class); + } + + public boolean isCompatible() { + if(didVersionCheck) return canRunWithVersion; + + if(!check.data.playerVersion.equals(ProtocolVersion.UNKNOWN)) { + didVersionCheck = true; + return this.canRunWithVersion = check.data.playerVersion.isOrBelow(check.maxVersion) + && check.data.playerVersion.isOrAbove(check.minVersion); + } + return true; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/handlers/ThreadHandler.java b/Impl/src/main/java/dev/brighten/anticheat/handlers/ThreadHandler.java new file mode 100644 index 000000000..9e905217c --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/handlers/ThreadHandler.java @@ -0,0 +1,22 @@ +package dev.brighten.anticheat.handlers; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ThreadHandler { + public List threads = new ArrayList<>(); + public int players; + + public ThreadHandler() { + threads.add(Executors.newSingleThreadExecutor()); + } + + public ExecutorService newThread() { + if(++players % 10 == 0) { + threads.add(Executors.newSingleThreadExecutor()); + } + return threads.get(threads.size() - 1); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/listeners/AtlasCheckListeners.java b/Impl/src/main/java/dev/brighten/anticheat/listeners/AtlasCheckListeners.java new file mode 100644 index 000000000..b6ddf4c30 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/listeners/AtlasCheckListeners.java @@ -0,0 +1,34 @@ +package dev.brighten.anticheat.listeners; + +import cc.funkemunky.api.events.AtlasListener; +import cc.funkemunky.api.events.Listen; +import cc.funkemunky.api.events.ListenerPriority; +import cc.funkemunky.api.events.impl.PacketReceiveEvent; +import cc.funkemunky.api.events.impl.PacketSendEvent; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; + +@Init +public class AtlasCheckListeners implements AtlasListener { + + @Listen(priority = ListenerPriority.NORMAL) + public void onPacket(PacketReceiveEvent event) { + if(event.getPlayer() == null) return; + + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null) { + data.checkManager.runEvent(event); + } + } + + @Listen(priority = ListenerPriority.NORMAL) + public void onPacket(PacketSendEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null) { + data.checkManager.runEvent(event); + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/listeners/BukkitCheckListeners.java b/Impl/src/main/java/dev/brighten/anticheat/listeners/BukkitCheckListeners.java new file mode 100644 index 000000000..05de704b6 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/listeners/BukkitCheckListeners.java @@ -0,0 +1,54 @@ +package dev.brighten.anticheat.listeners; + +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.player.PlayerEditBookEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; + +@Init +public class BukkitCheckListeners implements Listener { + + @EventHandler(priority = EventPriority.LOWEST) + public void onInteract(PlayerInteractEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null) { + data.playerInfo.breakingBlock = event.getAction().equals(Action.LEFT_CLICK_BLOCK); + data.checkManager.runEvent(event); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onMove(PlayerMoveEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null) { + data.checkManager.runEvent(event); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onBookEdit(PlayerEditBookEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null) { + data.checkManager.runEvent(event); + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onEvent(SignChangeEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null) { + data.checkManager.runEvent(event); + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/listeners/BungeeListener.java b/Impl/src/main/java/dev/brighten/anticheat/listeners/BungeeListener.java new file mode 100644 index 000000000..1b5810813 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/listeners/BungeeListener.java @@ -0,0 +1,64 @@ +package dev.brighten.anticheat.listeners; + +import cc.funkemunky.api.bungee.events.BungeeReceiveEvent; +import cc.funkemunky.api.events.AtlasListener; +import cc.funkemunky.api.events.Listen; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.MathUtils; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Config; +import dev.brighten.anticheat.utils.TickTimer; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Init +public class BungeeListener implements AtlasListener, Listener { + + //uuid, info, checkName, vl + public Map lastAlertsMap = new HashMap<>(); + + @Listen + public void onBungee(BungeeReceiveEvent event) { + if(!Config.bungeeAlerts || event.objects.length != 4) return; + UUID uuid = (UUID)event.objects[0]; + String checkName = (String)event.objects[1]; + CheckEntry entry = new CheckEntry(uuid, checkName); + TickTimer lastAlert; + if(!lastAlertsMap.containsKey(entry)) { + lastAlert = new TickTimer(MathUtils.millisToTicks(Config.alertsDelay)); + lastAlertsMap.put(entry, lastAlert); + } else { + lastAlert = lastAlertsMap.get(entry); + } + + if(lastAlert.hasPassed(MathUtils.millisToTicks(Config.alertsDelay))) { + float vl = (float)event.objects[2]; + String info = (String)event.objects[3]; + + OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); + + String alert = Color.translate("&8[&6K&8] &f" + player.getName() + + " &7flagged &f" + checkName + + " &8(&e" + info + "&8) &8[&c" + vl + "&8]"); + + Kauri.INSTANCE.dataManager.hasAlerts.forEach(data -> data.getPlayer().sendMessage(alert)); + lastAlert.reset(); + } + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + lastAlertsMap.keySet() + .stream() + .filter(entry -> entry.uuid.equals(event.getPlayer().getUniqueId())) + .forEach(lastAlertsMap::remove); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/listeners/CancelListeners.java b/Impl/src/main/java/dev/brighten/anticheat/listeners/CancelListeners.java new file mode 100644 index 000000000..53488974c --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/listeners/CancelListeners.java @@ -0,0 +1,129 @@ +package dev.brighten.anticheat.listeners; + +import cc.funkemunky.api.utils.BlockUtils; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.CancelType; +import dev.brighten.anticheat.data.ObjectData; +import lombok.val; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityInteractEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerMoveEvent; + +@Init +public class CancelListeners implements Listener { + + /** Cancels for MOVEMENT **/ + @EventHandler(priority = EventPriority.MONITOR) + public void onEvent(PlayerMoveEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null && data.typesToCancel.size() > 0) { + for (CancelType cancelType : data.typesToCancel) { + if(!cancelType.equals(CancelType.MOVEMENT)) continue; + + val ground = BlockUtils.findGround(event.getFrom().getWorld(), event.getFrom()).clone(); + + ground.setYaw(event.getFrom().getYaw()); + ground.setPitch(event.getFrom().getPitch()); + + event.getPlayer().teleport(ground.add(0,0.01,0)); + data.typesToCancel.remove(cancelType); + return; + } + } + } + + /** Cancels for ATTACK **/ + @EventHandler(priority = EventPriority.MONITOR) + public void onEvent(EntityDamageByEntityEvent event) { + if(!(event.getDamager() instanceof Player)) return; + + ObjectData data = Kauri.INSTANCE.dataManager.getData((Player)event.getDamager()); + + if(data != null && data.typesToCancel.size() > 0) { + for (CancelType cancelType : data.typesToCancel) { + if(!cancelType.equals(CancelType.ATTACK)) continue; + + event.setCancelled(true); + data.typesToCancel.remove(cancelType); + break; + } + } + } + + /** Cancels for PLACE **/ + @EventHandler(priority = EventPriority.MONITOR) + public void onEvent(BlockPlaceEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null && data.typesToCancel.size() > 0) { + for (CancelType cancelType : data.typesToCancel) { + if(!cancelType.equals(CancelType.PLACE)) continue; + + event.setCancelled(true); + data.typesToCancel.remove(cancelType); + break; + } + } + } + + /** Cancels for BREAK **/ + @EventHandler(priority = EventPriority.MONITOR) + public void onEvent(BlockBreakEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null && data.typesToCancel.size() > 0) { + for (CancelType cancelType : data.typesToCancel) { + if(!cancelType.equals(CancelType.BREAK)) continue; + + event.setCancelled(true); + data.typesToCancel.remove(cancelType); + break; + } + } + } + + /** Cancels for Interact **/ + //Done for block and sword interact stuff. + @EventHandler(priority = EventPriority.MONITOR) + public void onEvent(PlayerInteractEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data != null && data.typesToCancel.size() > 0) { + for (CancelType cancelType : data.typesToCancel) { + if(!cancelType.equals(CancelType.INTERACT)) continue; + + event.setCancelled(true); + data.typesToCancel.remove(cancelType); + break; + } + } + } + + /** Cancels for Interact **/ + //Done for interacting with entities like NPC or players. + @EventHandler(priority = EventPriority.MONITOR) + public void onEvent(EntityInteractEvent event) { + if(!(event.getEntity() instanceof Player)) return; + + ObjectData data = Kauri.INSTANCE.dataManager.getData((Player)event.getEntity()); + + if(data != null && data.typesToCancel.size() > 0) { + for (CancelType cancelType : data.typesToCancel) { + if(!cancelType.equals(CancelType.INTERACT)) continue; + + event.setCancelled(true); + data.typesToCancel.remove(cancelType); + break; + } + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/listeners/CheckEntry.java b/Impl/src/main/java/dev/brighten/anticheat/listeners/CheckEntry.java new file mode 100644 index 000000000..580bb31c7 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/listeners/CheckEntry.java @@ -0,0 +1,11 @@ +package dev.brighten.anticheat.listeners; + +import lombok.AllArgsConstructor; + +import java.util.UUID; + +@AllArgsConstructor +public class CheckEntry { + public UUID uuid; + public String checkName; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/listeners/PacketListener.java b/Impl/src/main/java/dev/brighten/anticheat/listeners/PacketListener.java new file mode 100644 index 000000000..626673b63 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/listeners/PacketListener.java @@ -0,0 +1,113 @@ +package dev.brighten.anticheat.listeners; + +import cc.funkemunky.api.events.AtlasListener; +import cc.funkemunky.api.events.Listen; +import cc.funkemunky.api.events.ListenerPriority; +import cc.funkemunky.api.events.impl.PacketReceiveEvent; +import cc.funkemunky.api.events.impl.PacketSendEvent; +import cc.funkemunky.api.tinyprotocol.api.Packet; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInUseEntityPacket; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutRelativePosition; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutTransaction; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutVelocityPacket; +import cc.funkemunky.api.utils.ConfigSetting; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import lombok.val; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Init +public class PacketListener implements AtlasListener { + + @ConfigSetting(path = "performance", name = "expansiveThreading") + public static boolean expansiveThreading = true; + + public static ExecutorService service = Executors.newSingleThreadScheduledExecutor(); + + @Listen(ignoreCancelled = true, priority = ListenerPriority.LOW) + public void onEvent(PacketReceiveEvent event) { + if(event.getPlayer() == null) return; + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data == null || data.checkManager == null) return; + + data.getThread().execute(() -> + Kauri.INSTANCE.packetProcessor.processClient(event, + data, event.getPacket(), event.getType(), event.getTimeStamp())); + + switch(event.getType()) { + case Packet.Client.USE_ENTITY: { + val packet = new WrappedInUseEntityPacket(event.getPacket(), event.getPlayer()); + + if(data.checkManager.runPacketCancellable(packet, event.getTimeStamp())) { + event.setCancelled(true); + } + break; + } + case Packet.Client.FLYING: + case Packet.Client.POSITION: + case Packet.Client.POSITION_LOOK: + case Packet.Client.LOOK: { + val packet = new WrappedInFlyingPacket(event.getPacket(), event.getPlayer()); + + if(data.checkManager.runPacketCancellable(packet, event.getTimeStamp())) { + event.setCancelled(true); + } + break; + } + } + } + + @Listen(ignoreCancelled = true,priority = ListenerPriority.LOW) + public void onEvent(PacketSendEvent event) { + if(event.isCancelled() || event.getPlayer() == null + || !Kauri.INSTANCE.enabled || event.getPacket() == null) return; + + if(!Kauri.INSTANCE.dataManager.dataMap.containsKey(event.getPlayer().getUniqueId())) + return; + + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data == null || data.checkManager == null) return; + + data.getThread().execute(() -> Kauri.INSTANCE.packetProcessor.processServer(event, + data, event.getPacket(), event.getType(), event.getTimeStamp())); + + switch(event.getType()) { + case Packet.Server.REL_LOOK: + case Packet.Server.REL_POSITION: + case Packet.Server.REL_POSITION_LOOK: + case Packet.Server.ENTITY: + case Packet.Server.LEGACY_REL_LOOK: + case Packet.Server.LEGACY_REL_POSITION: + case Packet.Server.LEGACY_REL_POSITION_LOOK: { + val packet = new WrappedOutRelativePosition(event.getPacket(), event.getPlayer()); + + if(data.checkManager.runPacketCancellable(packet, event.getTimeStamp())) { + event.setCancelled(true); + } + break; + } + case Packet.Server.TRANSACTION: { + val packet = new WrappedOutTransaction(event.getPacket(), event.getPlayer()); + + if(data.checkManager.runPacketCancellable(packet, event.getTimeStamp())) { + event.setCancelled(true); + } + break; + } + case Packet.Server.ENTITY_VELOCITY: { + val packet = new WrappedOutVelocityPacket(event.getPacket(), event.getPlayer()); + + if(data.checkManager.runPacketCancellable(packet, event.getTimeStamp())) { + event.setCancelled(true); + } + break; + } + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/listeners/generalChecks/BukkitListener.java b/Impl/src/main/java/dev/brighten/anticheat/listeners/generalChecks/BukkitListener.java new file mode 100644 index 000000000..d0bbbb552 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/listeners/generalChecks/BukkitListener.java @@ -0,0 +1,131 @@ +package dev.brighten.anticheat.listeners.generalChecks; + +import cc.funkemunky.api.reflections.impl.MinecraftReflection; +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.packet.types.enums.WrappedEnumParticle; +import cc.funkemunky.api.utils.*; +import cc.funkemunky.api.utils.world.BlockData; +import cc.funkemunky.api.utils.world.CollisionBox; +import cc.funkemunky.api.utils.world.types.SimpleCollisionBox; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import lombok.val; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Init +public class BukkitListener implements Listener { + + public static ItemStack MAGIC_WAND = MiscUtils.createItem(XMaterial.BLAZE_ROD.parseMaterial(), + 1, "&6Magic Wand"); + + @EventHandler(priority = EventPriority.HIGHEST) + public void onJoin(PlayerJoinEvent event) { + Kauri.INSTANCE.dataManager.dataMap.remove(event.getPlayer().getUniqueId()); + Kauri.INSTANCE.executor.execute(() -> Kauri.INSTANCE.dataManager.createData(event.getPlayer())); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onLeave(PlayerQuitEvent event) { + //Removing if the player has debug access so there aren't any null objects left to cause problems later. + if(event.getPlayer().hasPermission("kauri.debug")) + ObjectData.debugBoxes(false, event.getPlayer()); + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + if(data != null) data.onLogout(); + Kauri.INSTANCE.dataManager.dataMap.remove(event.getPlayer().getUniqueId()); + } + + @EventHandler + public void onInteract(PlayerInteractEvent event) { + if(event.getClickedBlock() == null || !event.getAction().equals(Action.RIGHT_CLICK_BLOCK)) return; + + if(event.getItem() != null && event.getItem().isSimilar(MAGIC_WAND)) { + BlockData data = BlockData.getData(event.getClickedBlock().getType()); + + CollisionBox box = data.getBox(event.getClickedBlock(), ProtocolVersion.getGameVersion()); + + Bukkit.dispatchCommand(event.getPlayer(), "kauri block " + + event.getClickedBlock().getType().name()); + + List downcasted = new ArrayList<>(); + + box.downCast(downcasted); + + for (SimpleCollisionBox sbox : downcasted) { + val max = sbox.max().subtract(event.getClickedBlock().getLocation().toVector()); + val min = sbox.min().subtract(event.getClickedBlock().getLocation().toVector()); + + Vector subbed = max.subtract(min); + + event.getPlayer().sendMessage("x=" + subbed.getX() + " y=" + subbed.getY() + " z=" + subbed.getZ()); + } + + box.draw(WrappedEnumParticle.FLAME, Collections.singleton(event.getPlayer())); + event.setCancelled(true); + } + } + + @EventHandler + public void onEntityInteract(PlayerInteractEntityEvent event) { + if(event.getPlayer().getItemInHand() == null + || !event.getPlayer().getItemInHand().isSimilar(MAGIC_WAND)) return; + if(MiscUtils.entityDimensions.containsKey(event.getRightClicked().getType())) { + Vector dimension = MiscUtils.entityDimensions.get(event.getRightClicked().getType()); + + SimpleCollisionBox box = new SimpleCollisionBox(event.getRightClicked().getLocation().toVector(), event.getRightClicked().getLocation().toVector()) + .expand(dimension.getX(), 0, dimension.getZ()) + .expandMax(0, dimension.getY(), 0); + + box.draw(WrappedEnumParticle.FLAME, Collections.singleton(event.getPlayer())); + event.getPlayer().sendMessage(Color.Gold + Color.Bold + + event.getRightClicked().getType() + ": " + Color.White); + event.getPlayer().sendMessage(boxToString(box)); + } else { + SimpleCollisionBox box = MinecraftReflection + .fromAABB(ReflectionsUtil + .getBoundingBox(event.getRightClicked())) + .toCollisionBox(); + box.draw(WrappedEnumParticle.FLAME, Collections.singleton(event.getPlayer())); + event.getPlayer().sendMessage(Color.Gold + Color.Bold + + event.getRightClicked().getType() + ": " + Color.White); + event.getPlayer().sendMessage(boxToString(box)); + } + event.setCancelled(true); + } + + private static String vectorString = "{%1$.2f, %2$.2f, %3$.2f}"; + private static String boxToString(CollisionBox box) { + if(box instanceof SimpleCollisionBox) { + SimpleCollisionBox sbox = (SimpleCollisionBox) box; + return "SimpleCollisionBox[" + vectorToString(sbox.toBoundingBox().getMinimum()) + + ", " + vectorToString(sbox.toBoundingBox().getMaximum()) + "]"; + } else { + List downCasted = new ArrayList<>(); + + box.downCast(downCasted); + + return "ComplexBox[" + downCasted.stream() + .map(sbox -> "SimpleCollisionBox[" + vectorToString(sbox.toBoundingBox().getMinimum()) + + ", " + vectorToString(sbox.toBoundingBox().getMaximum()) + "]") + .collect(Collectors.joining(", ")) + "]"; + } + } + + private static String vectorToString(Vector vector) { + return String.format(vectorString, vector.getX(), vector.getY(), vector.getZ()); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/listeners/generalChecks/ChatCheck.java b/Impl/src/main/java/dev/brighten/anticheat/listeners/generalChecks/ChatCheck.java new file mode 100644 index 000000000..6d45c577e --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/listeners/generalChecks/ChatCheck.java @@ -0,0 +1,61 @@ +package dev.brighten.anticheat.listeners.generalChecks; + +import cc.funkemunky.api.events.AtlasListener; +import cc.funkemunky.api.events.Listen; +import cc.funkemunky.api.events.ListenerPriority; +import cc.funkemunky.api.events.impl.PacketReceiveEvent; +import cc.funkemunky.api.tinyprotocol.api.Packet; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInChatPacket; +import cc.funkemunky.api.utils.ConfigSetting; +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; + +import java.util.ArrayList; +import java.util.List; + +@Init +public class ChatCheck implements AtlasListener { + + @ConfigSetting(path = "general.illegalCharacters", name = "enabled") + private static boolean enabled = true; + + @ConfigSetting(path = "general.illegalCharacters", name = "whitelisted") + private static List whitelistedCharacters = new ArrayList<>(); + + @ConfigSetting(path = "general.illegalCharacters", name = "allowExtended") + private static boolean allowExtendedCharacters = false; + + @ConfigSetting(path = "general.illegalCharacters", name = "messageLengthMax") + private static int lengthMax = 250; + + @Listen(priority = ListenerPriority.LOW) + public void onPacketReceive(PacketReceiveEvent event) { + if(!enabled || event.getPacket() == null) return; + if(event.getType().equals(Packet.Client.CHAT)) { + WrappedInChatPacket packet = new WrappedInChatPacket(event.getPacket(), event.getPlayer()); + + if(packet.getMessage().length() <= 0) return; + + if(packet.getMessage().length() > lengthMax) { + event.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("msg-too-long", + "&8[&6K&8] &cYour chat message was cancelled because it was too long.")); + event.setCancelled(true); + return; + } + + int min = 0; + int max = allowExtendedCharacters ? 591 : 255; + + for (char c : packet.getMessage().toCharArray()) { + if((int)c > min && (int)c < max || whitelistedCharacters.contains(c)) continue; + + event.getPlayer().sendMessage(Kauri.INSTANCE.msgHandler.getLanguage() + .msg("illegal-chars", + "&8[&6K&8] &cYou are not allowed to use character \"" + c + "\".")); + event.setCancelled(true); + break; + } + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/listeners/generalChecks/LagCheck.java b/Impl/src/main/java/dev/brighten/anticheat/listeners/generalChecks/LagCheck.java new file mode 100644 index 000000000..718011bd9 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/listeners/generalChecks/LagCheck.java @@ -0,0 +1,36 @@ +package dev.brighten.anticheat.listeners.generalChecks; + +import cc.funkemunky.api.events.AtlasEvent; +import cc.funkemunky.api.events.Listen; +import cc.funkemunky.api.events.impl.TickEvent; +import cc.funkemunky.api.utils.ConfigSetting; +import cc.funkemunky.api.utils.RunUtils; +import dev.brighten.anticheat.Kauri; + +public class LagCheck extends AtlasEvent { + + @ConfigSetting(path = "general", name = "lagCheck") + private static boolean enabled = true; + + @Listen + public void onEvent(TickEvent event) { + if(!enabled || !Kauri.INSTANCE.enabled) return; + + //We do this to ensure no one is abusing fake lag attempts. + long timeStamp = System.currentTimeMillis(); + Kauri.INSTANCE.dataManager.dataMap.values() + .parallelStream() + .forEach(data -> { + if(timeStamp - data.lagInfo.lastClientTrans > 10000L + && Kauri.INSTANCE.keepaliveProcessor.tick - data.playerInfo.to.timeStamp < 2L) { + data.lagTicks++; + + RunUtils.task(() -> + data.getPlayer().kickPlayer("Kicked for timing out. (Extreme lag?)")); + } else { + data.noLagTicks++; + data.lagTicks = 0; + } + }); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/LoggerManager.java b/Impl/src/main/java/dev/brighten/anticheat/logs/LoggerManager.java new file mode 100644 index 000000000..eae49df03 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/LoggerManager.java @@ -0,0 +1,80 @@ +package dev.brighten.anticheat.logs; + +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.logs.data.DataStorage; +import dev.brighten.anticheat.logs.data.config.MongoConfig; +import dev.brighten.anticheat.logs.data.config.MySQLConfig; +import dev.brighten.anticheat.logs.data.impl.FlatfileStorage; +import dev.brighten.anticheat.logs.data.impl.MongoStorage; +import dev.brighten.anticheat.logs.data.impl.MySQLStorage; +import dev.brighten.anticheat.logs.objects.Log; +import dev.brighten.anticheat.logs.objects.Punishment; + +import java.util.*; + +public class LoggerManager { + + public DataStorage storage; + + public LoggerManager() { + if(MongoConfig.enabled) { + storage = new MongoStorage(); + } else if(MySQLConfig.enabled) { + storage = new MySQLStorage(); + } else storage = new FlatfileStorage(); + } + + public void addLog(ObjectData data, Check check, String info) { + Log log = new Log(data.uuid , check.name, info, check.vl, data.lagInfo.transPing, + System.currentTimeMillis(), Kauri.INSTANCE.getTps()); + + storage.addLog(log); + } + + public void addPunishment(ObjectData data, Check check) { + Punishment punishment = new Punishment(data.uuid, check.name, System.currentTimeMillis()); + + storage.addPunishment(punishment); + } + + public List getLogs(UUID uuid) { + return storage.getLogs(uuid, null, 0, Integer.MAX_VALUE, 0, System.currentTimeMillis()); + } + + public List getLogs(UUID uuid, Check check, int arrayMin, int arrayMax, long timeFrom, long timeTo) { + return storage.getLogs(uuid, check, arrayMin, arrayMax, timeFrom, timeTo); + } + + public void clearLogs(UUID uuid) { + storage.removeAll(uuid); + } + + public List getPunishments(UUID uuid) { + return storage.getPunishments(uuid, 0, Integer.MAX_VALUE, 0, System.currentTimeMillis()); + } + + public List getPunishments(UUID uuid, int arrayMin, int arrayMax, long timeFrom, long timeTo) { + return storage.getPunishments(uuid, arrayMin, arrayMax, timeFrom, timeTo); + } + + public Map> getLogsWithinTimeFrame(long timeFrame) { + long currentTime = System.currentTimeMillis(); + + Map> logs = new HashMap<>(); + + getLogs(null, null, 0, Integer.MAX_VALUE, + currentTime - timeFrame, currentTime + 100) + .forEach(log -> { + List logsList = logs.getOrDefault(log.uuid, new ArrayList<>()); + + logsList.add(log); + + logs.put(log.uuid, logsList); + }); + + return logs; + } + +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/DataStorage.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/DataStorage.java new file mode 100644 index 000000000..a8d166647 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/DataStorage.java @@ -0,0 +1,31 @@ +package dev.brighten.anticheat.logs.data; + +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.logs.objects.Log; +import dev.brighten.anticheat.logs.objects.Punishment; + +import java.util.List; +import java.util.UUID; + +public interface DataStorage { + + List getLogs(UUID uuid, Check check, int arrayMin, int arrayMax, long timeFrom, long timeTo); + + List getPunishments(UUID uuid, int arrayMin, int arrayMax, long timeFrom, long timeTo); + + List getHighestVL(UUID uuid, Check check, int limit, long timeFrom, long timeTo); + + void addLog(Log log); + + void removeAll(UUID uuid); + + void addPunishment(Punishment punishment); + + void cacheAPICall(UUID uuid, String name); + + UUID getUUIDFromName(String name); + + String getNameFromUUID(UUID uuid); + + void shutdown(); +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/config/MongoConfig.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/config/MongoConfig.java new file mode 100644 index 000000000..4387ec372 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/config/MongoConfig.java @@ -0,0 +1,31 @@ +package dev.brighten.anticheat.logs.data.config; + +import cc.funkemunky.api.utils.ConfigSetting; +import cc.funkemunky.api.utils.Init; + +@Init +public class MongoConfig { + @ConfigSetting(path = "database.mongo", name = "enabled") + public static boolean enabled = false; + + @ConfigSetting(path = "database.mongo", name = "username", hide = true) + public static String username = "root"; + + @ConfigSetting(path = "database.mongo", name = "password", hide = true) + public static String password = "password"; + + @ConfigSetting(path = "database.mongo", name = "requiresLoginDetails", hide = true) + public static boolean loginDetails = false; + + @ConfigSetting(path = "database.mongo", name = "database", hide = true) + public static String database = "Kauri"; + + @ConfigSetting(path = "database.mongo", name = "authDatabase", hide = true) + public static String authDatabase = "admin"; + + @ConfigSetting(path = "database.mongo", name = "ip", hide = true) + public static String ip = "127.0.0.1"; + + @ConfigSetting(path = "database.mongo", name = "port", hide = true) + public static int port = 27017; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/config/MySQLConfig.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/config/MySQLConfig.java new file mode 100644 index 000000000..9e6210d73 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/config/MySQLConfig.java @@ -0,0 +1,25 @@ +package dev.brighten.anticheat.logs.data.config; + +import cc.funkemunky.api.utils.ConfigSetting; +import cc.funkemunky.api.utils.Init; + +@Init +public class MySQLConfig { + @ConfigSetting(path = "database.mysql", name = "enabled") + public static boolean enabled = false; + + @ConfigSetting(path = "database.mysql", name = "username", hide = true) + public static String username = "root"; + + @ConfigSetting(path = "database.mysql", name = "database", hide = true) + public static String database = "Kauri"; + + @ConfigSetting(path = "database.mysql", name = "password", hide = true) + public static String password = "password"; + + @ConfigSetting(path = "database.mysql", name = "ip", hide = true) + public static String ip = "127.0.0.1"; + + @ConfigSetting(path = "database.mysql", name = "port", hide = true) + public static int port = 3306; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/impl/FlatfileStorage.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/impl/FlatfileStorage.java new file mode 100644 index 000000000..69d84f7b8 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/impl/FlatfileStorage.java @@ -0,0 +1,209 @@ +package dev.brighten.anticheat.logs.data.impl; + +import cc.funkemunky.api.utils.RunUtils; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.logs.data.DataStorage; +import dev.brighten.anticheat.logs.objects.Log; +import dev.brighten.anticheat.logs.objects.Punishment; +import dev.brighten.db.db.FlatfileDatabase; +import dev.brighten.db.db.StructureSet; +import dev.brighten.db.utils.MiscUtils; +import dev.brighten.db.utils.Pair; +import lombok.val; +import org.bukkit.scheduler.BukkitTask; + +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class FlatfileStorage implements DataStorage { + + private FlatfileDatabase database, nameCache; + + private List logs = new CopyOnWriteArrayList<>(); + private List punishments = new CopyOnWriteArrayList<>(); + private BukkitTask task; + + public FlatfileStorage() { + database = new FlatfileDatabase("logs"); + database.loadMappings(); + nameCache = new FlatfileDatabase("nameCache"); + nameCache.loadMappings(); + + task = RunUtils.taskTimerAsync(() -> { + if(logs.size() > 0) { + for (Log log : logs) { + StructureSet set = database.create(UUID.randomUUID().toString()); + + Arrays.asList(new Pair<>("type", "log"), + new Pair<>("uuid", log.uuid.toString()), + new Pair<>("checkName", log.checkName), + new Pair<>("vl", log.vl), + new Pair<>("info", log.info), + new Pair<>("ping", log.ping), + new Pair<>("timeStamp", log.timeStamp), + new Pair<>("tps", log.tps)).forEach(pair -> set.input(pair.key, pair.value)); + + set.save(database); + logs.remove(log); + } + } + if(punishments.size() > 0) { + for(Punishment punishment : punishments) { + StructureSet set = database.create(UUID.randomUUID().toString()); + + Arrays.asList( new Pair<>("type", "punishment"), + new Pair<>("uuid", punishment.uuid.toString()), + new Pair<>("checkName", punishment.checkName), + new Pair<>("timeStamp", punishment.timeStamp)) + .forEach(pair -> set.input(pair.key, pair.value)); + + set.save(database); + punishments.remove(punishment); + } + } + }, Kauri.INSTANCE, 120L, 40L); + } + + + @Override + public void shutdown() { + task.cancel(); + task = null; + logs.clear(); + punishments.clear(); + database.disconnect(); + database = null; + nameCache.disconnect(); + nameCache = null; + } + + @Override + public List getLogs(UUID uuid, Check check, int arrayMin, int arrayMax, long timeFrom, long timeTo) { + List sets = database.get(structSet -> + structSet.contains("type") && structSet.getObject("type").equals("log") + && (long)structSet.getObject("timeStamp") >= timeFrom + && (long)structSet.getObject("timeStamp") <= timeTo + && (check == null || structSet.getObject("checkName").equals(check.name)) + && structSet.contains("uuid") && structSet.getObject("uuid").equals(uuid.toString())).stream() + .skip(arrayMin).limit(arrayMax).collect(Collectors.toList()); + + List toReturn; + if(sets.size() == 0) toReturn = new ArrayList<>(); + else { + toReturn = sets.stream().map(set -> new Log( + uuid, + set.getObject("checkName"), + set.getObject("info"), + set.getObject("vl") instanceof Integer + ? (int)set.getObject("vl") + : (set.getObject("vl") instanceof Double + ? (float)(double)set.getObject("vl") : (float)set.getObject("vl")), + set.getObject("ping") instanceof Integer + ? (int)set.getObject("ping") : set.getObject("ping"), + (long)set.getObject("timeStamp"), + set.getObject("tps") instanceof Integer + ? (int)set.getObject("tps") + : set.getObject("tps") instanceof Double + ? (double)set.getObject("tps") : (float)set.getObject("tps"))) + .collect(Collectors.toList()); + } + return toReturn; + } + + @Override + public List getPunishments(UUID uuid, int arrayMin, int arrayMax, long timeFrom, long timeTo) { + List structureSets = database + .get(set -> set.contains("uuid") && set.getObject("uuid").equals(uuid.toString()) + && (long)set.getObject("timeStamp") >= timeFrom + && (long)set.getObject("timeStamp") <= timeTo + && set.contains("type") && set.getObject("type").equals("punishment")).stream() + .skip(arrayMin).limit(arrayMax).collect(Collectors.toList()); + + return structureSets.stream() + .map(set -> + new Punishment( + uuid, + set.getObject("checkName"), + set.getObject("timeStamp"))) + .collect(Collectors.toList()); + } + + @Override + public List getHighestVL(UUID uuid, Check check, int limit, long timeFrom, long timeTo) { + List logs = getLogs(uuid, check, 0, limit, timeFrom, timeTo); + + Map logsMax = new HashMap<>(); + + logs.forEach(log -> { + if(logsMax.containsKey(log.checkName)) { + Log toCheck = logsMax.get(log.checkName); + + if(toCheck.vl < log.vl) { + logsMax.put(log.checkName, log); + } + } else logsMax.put(log.checkName, log); + }); + return new ArrayList<>(logsMax.values()); + } + + @Override + public void addLog(Log log) { + logs.add(log); + } + + @Override + public void removeAll(UUID uuid) { + database.remove(set -> set.getObject("uuid").equals(uuid.toString())); + } + + @Override + public void addPunishment(Punishment punishment) { + punishments.add(punishment); + } + + @Override + public void cacheAPICall(UUID uuid, String name) { + Kauri.INSTANCE.loggingThread.execute(() -> { + nameCache.remove(set -> set.getObject("uuid").equals(uuid.toString())); + val set = nameCache.create(MiscUtils.randomString(30, true)); + + set.input("uuid", uuid.toString()); + set.input("name", name); + set.input("timestamp", System.currentTimeMillis()); + }); + } + + @Override + public UUID getUUIDFromName(String name) { + val optional = nameCache.get(false, set -> set.getObject("name").equals(name)).stream().findFirst(); + + if(optional.isPresent()) { + val set = optional.get(); + + if((System.currentTimeMillis() - (long)set.getObject("timestamp")) > TimeUnit.DAYS.toMillis(1)) { + nameCache.remove(set.getId()); + } + return UUID.fromString(set.getObject("uuid")); + } + return null; + } + + @Override + public String getNameFromUUID(UUID uuid) { + val optional = nameCache.get(false, set -> set.getObject("uuid").equals(uuid.toString())) + .stream().findFirst(); + + if(optional.isPresent()) { + val set = optional.get(); + + if((System.currentTimeMillis() - (long)set.getObject("timestamp")) > TimeUnit.DAYS.toMillis(1)) { + nameCache.remove(set.getId()); + } + return set.getObject("name"); + } + return null; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/impl/MongoStorage.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/impl/MongoStorage.java new file mode 100644 index 000000000..96dd1ce7a --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/impl/MongoStorage.java @@ -0,0 +1,198 @@ +package dev.brighten.anticheat.logs.data.impl; + +import cc.funkemunky.api.utils.RunUtils; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.logs.data.DataStorage; +import dev.brighten.anticheat.logs.data.config.MongoConfig; +import dev.brighten.anticheat.logs.objects.Log; +import dev.brighten.anticheat.logs.objects.Punishment; +import dev.brighten.db.depends.com.mongodb.BasicDBObject; +import dev.brighten.db.depends.com.mongodb.MongoClientSettings; +import dev.brighten.db.depends.com.mongodb.MongoCredential; +import dev.brighten.db.depends.com.mongodb.ServerAddress; +import dev.brighten.db.depends.com.mongodb.client.*; +import dev.brighten.db.depends.com.mongodb.client.model.Aggregates; +import dev.brighten.db.depends.com.mongodb.client.model.Filters; +import dev.brighten.db.depends.com.mongodb.client.model.Indexes; +import dev.brighten.dev.depends.org.bson.Document; +import dev.brighten.dev.depends.org.bson.conversions.Bson; +import org.bukkit.scheduler.BukkitTask; + +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class MongoStorage implements DataStorage { + + private MongoCollection logsCollection, punishmentsCollection, nameUUIDCollection; + private MongoDatabase database; + private BukkitTask task; + + private List logs = new CopyOnWriteArrayList<>(); + private List punishments = new CopyOnWriteArrayList<>(); + + public MongoStorage() { + MongoClient client; + if(MongoConfig.loginDetails) { + client = MongoClients.create(MongoClientSettings.builder().applyToClusterSettings(builder -> + builder.hosts(Collections.singletonList(new ServerAddress(MongoConfig.ip, MongoConfig.port)))) + .credential(MongoCredential.createCredential(MongoConfig.username, + MongoConfig.authDatabase.length() > 0 ? MongoConfig.authDatabase : MongoConfig.database, + MongoConfig.password.toCharArray())) + .build()); + } else { + client = MongoClients.create(MongoClientSettings.builder().applyToClusterSettings(builder -> + builder.hosts(Collections.singletonList(new ServerAddress(MongoConfig.ip, MongoConfig.port)))) + .build()); + } + database = client.getDatabase(MongoConfig.database); + logsCollection = database.getCollection("logs"); + punishmentsCollection = database.getCollection("punishments"); + nameUUIDCollection = database.getCollection("nameUuid"); + + logsCollection.createIndex(Indexes.ascending("uuid")); + punishmentsCollection.createIndex(Indexes.ascending("uuid")); + + + task = RunUtils.taskTimerAsync(() -> { + if(logs.size() > 0) { + for (Log log : logs) { + logsCollection.insertOne(new Document("uuid", log.uuid.toString()) + .append("time", log.timeStamp).append("check", log.checkName) + .append("vl", (double)log.vl).append("info", log.info).append("ping", log.ping) + .append("tps", log.tps)); + logs.remove(log); + } + } + if(punishments.size() > 0) { + for(Punishment punishment : punishments) { + punishmentsCollection.insertOne(new Document("uuid", punishment.uuid) + .append("time", punishment.timeStamp).append("check", punishment.checkName)); + punishments.remove(punishment); + } + } + }, Kauri.INSTANCE, 120L, 40L); + } + @Override + public List getLogs(UUID uuid, Check check, int arrayMin, int arrayMax, long timeFrom, long timeTo) { + Bson document = new Document("$gte", timeFrom).append("$lt", timeTo); + List logs = new ArrayList<>(); + + List aggregates = new ArrayList<>(); + + if(uuid != null) aggregates.add(Aggregates.match(Filters.eq("uuid", uuid.toString()))); + if(check != null) aggregates.add(Aggregates.match(Filters.eq("check", check.name))); + + aggregates.addAll(Arrays.asList(Aggregates.match(Filters.eq("time", document)), + new BasicDBObject("$skip", arrayMin), new BasicDBObject("$limit", arrayMax), + new BasicDBObject("$sort", new BasicDBObject("time", -1)))); + + AggregateIterable agg = logsCollection.aggregate(aggregates).allowDiskUse(true); + + agg.forEach((Consumer) logs::add); + + return logs.stream() + .map(doc -> new Log(UUID.fromString(doc.getString("uuid")), doc.getString("check"), + doc.getString("info"), doc.getDouble("vl").floatValue(), doc.getLong("ping"), + doc.getLong("time"), doc.getDouble("tps"))) + .collect(Collectors.toList()); + } + + @Override + public List getPunishments(UUID uuid, int arrayMin, int arrayMax, long timeFrom, long timeTo) { + Bson document = new Document("$gte", timeFrom).append("$lt", timeTo); + List logs = new ArrayList<>(); + AggregateIterable agg = punishmentsCollection.aggregate(Arrays + .asList(Aggregates.match(Filters.eq("uuid", uuid.toString())), + Aggregates.match(Filters.eq("time", document)), + new BasicDBObject("$skip", arrayMin), new BasicDBObject("$limit", arrayMax), + new BasicDBObject("$sort", new BasicDBObject("time", -1)))).allowDiskUse(true); + + agg.forEach((Consumer) logs::add); + + return logs.stream() + .map(doc -> new Punishment(UUID.fromString(doc.getString("uuid")), + doc.getString("check"), doc.getLong("time"))) + .collect(Collectors.toList()); + } + + + @Override + public void shutdown() { + task.cancel(); + task = null; + logs.clear(); + punishments.clear(); + database = null; + logsCollection = null; + punishmentsCollection = null; + nameUUIDCollection = null; + } + + @Override + public List getHighestVL(UUID uuid, Check check, int limit, long timeFrom, long timeTo) { + Bson document = new Document("$gte", timeFrom).append("$lt", timeTo); + List logs = new ArrayList<>(); + logsCollection.find(Filters.eq("uuid", uuid.toString())) + .filter(new Document("time", document)) + .forEach((Consumer) logs::add); + + Map logsMax = new HashMap<>(); + + logs.stream() + .map(doc -> new Log(uuid, doc.getString("check"), doc.getString("info"), + doc.getDouble("vl").floatValue(), doc.getLong("ping"), doc.getLong("time"), + doc.getDouble("tps"))) + .forEach(log -> { + if(logsMax.containsKey(log.checkName)) { + Log toCheck = logsMax.get(log.checkName); + + if(toCheck.vl < log.vl) { + logsMax.put(log.checkName, log); + } + } else logsMax.put(log.checkName, log); + }); + + return new ArrayList<>(logsMax.values()); + } + + @Override + public void addLog(Log log) { + logs.add(log); + } + + @Override + public void removeAll(UUID uuid) { + punishmentsCollection.deleteMany(Filters.eq("uuid", uuid.toString())); + logsCollection.deleteMany(Filters.eq("uuid", uuid.toString())); + } + + @Override + public void addPunishment(Punishment punishment) { + punishments.add(punishment); + } + + @Override + public void cacheAPICall(UUID uuid, String name) { + nameUUIDCollection.deleteMany(Filters.or(Filters.eq("uuid", uuid.toString()), + Filters.eq("name", name))); + + Document document = new Document("uuid", uuid.toString()); + document.put("name", name); + document.put("timestamp", System.currentTimeMillis()); + + nameUUIDCollection.insertOne(document); + } + + @Override + public UUID getUUIDFromName(String name) { + return null; + } + + @Override + public String getNameFromUUID(UUID uuid) { + return null; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/impl/MySQLStorage.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/impl/MySQLStorage.java new file mode 100644 index 000000000..cc32e5603 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/impl/MySQLStorage.java @@ -0,0 +1,244 @@ +package dev.brighten.anticheat.logs.data.impl; + +import cc.funkemunky.api.utils.MiscUtils; +import cc.funkemunky.api.utils.RunUtils; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.logs.data.DataStorage; +import dev.brighten.anticheat.logs.data.sql.MySQL; +import dev.brighten.anticheat.logs.data.sql.Query; +import dev.brighten.anticheat.logs.objects.Log; +import dev.brighten.anticheat.logs.objects.Punishment; +import lombok.val; +import org.bukkit.scheduler.BukkitTask; + +import java.sql.SQLException; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +public class MySQLStorage implements DataStorage { + + private List logs = new CopyOnWriteArrayList<>(); + private List punishments = new CopyOnWriteArrayList<>(); + private BukkitTask task; + + public MySQLStorage() { + MySQL.init(); + Query.prepare("CREATE TABLE IF NOT EXISTS `VIOLATIONS` (" + + "`UUID` VARCHAR(64) NOT NULL," + + "`TIME` LONG NOT NULL," + + "`VL` FLOAT NOT NULL," + + "`CHECK` VARCHAR(32) NOT NULL," + + "`PING` SMALLINT NOT NULL," + + "`TPS` DOUBLE NOT NULL," + + "`INFO` LONGTEXT NOT NULL)").execute(); + Query.prepare("CREATE TABLE IF NOT EXISTS `PUNISHMENTS` (" + + "`UUID` VARCHAR(64) NOT NULL," + + "`TIME` LONG NOT NULL," + + "`CHECK` VARCHAR(32) NOT NULL)").execute(); + Query.prepare("CREATE TABLE IF NOT EXISTS `NAMECACHE` (" + + "`UUID` VARCHAR(64) NOT NULL," + + "`NAME` VARCHAR(16) NOT NULL," + + "`TIMESTAMP` LONG NOT NULL)").execute(); + Kauri.INSTANCE.loggingThread.execute(() -> { + MiscUtils.printToConsole("&7Creating iv_uuid index for SQL..."); + Query.prepare("CREATE INDEX `iv_uuid` ON `VIOLATIONS` (UUID)").execute(); + MiscUtils.printToConsole("&aCreated iv_uuid!"); + MiscUtils.printToConsole("&7Creating ip_uuid index for SQL..."); + Query.prepare("CREATE INDEX `ip_uuid` ON `PUNISHMENTS` (UUID)").execute(); + MiscUtils.printToConsole("&aCreated ip_uuid!"); + MiscUtils.printToConsole("&a7 Creating iv_time index for SQL..."); + Query.prepare("CREATE INDEX `iv_time` ON `VIOLATIONS` (`TIME`)").execute(); + MiscUtils.printToConsole("&aCreated!"); + }); + + task = RunUtils.taskTimerAsync(() -> { + if(logs.size() > 0) { + for (Log log : logs) { + try { + Query.prepare("INSERT INTO `VIOLATIONS`" + + " (`UUID`, `TIME`, `VL`, `CHECK`, `PING`, `TPS`, `INFO`) VALUES (?,?,?,?,?,?,?)") + .append(log.uuid.toString()).append(log.timeStamp).append(log.vl) + .append(log.checkName).append((int)log.ping).append(log.tps) + .append(log.info) + .execute(); + } catch(Exception e) { + e.printStackTrace(); + } + logs.remove(log); + } + } + if(punishments.size() > 0) { + for(Punishment punishment : punishments) { + try { + Query.prepare("INSERT INTO `PUNISHMENTS` (`UUID`,`TIME`,`CHECK`) VALUES (?,?,?)") + .append(punishment.uuid.toString()) + .append(punishment.timeStamp).append(punishment.checkName) + .execute(); + } catch(Exception e) { + e.printStackTrace(); + } + punishments.remove(punishment); + } + } + }, Kauri.INSTANCE, 120L, 40L); + } + + @Override + public void shutdown() { + task.cancel(); + task = null; + logs.clear(); + punishments.clear(); + MySQL.shutdown(); + } + + @Override + public List getLogs(UUID uuid, Check check, int arrayMin, int arrayMax, long timeFrom, long timeTo) { + List logs = new ArrayList<>(); + + if(uuid != null) { + Query.prepare("SELECT `TIME`, `VL`, `CHECK`, `PING`, `TPS`, `INFO` " + + "FROM `VIOLATIONS` WHERE `UUID` = ?"+ (check != null ? " AND WHERE `CHECK` = " + check.name : "") + + " AND `TIME` BETWEEN ? AND ? ORDER BY `TIME` DESC LIMIT ?,?") + .append(uuid.toString()).append(timeFrom).append(timeTo).append(arrayMin).append(arrayMax) + .execute(rs -> + logs.add(new Log(uuid, + rs.getString("CHECK"), rs.getString("INFO"), + rs.getFloat("VL"), (long)rs.getInt("PING"), + rs.getLong("TIME"), rs.getDouble("TPS")))); + } else { + Query.prepare("SELECT `UUID`, `TIME`, `VL`, `CHECK`, `PING`, `TPS`, `INFO` " + + "FROM `VIOLATIONS`" + (check != null ? " WHERE `CHECK` = " + check.name + " AND" : " WHERE") + + " `TIME` BETWEEN ? AND ? ORDER BY `TIME` DESC LIMIT ?,?") + .append(timeFrom).append(timeTo).append(arrayMin).append(arrayMax) + .execute(rs -> { + logs.add(new Log(UUID.fromString(rs.getString("UUID")), + rs.getString("CHECK"), rs.getString("INFO"), + rs.getFloat("VL"), (long)rs.getInt("PING"), + rs.getLong("TIME"), rs.getDouble("TPS"))); + }); + } + + return logs; + } + + @Override + public List getPunishments(UUID uuid, int arrayMin, int arrayMax, long timeFrom, long timeTo) { + List punishments = new ArrayList<>(); + + if(uuid != null) { + Query.prepare("SELECT `TIME`, `CHECK` FROM `PUNISHMENTS` " + + "WHERE `UUID` = ? AND TIME BETWEEN ? AND ? ORDER BY `TIME` DESC LIMIT ?,?") + .append(uuid.toString()).append(timeFrom).append(timeTo).append(arrayMin).append(arrayMax) + .execute(rs -> punishments + .add(new Punishment(uuid, rs.getString("CHECK"), rs.getLong("TIME")))); + } else { + Query.prepare("SELECT `UUID`, `TIME`, `CHECK` FROM `PUNISHMENTS` " + + "WHERE TIME BETWEEN ? AND ? ORDER BY `TIME` DESC LIMIT ?,?") + .append(timeFrom).append(timeTo).append(arrayMin).append(arrayMax) + .execute(rs -> punishments + .add(new Punishment(UUID.fromString(rs.getString("UUID")), + rs.getString("CHECK"), rs.getLong("TIME")))); + } + + return punishments; + } + + @Override + public List getHighestVL(UUID uuid, Check check, int limit, long timeFrom, long timeTo) { + List logs = getLogs(uuid, check, 0, limit, timeFrom, timeTo); + + Map logsMax = new HashMap<>(); + + logs.forEach(log -> { + if(logsMax.containsKey(log.checkName)) { + Log toCheck = logsMax.get(log.checkName); + + if(toCheck.vl < log.vl) { + logsMax.put(log.checkName, log); + } + } else logsMax.put(log.checkName, log); + }); + return new ArrayList<>(logsMax.values()); + } + + @Override + public void addLog(Log log) { + logs.add(log); + } + + @Override + public void removeAll(UUID uuid) { + Query.prepare("DELETE FROM `VIOLATIONS` WHERE UUID = ?").append(uuid.toString()) + .execute(); + Query.prepare("DELETE FROM `PUNISHMENTS` WHERE UUID = ?").append(uuid.toString()) + .execute(); + } + + @Override + public void addPunishment(Punishment punishment) { + punishments.add(punishment); + } + + @Override + public void cacheAPICall(UUID uuid, String name) { + Kauri.INSTANCE.loggingThread.execute(() -> { + Query.prepare("DELETE FROM `NAMECACHE` WHERE `UUID` = ?").append(uuid.toString()).execute(); + Query.prepare("INSERT INTO `NAMECACHE` (`UUID`, `NAME`, `TIMESTAMP`) VALUES (?, ?, ?)") + .append(uuid.toString()).append(name).append(System.currentTimeMillis()).execute(); + }); + } + + @Override + public UUID getUUIDFromName(String name) { + try { + val rs = Query.prepare("SELECT `UUID`, `TIMESTAMP` FROM `NAMECACHE` WHERE `NAME` = ?") + .append(name).executeQuery(); + + String uuidString = rs.getString("UUID"); + + if(uuidString != null) { + UUID uuid = UUID.fromString(rs.getString("UUID")); + + if(System.currentTimeMillis() - rs.getLong("TIMESTAMP") > TimeUnit.DAYS.toMillis(1)) { + Kauri.INSTANCE.loggingThread.execute(() -> { + Query.prepare("DELETE FROM `NAMECACHE` WHERE `UUID` = ?").append(uuidString).execute(); + MiscUtils.printToConsole("Deleted " + uuidString + " from name cache (age > 1 day)."); + }); + } + return uuid; + } + } catch (SQLException e) { + RunUtils.task(e::printStackTrace); + } catch(Exception e) { + e.printStackTrace(); + //Empty catch + } + return null; + } + + @Override + public String getNameFromUUID(UUID uuid) { + try { + val rs = Query.prepare("SELECT `NAME` `TIMESTAMP` FROM `NAMECACHE` WHERE `UUID` = ?") + .append(uuid.toString()).executeQuery(); + + String name = rs.getString("NAME"); + + if(name != null) { + if(System.currentTimeMillis() - rs.getLong("TIMESTAMP") > TimeUnit.DAYS.toMillis(1)) { + Kauri.INSTANCE.loggingThread.execute(() -> { + Query.prepare("DELETE FROM `NAMECACHE` WHERE `NAME` = ?").append(name).execute(); + MiscUtils.printToConsole("Deleted " + name + " from name cache (age > 1 day)."); + }); + } + return name; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/ExecutableStatement.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/ExecutableStatement.java new file mode 100644 index 000000000..a5843e587 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/ExecutableStatement.java @@ -0,0 +1,138 @@ +package dev.brighten.anticheat.logs.data.sql; + +import dev.brighten.anticheat.utils.MiscUtils; +import lombok.SneakyThrows; + +import java.sql.*; +import java.util.UUID; + +public class ExecutableStatement { + private PreparedStatement statement; + private int pos = 1; + + public ExecutableStatement(PreparedStatement statement) { + this.statement = statement; + } + + @SneakyThrows + public Integer execute() { + try { + return statement.executeUpdate(); + } finally { + MiscUtils.close(statement); + } + } + + @SneakyThrows + public void execute(ResultSetIterator iterator) { + ResultSet rs = null; + try { + rs = statement.executeQuery(); + while (rs.next()) iterator.next(rs); + } finally { + MiscUtils.close(statement, rs); + } + } + + @SneakyThrows + public void executeSingle(ResultSetIterator iterator) { + ResultSet rs = null; + try { + rs = statement.executeQuery(); + if (rs.next()) iterator.next(rs); + else iterator.next(null); + } finally { + MiscUtils.close(statement, rs); + } + } + + @SneakyThrows + public ResultSet executeQuery() { + return statement.executeQuery(); + } + + @SneakyThrows + public ExecutableStatement append(Object obj) { + statement.setObject(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(String obj) { + statement.setString(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(UUID uuid) { + if (uuid != null) statement.setString(pos++, uuid.toString().replace("-", "")); + else statement.setString(pos++, null); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Array obj) { + statement.setArray(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Integer obj) { + statement.setInt(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Short obj) { + statement.setShort(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Long obj) { + statement.setLong(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Float obj) { + statement.setFloat(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Double obj) { + statement.setDouble(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Date obj) { + statement.setDate(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Timestamp obj) { + statement.setTimestamp(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Time obj) { + statement.setTime(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(Blob obj) { + statement.setBlob(pos++, obj); + return this; + } + + @SneakyThrows + public ExecutableStatement append(byte[] obj) { + statement.setBytes(pos++, obj); + return this; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/MySQL.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/MySQL.java new file mode 100644 index 000000000..807ec2e2f --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/MySQL.java @@ -0,0 +1,49 @@ +package dev.brighten.anticheat.logs.data.sql; + +import dev.brighten.anticheat.logs.data.config.MySQLConfig; + +import java.sql.Connection; +import java.sql.DriverManager; + +public class MySQL { + private static Connection conn; + + public static void init() { + try { + if (conn == null || conn.isClosed()) { + Class.forName("com.mysql.jdbc.Driver"); + conn = DriverManager.getConnection("jdbc:mysql://" + MySQLConfig.ip + + ":3306/?useSSL=true&autoReconnect=true", + MySQLConfig.username, + MySQLConfig.password); + conn.setAutoCommit(true); + Query.use(conn); + Query.prepare("CREATE DATABASE IF NOT EXISTS `" + MySQLConfig.database + "`").execute(); + Query.prepare("USE `" + MySQLConfig.database + "`").execute(); + System.out.println("Connection to MySQL has been established."); + } + } catch (Exception e) { + System.out.println("Failed to load mysql: " + e.getMessage()); + e.printStackTrace(); + } + } + + public static void use() { + try { + init(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void shutdown() { + try { + if(conn != null && !conn.isClosed()) { + conn.close(); + conn = null; + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/Query.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/Query.java new file mode 100644 index 000000000..0bca68106 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/Query.java @@ -0,0 +1,23 @@ +package dev.brighten.anticheat.logs.data.sql; + +import lombok.SneakyThrows; + +import java.sql.Connection; + +public class Query { + private static Connection conn; + + public static void use(Connection conn) { + Query.conn = conn; + } + + @SneakyThrows + public static ExecutableStatement prepare(String query) { + return new ExecutableStatement(conn.prepareStatement(query)); + } + + @SneakyThrows + public static ExecutableStatement prepare(String query, Connection con) { + return new ExecutableStatement(con.prepareStatement(query)); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/ResultSetIterator.java b/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/ResultSetIterator.java new file mode 100644 index 000000000..ec28f5005 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/data/sql/ResultSetIterator.java @@ -0,0 +1,7 @@ +package dev.brighten.anticheat.logs.data.sql; + +import java.sql.ResultSet; + +public interface ResultSetIterator { + void next(ResultSet rs) throws Exception; +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/objects/Log.java b/Impl/src/main/java/dev/brighten/anticheat/logs/objects/Log.java new file mode 100644 index 000000000..398944ee6 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/objects/Log.java @@ -0,0 +1,52 @@ +package dev.brighten.anticheat.logs.objects; + +import cc.funkemunky.api.reflections.types.WrappedClass; +import cc.funkemunky.api.reflections.types.WrappedField; +import dev.brighten.db.utils.json.JSONException; +import dev.brighten.db.utils.json.JSONObject; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@AllArgsConstructor +@NoArgsConstructor +public class Log { + + public UUID uuid; + public String checkName, info; + public float vl; + public long ping, timeStamp; + public double tps; + private static WrappedClass logClass = new WrappedClass(Log.class); + + public static Log fromJson(String jsonString) { + try { + JSONObject object = new JSONObject(jsonString); + Log log = new Log(); + + for (WrappedField field : logClass.getFields(true)) { + field.set(log, object.get(field.getField().getName())); + } + + return log; + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + + public String toJson() { + JSONObject object = new JSONObject(); + + try { + for (WrappedField field : logClass.getFields(true)) { + object.put(field.getField().getName(), (Object)field.get(this)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + + return object.toString(); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/logs/objects/Punishment.java b/Impl/src/main/java/dev/brighten/anticheat/logs/objects/Punishment.java new file mode 100644 index 000000000..44f5941f0 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/logs/objects/Punishment.java @@ -0,0 +1,49 @@ +package dev.brighten.anticheat.logs.objects; + +import cc.funkemunky.api.reflections.types.WrappedClass; +import cc.funkemunky.api.reflections.types.WrappedField; +import dev.brighten.db.utils.json.JSONException; +import dev.brighten.db.utils.json.JSONObject; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +import java.util.UUID; + +@AllArgsConstructor +@NoArgsConstructor +public class Punishment { + public UUID uuid; + public String checkName; + public long timeStamp; + private static WrappedClass punishClass = new WrappedClass(Punishment.class); + + public static Punishment fromJson(String jsonString) { + try { + JSONObject object = new JSONObject(jsonString); + Punishment log = new Punishment(); + + for (WrappedField field : punishClass.getFields(true)) { + field.set(log, object.get(field.getField().getName())); + } + + return log; + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + + public String toJson() { + JSONObject object = new JSONObject(); + + try { + for (WrappedField field : punishClass.getFields(true)) { + object.put(field.getField().getName(), (Object)field.get(this)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + + return object.toString(); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/menu/LogsGUI.java b/Impl/src/main/java/dev/brighten/anticheat/menu/LogsGUI.java new file mode 100644 index 000000000..8fe9253a5 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/menu/LogsGUI.java @@ -0,0 +1,351 @@ +package dev.brighten.anticheat.menu; + +import cc.funkemunky.api.utils.*; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.check.api.Check; +import dev.brighten.anticheat.commands.LogCommand; +import dev.brighten.anticheat.logs.objects.Log; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.button.ClickAction; +import dev.brighten.anticheat.utils.menu.preset.button.FillerButton; +import dev.brighten.anticheat.utils.menu.type.impl.ChestMenu; +import dev.brighten.db.utils.Pair; +import lombok.val; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.scheduler.BukkitTask; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class LogsGUI extends ChestMenu { + + private List logs = new ArrayList<>(); + private BukkitTask updaterTask; + private AtomicInteger currentPage = new AtomicInteger(1); + private OfflinePlayer player; + private Player shown; + private Set filtered = new HashSet<>(); + + public LogsGUI(OfflinePlayer player) { + super("Violations", 6); + + this.player = player; + updateLogs(); + + setTitle(Color.Gray + player.getName() + "'s Logs"); + + setButtons(1); + buildInventory(true); + } + + public LogsGUI(OfflinePlayer player, int page) { + super(player.getName() + "'s Logs", 6); + + this.player = player; + currentPage.set(page); + updateLogs(); + setButtons(page); + + setTitle(Color.Gray + player.getName() + "'s Logs"); + + buildInventory(true); + } + + private void setButtons(int page) { + if (getMenuDimension().getSize() <= 0) return; + + if (updaterTask == null || !Bukkit.getScheduler().isCurrentlyRunning(updaterTask.getTaskId())) { + runUpdater(); + } + + List filteredLogs = (filtered.size() > 0 ? logs.stream() + .filter(log -> { + for (String s : filtered) { + if (s.equalsIgnoreCase(log.checkName)) { + return true; + } + } + return false; + }).sequential() + .sorted(Comparator.comparing(log -> log.timeStamp, Comparator.reverseOrder())) + .collect(Collectors.toList()) : logs); + + List subList = filteredLogs.subList(Math.min((page - 1) * 45, filteredLogs.size()), + Math.min(page * 45, filteredLogs.size())); + for (int i = 0; i < subList.size(); i++) setItem(i, buttonFromLog(subList.get(i))); + + if(subList.size() < 45) { + for(int i = subList.size() ; i < 45 ; i++) { + setItem(i, new FillerButton()); + } + } + + //Setting the next page option if possible. + if (Math.min(page * 45, filteredLogs.size()) < filteredLogs.size()) { + Button next = new Button(false, new ItemBuilder(XMaterial.BOOK.parseMaterial()) + .amount(1).name(Color.Red + "Next Page &7(&e" + (page + 1) + "&7)").build(), + (player, info) -> { + if (info.getClickType().isLeftClick()) { + setButtons(page + 1); + buildInventory(false); + currentPage.set(page + 1); + } + }); + setItem(50, next); + } else setItem(50, new FillerButton()); + + val punishments = Kauri.INSTANCE.loggerManager.getPunishments(player.getUniqueId()); + + Button getPastebin = new Button(false, new ItemBuilder(XMaterial.SKULL_ITEM.parseMaterial()) + .amount(1) + .durability(3) + .owner(player.getName()) + .name(Color.Red + player.getName()) + .lore("", "&7Page: &f" + page, "", "&6Punishments&8: &f" + punishments.size(), "", + "&e&oLeft click &7&oto view a summary of logs.", + (shown == null || shown.hasPermission("kauri.logs.share") + ? "&e&oRight Click &7&oto get an &f&ounlisted &7&opastebin link of the logs." + : "&c&o(No Permission) &e&o&mRight Click &7&o&mto get an &f&o&munlisted &7&o&mpastebin link of the logs."), + (shown == null || shown.hasPermission("kauri.logs.clear") + ? "&e&oShift Left Click &7&oto &f&oclear &7&othe logs of " + player.getName() + : "&c&o(No Permission) &e&o&mShift Right Click &7&o&mto &f&o&mclear &7&o&mthe logs of " + player.getName())).build(), + (player, info) -> { + if (player.hasPermission("kauri.logs.share")) { + if (info.getClickType().isRightClick()) { + runFunction(info, "kauri.logs.share", () -> { + close(player); + player.sendMessage(Color.Green + "Logs: " + + LogCommand.getLogsFromUUID(LogsGUI.this.player.getUniqueId())); + }); + } else if (info.getClickType().isLeftClick() && info.getClickType().isShiftClick()) { + runFunction(info, "kauri.logs.clear", + () -> player.performCommand("kauri logs clear " + this.player.getName())); + } else if (info.getClickType().isLeftClick()) { + getSummary().showMenu(player); + } + } + }); + + setItem(49, getPastebin); + + //Setting the previous page option if possible. + if (page > 1) { + Button back = new Button(false, new ItemBuilder(XMaterial.BOOK.parseMaterial()) + .amount(1).name(Color.Red + "Previous Page &7(&e" + (page - 1) + "&7)").build(), + (player, info) -> { + if (info.getClickType().isLeftClick()) { + setButtons(page - 1); + currentPage.set(page - 1); + buildInventory(false); + } + }); + setItem(48, back); + } else setItem(48, new FillerButton()); + + if(filtered.size() > 0) { + List lore = new ArrayList<>(Arrays.asList("", Color.translate("&eFilters:"))); + + for (String s : filtered) { + lore.add(Color.translate("&7- &f" + s)); + } + Button stopFilter = new Button(false, + new ItemBuilder(XMaterial.REDSTONE.parseMaterial()).amount(1) + .name(Color.Red + "Stop Filter").lore(lore).build(), + (player, info) -> { + filtered.clear(); + setButtons(currentPage.get()); + buildInventory(false); + }); + + setItem(47, stopFilter); + setItem(51, stopFilter); + } else { + setItem(47, new FillerButton()); + setItem(51, new FillerButton()); + } + + //Setting all empty slots with a filler. + fill(new FillerButton()); + } + + private ChestMenu getSummary() { + ChestMenu summary = new ChestMenu(player.getName() + "'s Summary", 6); + + summary.setParent(this); + Map> sortedLogs = new HashMap<>(); + + logs.forEach(log -> { + List list = sortedLogs.getOrDefault(log.checkName, new ArrayList<>()); + + list.add(log); + sortedLogs.put(log.checkName, list); + }); + + sortedLogs.keySet().stream() + .sorted(Comparator.comparing(key -> key)) + .map(key -> new Pair<>(key, sortedLogs.get(key))) + .forEach(pair -> { + String check = pair.key; + val list = pair.value; + Button button = new Button(false, + new ItemBuilder(filtered.contains(check) ? XMaterial.MAP.parseMaterial() : + XMaterial.PAPER.parseMaterial()).amount(1) + .name((filtered.contains(check) ? Color.Red + Color.Italics : Color.Gold) + + check) + .lore("", "&7Alerts: &f" + list.size(), + "&7Highest VL: &f" + + list.stream() + .max(Comparator.comparing(log -> log.vl)) + .map(log -> log.vl).orElse(0f), + "&7Type: &f" + (Check.getCheckInfo(check) != null + ? Check.getCheckInfo(check).checkType().name() : "UNKNOWN"), + "", + "&f&oLeft-Click &7&oto add check to vl filter.", + "&f&oRight-Click &7&oto remove check from vl filter.").build(), + (player, info) -> { + if(info.getClickType().name().contains("LEFT")) filtered.add(check); + else if(info.getClickType().name().contains("RIGHT")) filtered.remove(check); + else return; + + setButtons(1); + currentPage.set(1); + buildInventory(false); + info.getButton().setStack((filtered.contains(check) + ? new ItemBuilder(info.getButton().getStack()) + .enchantment(Enchantment.DURABILITY, 1) + : new ItemBuilder(info.getButton().getStack()).clearEnchantments()) + .type(filtered.contains(check) ? XMaterial.MAP.parseMaterial() + : XMaterial.PAPER.parseMaterial()) + .name((filtered.contains(check) + ? Color.Red + Color.Italics : Color.Gold) + check) + .build()); + List lore = new ArrayList<>(Arrays + .asList("", Color.translate("&eFilters:"))); + + for (String s : filtered) { + lore.add(Color.translate("&7- &f" + s)); + } + Button stopFilter = new Button(false, + new ItemBuilder(XMaterial.REDSTONE.parseMaterial()).amount(1) + .name(Color.Red + "Stop Filter").lore(lore).build(), + (player2, info2) -> { + filtered.clear(); + setButtons(1); + buildInventory(false); + info.getMenu().close(player2); + getSummary().showMenu(player2); + }); + + info.getMenu().setItem(53, stopFilter); + info.getMenu().buildInventory(false); + }); + summary.addItem(button); + }); + if(filtered.size() > 0) { + List lore = new ArrayList<>(Arrays.asList("", Color.translate("&eFilters:"))); + + for (String s : filtered) { + lore.add(Color.translate("&7- &f" + s)); + } + Button stopFilter = new Button(false, + new ItemBuilder(XMaterial.REDSTONE.parseMaterial()).amount(1) + .name(Color.Red + "Stop Filter").lore(lore).build(), + (player, info) -> { + filtered.clear(); + setButtons(1); + buildInventory(false); + info.getMenu().close(player); + getSummary().showMenu(player); + }); + + summary.setItem(53, stopFilter); + } else { + summary.setItem(53, new FillerButton()); + } + + summary.fill(new FillerButton()); + + return summary; + } + + private void updateLogs() { + logs = Kauri.INSTANCE.loggerManager.getLogs(player.getUniqueId()) + .stream() + .sorted(Comparator.comparing(log -> log.timeStamp, Comparator.reverseOrder())) + .collect(Collectors.toList()); + } + + private void runUpdater() { + updaterTask = RunUtils.taskTimerAsync(() -> { + if (shown != null + && shown.getOpenInventory() != null + && shown.getOpenInventory().getTopInventory() != null + && getHolder() != null + && shown.getOpenInventory().getTopInventory() == getHolder().getInventory()) { + updateLogs(); + setButtons(currentPage.get()); + buildInventory(false); + } else cancelTask(); + }, Kauri.INSTANCE, 80L, 200L); + } + + private void cancelTask() { + if (updaterTask == null) return; + updaterTask.cancel(); + } + + @Override + public void showMenu(Player player) { + this.shown = player; + runUpdater(); + + super.showMenu(player); + } + + private void runFunction(ClickAction.InformationPair info, String permission, Runnable function) { + if (shown == null) return; + + if (shown.hasPermission(permission)) { + function.run(); + } else { + String oldName = info.getButton().getStack().getItemMeta().getDisplayName(); + List oldLore = info.getButton().getStack().getItemMeta().getLore(); + ItemMeta meta = info.getButton().getStack().getItemMeta(); + + meta.setDisplayName(Color.Red + "No permission"); + meta.setLore(new ArrayList<>()); + info.getButton().getStack().setItemMeta(meta); + RunUtils.taskLater(() -> { + if (info.getButton() != null + && info.getButton().getStack().getItemMeta() + .getDisplayName().equals(Color.Red + "No permission")) { + ItemMeta newMeta = info.getButton().getStack().getItemMeta(); + newMeta.setDisplayName(oldName); + newMeta.setLore(oldLore); + info.getButton().getStack().setItemMeta(newMeta); + } + }, Kauri.INSTANCE, 20L); + } + } + + @Override + public void handleClose(Player player) { + cancelTask(); + } + + private Button buttonFromLog(Log log) { + return new Button(false, new ItemBuilder(XMaterial.PAPER.parseMaterial()) + .amount(1).name(Color.Gold + log.checkName) + .lore("", "&eTime&8: &f" + dev.brighten.anticheat.utils.MiscUtils.timeStampToDate(log.timeStamp), + "&eData&8: &f" + log.info, + "&eViolation Level&8: &f" + MathUtils.round(log.vl, 3), + "&ePing&8: &f" + log.ping, + "&eTPS&8: &f" + MathUtils.round(log.tps, 2)) + .build(), null); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/menu/PlayerInformationGUI.java b/Impl/src/main/java/dev/brighten/anticheat/menu/PlayerInformationGUI.java new file mode 100644 index 000000000..e88e21ca1 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/menu/PlayerInformationGUI.java @@ -0,0 +1,152 @@ +package dev.brighten.anticheat.menu; + +import cc.funkemunky.api.handlers.ForgeHandler; +import cc.funkemunky.api.handlers.ModData; +import cc.funkemunky.api.utils.*; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.logs.objects.Log; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.type.impl.ChestMenu; +import org.apache.commons.lang.time.DurationFormatUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitTask; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class PlayerInformationGUI extends ChestMenu { + + public ObjectData data; + private static String line = MiscUtils.line(Color.Dark_Gray); + private static String halfLine = line.substring(0, Math.round(line.length() / 1.5f)); + private Button playerButton, forgeButton, violationsButton; + private BukkitTask updatingTask; + private ModData modData; + + public PlayerInformationGUI(ObjectData data) { + super(data.getPlayer().getName() + "'s Information", 3); + this.data = data; + this.modData = ForgeHandler.getMods(data.getPlayer()); + + addItems(); + update(); + } + + public void addItems() { + playerButton = new Button(false, playerSkull(), null); + forgeButton = new Button(false, forgeItem(), ( + modData == null + || modData.getMods().size() == 0 ? null : (player, info) -> modsGUI().showMenu(player))); + violationsButton = new Button(false, violationsButton(), (player, info) -> { + if(info.getClickType().name().contains("LEFT")) { + info.getMenu().close(player); + Bukkit.dispatchCommand(player, "kauri logs " + data.getPlayer().getName()); + } + }); + + setItem(13, playerButton); + setItem(11, forgeButton); + setItem(15, violationsButton); + } + + private ItemStack playerSkull() { + ItemBuilder vioItem = new ItemBuilder(XMaterial.SKULL_ITEM.parseMaterial()); + + vioItem.amount(1); + vioItem.durability(3); + vioItem.name(Color.Gold + data.getPlayer().getName()); + vioItem.owner(data.getPlayer().getName()); + vioItem.lore("", + halfLine, + "&eVersion&7: &f" + data.playerVersion.name(), + "&ePing/tPing&7: &f" + data.lagInfo.ping + "ms, " + data.lagInfo.transPing + "ms", + "&eLast Packet Drop&7: &f" + DurationFormatUtils + .formatDurationHMS(data.lagInfo.lastPacketDrop.getPassed() * 50), + halfLine); + return vioItem.build(); + } + + private ItemStack forgeItem() { + modData = ForgeHandler.getMods(data.getPlayer()); + ItemBuilder forgeItem = new ItemBuilder(XMaterial.ANVIL.parseMaterial()); + + forgeItem.amount(1); + forgeItem.name(Color.Gold + "Client Information"); + + List loreList = new ArrayList<>(Arrays.asList("", + halfLine, + "&eUsing Forge&7: &f" + (modData != null), + "&eUsing Lunar&7: &f" + data.usingLunar, + "")); + + if(modData != null) { + List modList = new ArrayList<>(modData.getMods()); + + loreList.add(modList.size() > 0 ? "&7&oRight click to view mods" : "&c&oNo mods."); + modList.clear(); + } else { + loreList.add("&c&oNo mods."); + } + loreList.add(halfLine); + + forgeItem.lore(loreList.stream().map(Color::translate).collect(Collectors.toList())); + + return forgeItem.build(); + } + + private ItemStack violationsButton() { + ItemBuilder violationsItem = new ItemBuilder(XMaterial.ENCHANTED_BOOK.parseMaterial()); + + List logs = Kauri.INSTANCE.loggerManager.getLogs(data.getPlayer().getUniqueId()); + + violationsItem.amount(1); + violationsItem.name(Color.Gold + "Violations"); + violationsItem.lore("", + halfLine, + "&fThis player currently has &e" + logs.size() + " logs&f.", + "&7&oLeft click to view logs", + halfLine); + + logs.clear(); + return violationsItem.build(); + } + + private ChestMenu modsGUI() { + ChestMenu subMenu = new ChestMenu(data.getPlayer().getName() + "'s Mods", + Math.min(6, (int)Math.ceil(modData.getMods().size() / 9D))); + + subMenu.setParent(this); + + modData.getMods().forEach(string -> { + ItemBuilder builder = new ItemBuilder(XMaterial.BOOK.parseMaterial()); + + builder.amount(1); + builder.name(Color.Gold + string); + subMenu.addItem(new Button(false, builder.build(), null)); + }); + + return subMenu; + } + + private void update() { + updatingTask = RunUtils.taskTimerAsync(() -> { + if(data == null) { + updatingTask.cancel(); + return; + } + playerButton.getStack().setItemMeta(playerSkull().getItemMeta()); + buildInventory(false); + }, Kauri.INSTANCE, 80L, 50L); + } + + @Override + public void handleClose(Player player) { + super.handleClose(player); + updatingTask.cancel(); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/ActionProcessor.java b/Impl/src/main/java/dev/brighten/anticheat/processing/ActionProcessor.java new file mode 100644 index 000000000..38222d8d6 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/ActionProcessor.java @@ -0,0 +1,34 @@ +package dev.brighten.anticheat.processing; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInEntityActionPacket; +import dev.brighten.anticheat.data.ObjectData; + +public class ActionProcessor { + + public static void process(ObjectData data, WrappedInEntityActionPacket packet) { + switch(packet.getAction()) { + case START_SNEAKING: + data.playerInfo.sneaking = true; + data.predictionService.sneak = true; + break; + case STOP_SNEAKING: + data.playerInfo.sneaking = false; + data.predictionService.sneak = false; + break; + case START_RIDING_JUMP: + data.playerInfo.ridingJump = true; + break; + case STOP_RIDING_JUMP: + data.playerInfo.ridingJump = false; + break; + case START_SPRINTING: + data.playerInfo.sprinting = true; + data.predictionService.sprint = true; + break; + case STOP_SPRINTING: + data.playerInfo.sprinting = false; + data.predictionService.sprint = false; + break; + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/ClickProcessor.java b/Impl/src/main/java/dev/brighten/anticheat/processing/ClickProcessor.java new file mode 100644 index 000000000..56b6ef16f --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/ClickProcessor.java @@ -0,0 +1,87 @@ +package dev.brighten.anticheat.processing; + +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInArmAnimationPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInTransactionPacket; +import cc.funkemunky.api.utils.Tuple; +import cc.funkemunky.api.utils.objects.evicting.EvictingList; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.utils.MiscUtils; +import dev.brighten.anticheat.utils.TickTimer; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +public class ClickProcessor { + public EvictingList cpsList = new EvictingList<>(40); + @Getter + private double std, mean, kurtosis, skewness, median, variance; + @Getter + private long min, max, sum, zeros; + @Getter + private int outliers, lowOutliers, highOutliers; + @Getter + private List modes = new ArrayList<>(); + @Getter + private Tuple, List> outliersTuple = new Tuple<>(new ArrayList<>(), new ArrayList<>()); + + private TickTimer lastZeroCheck = new TickTimer(1); + + @Getter + private boolean notReady; + + private final ObjectData data; + private int flyingTicks; + + public void onFlying(WrappedInTransactionPacket packet) { + flyingTicks++; + } + + public void onArm(WrappedInArmAnimationPacket packet, long timeStamp) { + long delta = flyingTicks; + + if(delta < 15 + && !data.playerInfo.breakingBlock && data.playerInfo.lastBlockPlace.hasPassed(3)) { + cpsList.add(delta); + + if(lastZeroCheck.hasPassed()) { + zeros = cpsList.stream().filter(dt -> dt <= 2).count(); + if(cpsList.size() >= 20) { + outliersTuple = MiscUtils.getOutliers(cpsList); + outliers = (lowOutliers = outliersTuple.one.size()) + (highOutliers = outliersTuple.two.size()); + } + lastZeroCheck.reset(); + } + + min = Long.MAX_VALUE; + max = Long.MIN_VALUE; + sum = 0; + for (Long v : cpsList) { + sum+= v; + if(v > 20) { + min = Math.min(v, min); + max = Math.max(v, max); + } + } + + mean = sum / (double)Math.max(1, cpsList.size()); + modes = MiscUtils.getModes(cpsList); + median = MiscUtils.getMedian(cpsList); + + std = 0; + for (Long v : cpsList) std+= Math.pow(v - mean, 2); + + variance = std / (long)cpsList.size(); + std = Math.sqrt(std / (long)cpsList.size()); + kurtosis = MiscUtils.getKurtosis(cpsList); + skewness = MiscUtils.getSkewnessApache(cpsList); + } + notReady = data.playerInfo.breakingBlock + || data.playerInfo.lastBlockPlace.hasNotPassed(3) + || cpsList.size() < 22; + + flyingTicks = 0; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/EntityProcessor.java b/Impl/src/main/java/dev/brighten/anticheat/processing/EntityProcessor.java new file mode 100644 index 000000000..3c474a4f2 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/EntityProcessor.java @@ -0,0 +1,43 @@ +package dev.brighten.anticheat.processing; + +import cc.funkemunky.api.Atlas; +import cc.funkemunky.api.utils.RunUtils; +import dev.brighten.anticheat.Kauri; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Vehicle; +import org.bukkit.scheduler.BukkitTask; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public class EntityProcessor { + + public Map> vehicles = new ConcurrentHashMap<>(); + public BukkitTask task; + + private void runEntityProcessor() { + Atlas.getInstance().getEntities().keySet().parallelStream() + .map(uuid -> Atlas.getInstance().getEntities().get(uuid)) + .filter(entity -> entity instanceof Vehicle) + .sequential() + .forEach(entity -> { + vehicles.compute(entity.getWorld().getUID(), (key, entities) -> { + if(entities == null) entities = new ArrayList<>(); + + entities.add(entity); + + return entities; + }); + }); + } + + public static EntityProcessor start() { + EntityProcessor processor = new EntityProcessor(); + processor.task = RunUtils.taskTimerAsync(processor::runEntityProcessor, Kauri.INSTANCE, 0L, 10L); + return processor; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/LunarClientProcessor.java b/Impl/src/main/java/dev/brighten/anticheat/processing/LunarClientProcessor.java new file mode 100644 index 000000000..c98054c9f --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/LunarClientProcessor.java @@ -0,0 +1,24 @@ +package dev.brighten.anticheat.processing; + +import cc.funkemunky.api.utils.Init; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import gg.manny.lunar.event.PlayerAuthenticateEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; + +@Init(requirePlugins = "LunarClientAPI") +public class LunarClientProcessor implements Listener { + + @EventHandler(priority = EventPriority.HIGHEST) + public void onAuth(PlayerAuthenticateEvent event) { + ObjectData data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + + if(data == null) { + Kauri.INSTANCE.dataManager.createData(event.getPlayer()); + data = Kauri.INSTANCE.dataManager.getData(event.getPlayer()); + } + data.usingLunar = true; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/MovementProcessor.java b/Impl/src/main/java/dev/brighten/anticheat/processing/MovementProcessor.java new file mode 100644 index 000000000..45326c767 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/MovementProcessor.java @@ -0,0 +1,465 @@ +package dev.brighten.anticheat.processing; + +import cc.funkemunky.api.Atlas; +import cc.funkemunky.api.reflections.impl.MinecraftReflection; +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.tinyprotocol.packet.types.MathHelper; +import cc.funkemunky.api.utils.*; +import cc.funkemunky.api.utils.handlers.PlayerSizeHandler; +import cc.funkemunky.api.utils.objects.VariableValue; +import cc.funkemunky.api.utils.objects.evicting.EvictingList; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import dev.brighten.anticheat.utils.MiscUtils; +import dev.brighten.anticheat.utils.MouseFilter; +import dev.brighten.anticheat.utils.MovementUtils; +import dev.brighten.anticheat.utils.TickTimer; +import lombok.val; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.potion.PotionEffectType; + +import java.math.RoundingMode; +import java.util.Deque; +import java.util.List; + +public class MovementProcessor { + private final ObjectData data; + + public Deque yawGcdList = new EvictingList<>(50), + pitchGcdList = new EvictingList<>(50); + public float deltaX, deltaY, lastDeltaX, lastDeltaY, smoothYaw, smoothPitch, lsmoothYaw, lsmoothPitch; + public Tuple, List> yawOutliers, pitchOutliers; + public long lastCinematic; + public float sensitivityX, sensitivityY, yawMode, pitchMode, sensXPercent, sensYPercent; + private MouseFilter mxaxis = new MouseFilter(), myaxis = new MouseFilter(); + private float smoothCamFilterX, smoothCamFilterY, smoothCamYaw, smoothCamPitch; + private TickTimer lastReset = new TickTimer(1), generalProcess = new TickTimer(3); + private GameMode lastGamemode; + public static float offset = (int)Math.pow(2, 24); + + public PotionEffectType levitation = null; + + public MovementProcessor(ObjectData data) { + this.data = data; + + if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_8)) { + try { + levitation = PotionEffectType.getByName("LEVITATION"); + } catch(Exception e) { + + } + } + } + + public void process(WrappedInFlyingPacket packet, long timeStamp) { + //We check if it's null and intialize the from and to as equal to prevent large deltas causing false positives since there + //was no previous from (Ex: delta of 380 instead of 0.45 caused by jump jump in location from 0,0,0 to 380,0,0) + if (data.playerInfo.from == null) { + data.playerInfo.from + = data.playerInfo.to + = new KLocation(packet.getX(), packet.getY(), packet.getZ(), packet.getYaw(), packet.getPitch()); + } else { + data.playerInfo.from = new KLocation( + data.playerInfo.to.x, + data.playerInfo.to.y, + data.playerInfo.to.z, + data.playerInfo.to.yaw, + data.playerInfo.to.pitch, + data.playerInfo.to.timeStamp); + } + + //We set the to x,y,z like this to prevent inaccurate data input. Because if it isnt a positional packet, + // it returns getX, getY, getZ as 0. + if (packet.isPos()) { + data.playerInfo.to.x = packet.getX(); + data.playerInfo.to.y = packet.getY(); + data.playerInfo.to.z = packet.getZ(); + //if this is the case, this assumes client movement in between therefore we have to calculate where ground would be. + } else if(packet.isGround() && !data.playerInfo.clientGround) { //this is the last ground + val optional = data.blockInfo.belowCollisions.stream() + .filter(box -> Math.pow(box.yMax - data.playerInfo.to.y, 2) <= 9.0E-4D && data.box.copy() + .offset(0, -.1, 0).isCollided(box)).findFirst(); + + if(optional.isPresent()) { + data.playerInfo.to.y-= data.playerInfo.to.y - optional.get().yMax; + data.playerInfo.clientGround = data.playerInfo.serverGround = true; + } + } + + data.playerInfo.to.timeStamp = timeStamp; + //Adding past location + data.pastLocation.addLocation(data.playerInfo.to); + + if (data.playerInfo.posLocs.size() > 0 && !packet.isGround()) { + val optional = data.playerInfo.posLocs.stream() + .filter(loc -> loc.x == packet.getX() && loc.y == packet.getY() && loc.z == packet.getZ()) + .findFirst(); + + if (optional.isPresent()) { + data.playerInfo.serverPos = true; + data.playerInfo.lastServerPos = timeStamp; + data.playerInfo.lastTeleportTimer.reset(); + data.playerInfo.inventoryOpen = false; + data.playerInfo.posLocs.remove(optional.get()); + } + } else if (data.playerInfo.serverPos) { + data.playerInfo.serverPos = false; + } + + data.playerInfo.lClientGround = data.playerInfo.clientGround; + data.playerInfo.clientGround = packet.isGround(); + //Setting the motion delta for use in checks to prevent repeated functions. + data.playerInfo.lDeltaX = data.playerInfo.deltaX; + data.playerInfo.lDeltaY = data.playerInfo.deltaY; + data.playerInfo.lDeltaZ = data.playerInfo.deltaZ; + data.playerInfo.deltaX = data.playerInfo.serverPos ? 0 : data.playerInfo.to.x - data.playerInfo.from.x; + data.playerInfo.deltaY = data.playerInfo.serverPos ? 0 : data.playerInfo.to.y - data.playerInfo.from.y; + data.playerInfo.deltaZ = data.playerInfo.serverPos ? 0 : data.playerInfo.to.z - data.playerInfo.from.z; + data.playerInfo.lDeltaXZ = data.playerInfo.deltaXZ; + data.playerInfo.deltaXZ = data.playerInfo.serverPos ? 0 : MathUtils.hypot(data.playerInfo.deltaX, data.playerInfo.deltaZ); + + data.playerInfo.blockOnTo = BlockUtils.getBlock(data.playerInfo.to.toLocation(data.getPlayer().getWorld())); + data.playerInfo.blockBelow = BlockUtils.getBlock(data.playerInfo.to.toLocation(data.getPlayer().getWorld()) + .subtract(0, 1, 0)); + + if(!data.getPlayer().getGameMode().equals(lastGamemode)) data.playerInfo.lastGamemodeTimer.reset(); + lastGamemode = data.getPlayer().getGameMode(); + data.playerInfo.creative = !data.getPlayer().getGameMode().equals(GameMode.SURVIVAL) + && !data.getPlayer().getGameMode().equals(GameMode.ADVENTURE); + + if(data.playerInfo.blockBelow != null) + data.blockInfo.currentFriction = MinecraftReflection.getFriction(data.playerInfo.blockBelow); + + Block block = BlockUtils.getBlock(new Location(data.getPlayer().getWorld(), + data.playerInfo.from.x, data.playerInfo.from.y - 1, data.playerInfo.from.z)); + + if(block != null) + data.blockInfo.fromFriction = MinecraftReflection.getFriction(block); + + if(packet.isPos()) { + //We create a separate from BoundingBox for the predictionService since it should operate on pre-motion data. + data.box = PlayerSizeHandler.instance.bounds(data.getPlayer(), + data.playerInfo.to.x, data.playerInfo.to.y, data.playerInfo.to.z); + + if(timeStamp - data.creation > 400L) data.blockInfo.runCollisionCheck(); //run b4 everything else for use below. + } + + if(MathUtils.getDelta(deltaY, -0.098) < 0.001) { + data.playerInfo.worldLoaded = false; + } + data.playerInfo.inVehicle = data.getPlayer().getVehicle() != null; + data.playerInfo.gliding = PlayerUtils.isGliding(data.getPlayer()); + data.playerInfo.riptiding = Atlas.getInstance().getBlockBoxManager() + .getBlockBox().isRiptiding(data.getPlayer()); + /* We only set the jumpheight on ground since there's no need to check for it while they're in the air. + * If we did check while it was in the air, there would be false positives in the checks that use it. */ + if (packet.isGround() || data.playerInfo.serverGround || data.playerInfo.lClientGround) { + data.playerInfo.jumpHeight = MovementUtils.getJumpHeight(data); + data.playerInfo.totalHeight = MovementUtils.getTotalHeight(data.playerVersion, + (float)data.playerInfo.jumpHeight); + } + + if(Atlas.getInstance().getBlockBoxManager().getBlockBox() + .isChunkLoaded(data.playerInfo.to.toLocation(data.getPlayer().getWorld()))) + data.playerInfo.lastChunkUnloaded.reset(); + + data.playerInfo.lworldLoaded = data.playerInfo.worldLoaded; + + if(MathUtils.getDelta(data.playerInfo.deltaY, -0.098) < 0.0001 + && data.playerInfo.lastChunkUnloaded.hasNotPassed(35)) + data.playerInfo.worldLoaded = false; + else data.playerInfo.worldLoaded = true; + + + data.lagInfo.lagging = data.lagInfo.lagTicks.subtract() > 0 + || !data.playerInfo.worldLoaded + || timeStamp - Kauri.INSTANCE.lastTick > + new VariableValue<>(110, 60, ProtocolVersion::isPaper).get(); + + if(data.playerInfo.insideBlock = (data.playerInfo.blockOnTo != null && data.playerInfo.blockOnTo.getType() + .equals(XMaterial.AIR.parseMaterial()))) { + data.playerInfo.lastInsideBlock.reset(); + } + + //We set the yaw and pitch like this to prevent inaccurate data input. Like above, it will return both pitch + //and yaw as 0 if it isnt a look packet. + if(packet.isLook()) { + data.playerInfo.to.yaw = packet.getYaw(); + data.playerInfo.to.pitch = packet.getPitch(); + } + + //Setting the angle delta for use in checks to prevent repeated functions. + data.playerInfo.lDeltaYaw = data.playerInfo.deltaYaw; + data.playerInfo.lDeltaPitch = data.playerInfo.deltaPitch; + data.playerInfo.deltaYaw = data.playerInfo.to.yaw + - data.playerInfo.from.yaw; + data.playerInfo.deltaPitch = data.playerInfo.to.pitch - data.playerInfo.from.pitch; + if (packet.isLook()) { + + data.playerInfo.lastPitchGCD = data.playerInfo.pitchGCD; + data.playerInfo.lastYawGCD = data.playerInfo.yawGCD; + data.playerInfo.yawGCD = MiscUtils.gcd((int) (data.playerInfo.deltaYaw * offset), + (int) (data.playerInfo.lDeltaYaw * offset)); + data.playerInfo.pitchGCD = MiscUtils.gcd((int) (data.playerInfo.deltaPitch * offset), + (int) (data.playerInfo.lDeltaPitch * offset)); + + val origin = data.playerInfo.to.clone(); + + origin.y+= data.playerInfo.sneaking ? 1.54 : 1.62; + + if(data.playerInfo.lastTeleportTimer.hasPassed(1)) { + float yawGcd = data.playerInfo.yawGCD / offset, pitchGcd = data.playerInfo.pitchGCD / offset; + + //Adding gcd of yaw and pitch. + if (data.playerInfo.yawGCD > 160000 && data.playerInfo.yawGCD < 10500000) + yawGcdList.add(yawGcd); + if (data.playerInfo.pitchGCD > 160000 && data.playerInfo.pitchGCD < 10500000) + pitchGcdList.add(pitchGcd); + + if (yawGcdList.size() > 3 && pitchGcdList.size() > 3) { + + //Making sure to get shit within the std for a more accurate result. + if (lastReset.hasPassed()) { + yawMode = MathUtils.getMode(yawGcdList); + pitchMode = MathUtils.getMode(pitchGcdList); + yawOutliers = MiscUtils.getOutliers(yawGcdList); + pitchOutliers = MiscUtils.getOutliers(pitchGcdList); + lastReset.reset(); + sensXPercent = sensToPercent(sensitivityX = getSensitivityFromYawGCD(yawMode)); + sensYPercent = sensToPercent(sensitivityY = getSensitivityFromPitchGCD(pitchMode)); + } + + + lastDeltaX = deltaX; + lastDeltaY = deltaY; + deltaX = getDeltaX(data.playerInfo.deltaYaw, yawMode); + deltaY = getDeltaY(data.playerInfo.deltaPitch, pitchMode); + + if ((data.playerInfo.pitchGCD < 1E5 || data.playerInfo.yawGCD < 1E5) && smoothCamFilterY < 1E6 + && smoothCamFilterX < 1E6 && timeStamp - data.creation > 1000L) { + float f = sensitivityX * 0.6f + .2f; + float f1 = f * f * f * 8; + float f2 = deltaX * f1; + float f3 = deltaY * f1; + + smoothCamFilterX = mxaxis.smooth(smoothCamYaw, .05f * f1); + smoothCamFilterY = myaxis.smooth(smoothCamPitch, .05f * f1); + + this.smoothCamYaw += f2; + this.smoothCamFilterY += f3; + + f2 = smoothCamFilterX * 0.5f; + f3 = smoothCamFilterY * 0.5f; + + float pyaw = data.playerInfo.from.yaw + f2 * .15f; + float ppitch = data.playerInfo.from.pitch - f3 * .15f; + + this.lsmoothYaw = smoothYaw; + this.lsmoothPitch = smoothPitch; + this.smoothYaw = pyaw; + this.smoothPitch = ppitch; + + float yaccel = Math.abs(data.playerInfo.deltaYaw) - Math.abs(data.playerInfo.lDeltaYaw), + pAccel = Math.abs(data.playerInfo.deltaPitch) - Math.abs(data.playerInfo.lDeltaPitch); + + if (MathUtils.getDelta(smoothYaw, data.playerInfo.from.yaw) > (yaccel > 0 ? (yaccel > 10 ? 3 : 1) : 0.1) + || MathUtils.getDelta(smoothPitch, data.playerInfo.from.pitch) > (pAccel > 0 ? (yaccel > 10 ? 3 : 1) : 0.1)) { + smoothCamYaw = smoothCamPitch = 0; + data.playerInfo.cinematicMode = false; + mxaxis.reset(); + myaxis.reset(); + } else data.playerInfo.cinematicMode = true; + + //MiscUtils.testMessage("pyaw=" + pyaw + " ppitch=" + ppitch + " yaw=" + data.playerInfo.to.yaw + " pitch=" + data.playerInfo.to.pitch); + } else { + mxaxis.reset(); + myaxis.reset(); + data.playerInfo.cinematicMode = false; + } + } + } else { + yawGcdList.clear(); + pitchGcdList.clear(); + } + } else { + smoothCamYaw = smoothCamPitch = 0; + } + + if (packet.isPos()) { + if (data.playerInfo.serverGround && data.playerInfo.groundTicks > 4) + data.playerInfo.groundLoc = data.playerInfo.to; + } + + //Fixes glitch when logging in. + //We use the NMS (bukkit) version since their state is likely saved in a player data file in the world. + //This should prevent false positives from ability inaccuracies. + if (timeStamp - data.creation < 500L) { + if (data.playerInfo.canFly != data.getPlayer().getAllowFlight()) { + data.playerInfo.lastToggleFlight.reset(); + } + data.playerInfo.canFly = data.getPlayer().getAllowFlight(); + data.playerInfo.flying = data.getPlayer().isFlying(); + } + + data.playerInfo.serverAllowedFlight = data.getPlayer().getAllowFlight(); + if (data.playerInfo.breakingBlock) data.playerInfo.lastBrokenBlock.reset(); + + //Setting fallDistance + if (!data.playerInfo.serverGround + && data.playerInfo.deltaY < 0 + && !data.blockInfo.onClimbable + && !data.blockInfo.inLiquid + && !data.blockInfo.inWeb) { + data.playerInfo.fallDistance += -data.playerInfo.deltaY; + } else data.playerInfo.fallDistance = 0; + + //Running jump check + if (!data.playerInfo.clientGround) { + if (!data.playerInfo.jumped && data.playerInfo.lClientGround + && data.playerInfo.deltaY >= 0 + && data.playerInfo.deltaY <= data.playerInfo.jumpHeight) { + data.playerInfo.jumped = true; + } else { + data.playerInfo.inAir = true; + data.playerInfo.jumped = false; + } + } else data.playerInfo.jumped = data.playerInfo.inAir = false; + + /* General Block Info */ + + //Setting if players were on blocks when on ground so it can be used with checks that check air things. + if (data.playerInfo.serverGround || data.playerInfo.clientGround || data.playerInfo.collided) { + data.playerInfo.wasOnIce = data.blockInfo.onIce; + data.playerInfo.wasOnSlime = data.blockInfo.onSlime; + } + + if((data.playerInfo.onLadder = MovementUtils.isOnLadder(data)) + && (data.playerInfo.deltaY <= 0 || data.blockInfo.collidesHorizontally)) { + data.playerInfo.isClimbing = true; + } + + //Checking if user is in liquid. + if (data.blockInfo.inLiquid) data.playerInfo.liquidTimer.reset(); + //Half block ticking (slabs, stairs, bed, cauldron, etc.) + if (data.blockInfo.onHalfBlock) data.playerInfo.lastHalfBlock.reset(); + //We dont check if theyre still on ice because this would be useless to checks that check a player in air too. + if (data.blockInfo.onIce) data.playerInfo.iceTimer.reset(); + if (data.blockInfo.inWeb) data.playerInfo.webTimer.reset(); + if (data.blockInfo.onClimbable) data.playerInfo.climbTimer.reset(); + if (data.blockInfo.onSlime) data.playerInfo.slimeTimer.reset(); + if (data.blockInfo.onSoulSand) data.playerInfo.soulSandTimer.reset(); + if (data.blockInfo.blocksAbove) data.playerInfo.blockAboveTimer.reset(); + + //Player ground/air positioning ticks. + if (!data.playerInfo.serverGround) { + data.playerInfo.airTicks++; + data.playerInfo.groundTicks = 0; + } else { + data.playerInfo.groundTicks++; + data.playerInfo.airTicks = 0; + } + + data.playerInfo.baseSpeed = MovementUtils.getBaseSpeed(data); + /* General Cancel Booleans */ + boolean hasLevi = levitation != null && data.potionProcessor.hasPotionEffect(levitation); + + data.playerInfo.generalCancel = data.getPlayer().getAllowFlight() + || data.playerInfo.creative + || hasLevi + || (MathUtils.getDelta(-0.098, data.playerInfo.deltaY) < 0.001 + && data.playerInfo.lastChunkUnloaded.hasNotPassed(60)) + || data.playerInfo.serverPos + || data.playerInfo.riptiding + || data.playerInfo.lastTeleportTimer.hasNotPassed(1) + || data.playerInfo.gliding + || data.playerInfo.lastPlaceLiquid.hasNotPassed(5) + || data.playerInfo.inVehicle + || !data.playerInfo.worldLoaded + || timeStamp - data.playerInfo.lastRespawn < 2500L + || data.playerInfo.lastToggleFlight.hasNotPassed(40) + || timeStamp - data.creation < 4000 + || Kauri.INSTANCE.lastTickLag.hasNotPassed(5); + + data.playerInfo.flightCancel = data.playerInfo.generalCancel + || data.playerInfo.webTimer.hasNotPassed(8) + || data.playerInfo.liquidTimer.hasNotPassed(8) + || data.playerInfo.onLadder + || data.playerInfo.slimeTimer.hasNotPassed(8) + || data.playerInfo.climbTimer.hasNotPassed(6) + || data.playerInfo.lastHalfBlock.hasNotPassed(5); + } + private static float getDeltaX(float yawDelta, float gcd) { + return MathHelper.floor(yawDelta / gcd); + } + + private static float getDeltaY(float pitchDelta, float gcd) { + return MathHelper.floor(pitchDelta / gcd); + } + + public static int sensToPercent(float sensitivity) { + return (int) MathUtils.round( + sensitivity / .5f * 100, 0, + RoundingMode.HALF_UP); + } + + //Noncondensed + /*private static double getSensitivityFromYawGCD(double gcd) { + double stepOne = yawToF2(gcd) / 8; + double stepTwo = Math.cbrt(stepOne); + double stepThree = stepTwo - .2f; + return stepThree / .6f; + }*/ + + //Condensed + public static float getSensitivityFromYawGCD(float gcd) { + return ((float)Math.cbrt((double)yawToF2(gcd) / 8) - .2f) / .6f; + } + + //Noncondensed + /*private static double getSensitivityFromPitchGCD(double gcd) { + double stepOne = pitchToF3(gcd) / 8; + double stepTwo = Math.cbrt(stepOne); + double stepThree = stepTwo - .2f; + return stepThree / .6f; + }*/ + + //Condensed + private static float getSensitivityFromPitchGCD(float gcd) { + return ((float)Math.cbrt((double)pitchToF3(gcd) / 8) - .2f) / .6f; + } + + private static float getF1FromYaw(float gcd) { + float f = getFFromYaw(gcd); + + return f * f * f * 8; + } + + private static float getFFromYaw(float gcd) { + float sens = getSensitivityFromYawGCD(gcd); + return sens * .6f + .2f; + } + + private static float getFFromPitch(float gcd) { + float sens = getSensitivityFromPitchGCD(gcd); + return sens * .6f + .2f; + } + + private static float getF1FromPitch(float gcd) { + float f = getFFromPitch(gcd); + + return (float)Math.pow(f, 3) * 8; + } + + private static float yawToF2(float yawDelta) { + return (float)((double)yawDelta / .15); + } + + private static float pitchToF3(float pitchDelta) { + int b0 = pitchDelta >= 0 ? 1 : -1; //Checking for inverted mouse. + return (float)((double)(pitchDelta / b0) / .15); + } + +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/PacketProcessor.java b/Impl/src/main/java/dev/brighten/anticheat/processing/PacketProcessor.java new file mode 100644 index 000000000..a39fb6638 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/PacketProcessor.java @@ -0,0 +1,513 @@ +package dev.brighten.anticheat.processing; + +import cc.funkemunky.api.events.impl.PacketReceiveEvent; +import cc.funkemunky.api.events.impl.PacketSendEvent; +import cc.funkemunky.api.reflections.impl.MinecraftReflection; +import cc.funkemunky.api.reflections.types.WrappedField; +import cc.funkemunky.api.tinyprotocol.api.Packet; +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.tinyprotocol.api.TinyProtocolHandler; +import cc.funkemunky.api.tinyprotocol.packet.in.*; +import cc.funkemunky.api.tinyprotocol.packet.out.*; +import cc.funkemunky.api.utils.KLocation; +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import lombok.val; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +public class PacketProcessor { + + public synchronized void processClient(PacketReceiveEvent event, ObjectData data, Object object, String type, + long timeStamp) { + Kauri.INSTANCE.profiler.start("packet:client:" + getType(type)); + switch (type) { + case Packet.Client.ABILITIES: { + WrappedInAbilitiesPacket packet = new WrappedInAbilitiesPacket(object, data.getPlayer()); + + data.playerInfo.flying = packet.isFlying(); + + if (data.playerInfo.canFly != packet.isAllowedFlight()) { + data.playerInfo.lastToggleFlight.reset(); + } + + data.predictionService.fly = packet.isAllowedFlight(); + data.predictionService.walkSpeed = packet.getWalkSpeed(); + + data.playerInfo.canFly = packet.isAllowedFlight(); + data.checkManager.runPacket(packet, timeStamp); + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + + packet.getWalkSpeed() + ";" + packet.getFlySpeed() + ";" + packet.isAllowedFlight() + + ";" + packet.isCreativeMode() + "; " + packet.isInvulnerable() + ";" + packet.isFlying() + + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.USE_ENTITY: { + WrappedInUseEntityPacket packet = new WrappedInUseEntityPacket(object, data.getPlayer()); + + if (packet.getAction().equals(WrappedInUseEntityPacket.EnumEntityUseAction.ATTACK)) { + data.playerInfo.lastAttack.reset(); + data.playerInfo.lastAttackTimeStamp = timeStamp; + + if (packet.getEntity() instanceof LivingEntity) { + if (data.target != null && data.target.getEntityId() != packet.getId()) { + //Resetting location to prevent false positives. + data.targetPastLocation.previousLocations.clear(); + data.playerInfo.lastTargetSwitch.reset(); + if (packet.getEntity() instanceof Player) { + data.targetData = Kauri.INSTANCE.dataManager.getData((Player) packet.getEntity()); + } else data.targetData = null; + } + + if (data.target == null && packet.getEntity() instanceof Player) + data.targetData = Kauri.INSTANCE.dataManager.getData((Player) packet.getEntity()); + data.target = (LivingEntity) packet.getEntity(); + } + data.predictionService.hit = true; + data.playerInfo.usingItem = data.predictionService.useSword = false; + } + data.checkManager.runPacket(packet, timeStamp); + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + packet.getId() + ";" + packet.getAction().name() + + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.FLYING: + case Packet.Client.POSITION: + case Packet.Client.POSITION_LOOK: + case Packet.Client.LOOK: { + WrappedInFlyingPacket packet = new WrappedInFlyingPacket(object, data.getPlayer()); + + if (timeStamp - data.lagInfo.lastFlying <= 2) { + data.lagInfo.lastPacketDrop.reset(); + } + + data.lagInfo.lastFlying = timeStamp; + + data.potionProcessor.onFlying(packet); + Kauri.INSTANCE.profiler.start("data:moveprocessor"); + data.moveProcessor.process(packet, timeStamp); + Kauri.INSTANCE.profiler.stop("data:moveprocessor"); + data.predictionService.onReceive(packet); //Processing for prediction service. + + data.checkManager.runPacket(packet, timeStamp); + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + + packet.getX() + ";" + packet.getY() + ";" + packet.getZ() + ";" + + packet.getYaw() + ";" + packet.getPitch() + ";" + packet.isGround() + + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.ENTITY_ACTION: { + WrappedInEntityActionPacket packet = new WrappedInEntityActionPacket(object, data.getPlayer()); + + ActionProcessor.process(data, packet); + data.checkManager.runPacket(packet, timeStamp); + + //MiscUtils.testMessage(data.getPlayer().getName() + ": " + packet.getAction()); + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + packet.getAction().name() + + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.BLOCK_DIG: { + WrappedInBlockDigPacket packet = new WrappedInBlockDigPacket(object, data.getPlayer()); + + switch (packet.getAction()) { + case START_DESTROY_BLOCK: { + data.predictionService.useSword + = data.playerInfo.usingItem = false; + break; + } + case STOP_DESTROY_BLOCK: + case ABORT_DESTROY_BLOCK: { + data.predictionService.useSword + = data.playerInfo.usingItem = false; + break; + } + case RELEASE_USE_ITEM: { + data.predictionService.useSword + = data.playerInfo.usingItem = false; + break; + } + case DROP_ALL_ITEMS: + case DROP_ITEM: { + data.predictionService.useSword + = data.playerInfo.usingItem = false; + data.predictionService.dropItem = true; + break; + } + } + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + + packet.getAction().name() + ";" + packet.getDirection().name() + ";" + + "(" + packet.getPosition().getX() + "," + + packet.getPosition().getY() + "," + packet.getPosition().getZ() + ")" + + ":@:" + event.getTimeStamp()); + } + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Client.BLOCK_PLACE: { + WrappedInBlockPlacePacket packet = new WrappedInBlockPlacePacket(object, data.getPlayer()); + + if (event.getPlayer().getItemInHand() != null) { + if(event.getPlayer().getItemInHand().getType().name().contains("BUCKET")) { + data.playerInfo.lastPlaceLiquid.reset(); + } + val pos = packet.getPosition(); + val stack = packet.getItemStack(); + + if(pos.getX() == -1 && (pos.getY() == 255 || pos.getY() == -1) + && pos.getZ() == -1 && stack != null + && (stack.getType().name().contains("SWORD") + || stack.getType().equals(XMaterial.BOW.parseMaterial()))) { + data.predictionService.useSword = data.playerInfo.usingItem = true; + data.playerInfo.lastUseItem.reset(); + } else if(stack != null) { + if(stack.getType().isBlock() && stack.getType().getId() != 0) { + data.playerInfo.lastBlockPlace.reset(); + // MiscUtils.testMessage(event.getPlayer().getItemInHand().getType().name()); + } + } + } + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + + (packet.getItemStack() != null ? packet.getItemStack().toString() : "NULL") + + ";(" + packet.getPosition().getX() + "," + + packet.getPosition().getY() + "," + packet.getPosition().getZ() + ");" + + packet.getFace().name() + ";" + + packet.getVecX() + "," + packet.getVecY() + "," + packet.getVecZ() + + ":@:" + event.getTimeStamp()); + } + + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Client.KEEP_ALIVE: { + WrappedInKeepAlivePacket packet = new WrappedInKeepAlivePacket(object, data.getPlayer()); + + long time = packet.getTime(); + + if(data.keepAlives.containsKey(time)) { + long last = data.keepAlives.get(time); + + data.lagInfo.lastPing = data.lagInfo.ping; + data.lagInfo.ping = event.getTimeStamp() - last; + + data.lagInfo.pingAverages.add(data.lagInfo.ping); + data.lagInfo.averagePing = data.lagInfo.pingAverages.getAverage(); + } + + data.checkManager.runPacket(packet, timeStamp); + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + time + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.CLIENT_COMMAND: { + WrappedInClientCommandPacket packet = new WrappedInClientCommandPacket(object, data.getPlayer()); + + if(packet.getCommand() + .equals(WrappedInClientCommandPacket.EnumClientCommand.OPEN_INVENTORY_ACHIEVEMENT)) { + data.playerInfo.inventoryOpen = true; + } + + data.checkManager.runPacket(packet, timeStamp); + + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + packet.getCommand().name() + + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.TRANSACTION: { + WrappedInTransactionPacket packet = new WrappedInTransactionPacket(object, data.getPlayer()); + + Kauri.INSTANCE.keepaliveProcessor.addResponse(data, packet.getAction()); + + val optional = Kauri.INSTANCE.keepaliveProcessor.getResponse(data); + + int current = Kauri.INSTANCE.keepaliveProcessor.tick; + + optional.ifPresent(ka -> { + data.lagInfo.lastTransPing = data.lagInfo.transPing; + data.lagInfo.transPing = (current - ka.start); + + data.clickProcessor.onFlying(packet); + + ka.getReceived(data.uuid).ifPresent(r -> { + r.receivedStamp = data.lagInfo.recieved = event.getTimeStamp(); + data.lagInfo.lmillisPing = data.lagInfo.millisPing; + data.lagInfo.millisPing = r.receivedStamp - (data.lagInfo.start = ka.startStamp); + }); + + for (ObjectData.Action action : data.keepAliveStamps) { + if(action.stamp > ka.start) continue; + + action.action.accept(ka); + + data.keepAliveStamps.remove(action); + } + }); + + data.lagInfo.lastClientTrans = timeStamp; + + data.checkManager.runPacket(packet, timeStamp); + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + packet.getAction() + ";" + packet.getId() + + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.ARM_ANIMATION: { + WrappedInArmAnimationPacket packet = new WrappedInArmAnimationPacket(object, data.getPlayer()); + + data.clickProcessor.onArm(packet, timeStamp); + data.checkManager.runPacket(packet, timeStamp); + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.SETTINGS: { + WrappedInSettingsPacket packet = new WrappedInSettingsPacket(object, data.getPlayer()); + + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Client.HELD_ITEM_SLOT: { + WrappedInHeldItemSlotPacket packet = new WrappedInHeldItemSlotPacket(object, data.getPlayer()); + + data.predictionService.useSword = data.playerInfo.usingItem = false; + data.checkManager.runPacket(packet, timeStamp); + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + + packet.getSlot() + + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.TAB_COMPLETE: { + WrappedInTabComplete packet = new WrappedInTabComplete(event.getPacket(), event.getPlayer()); + + if(packet.getMessage().startsWith("/yourmom")) { + WrappedOutTabComplete tabComplete; + if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_13)) { + tabComplete = new WrappedOutTabComplete(0, packet.getMessage(), "gay", "homo"); + } else { + tabComplete = new WrappedOutTabComplete("gay", "homo"); + } + + TinyProtocolHandler.sendPacket(event.getPlayer(), tabComplete.getObject()); + } + break; + } + case Packet.Client.WINDOW_CLICK: { + WrappedInWindowClickPacket packet = new WrappedInWindowClickPacket(object, data.getPlayer()); + + data.predictionService.useSword = data.playerInfo.usingItem = false; + data.playerInfo.lastWindowClick.reset(); + data.checkManager.runPacket(packet, timeStamp); + + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + + packet.getAction().name() + ";" + packet.getButton() + ";" + packet.getId() + + ";" + (packet.getItem() != null ? packet.getItem().toString() : "NULL") + + ";" + packet.getMode() + ";" + packet.getCounter() + + ":@:" + event.getTimeStamp()); + } + break; + } + case Packet.Client.CLOSE_WINDOW: { + data.predictionService.useSword = data.playerInfo.usingItem = false; + data.playerInfo.inventoryOpen = false; + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + event.getTimeStamp()); + } + break; + } + default: { + if(data.sniffing) { + data.sniffedPackets.add(event.getType() + ":@:" + event.getTimeStamp()); + } + break; + } + } + Kauri.INSTANCE.profiler.stop("packet:client:" + getType(type)); + } + + public synchronized void processServer(PacketSendEvent event, ObjectData data, Object object, String type, long timeStamp) { + Kauri.INSTANCE.profiler.start("packet:server:" + type); + + switch (type) { + case Packet.Server.ABILITIES: { + WrappedOutAbilitiesPacket packet = new WrappedOutAbilitiesPacket(object, data.getPlayer()); + + if (data.playerInfo.canFly != packet.isAllowedFlight()) { + data.playerInfo.lastToggleFlight.reset(); + } + + data.playerInfo.canFly = packet.isAllowedFlight(); + data.playerInfo.flying = packet.isFlying(); + data.predictionService.fly = packet.isAllowedFlight(); + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Server.RESPAWN: { + WrappedOutRespawnPacket packet = new WrappedOutRespawnPacket(object, data.getPlayer()); + + data.playerInfo.lastRespawn = timeStamp; + data.playerInfo.lastRespawnTimer.reset(); + data.runKeepaliveAction(d -> { + data.playerInfo.lastRespawn = timeStamp; + data.playerInfo.lastRespawnTimer.reset(); + }); + + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Server.HELD_ITEM: { + WrappedOutHeldItemSlot packet = new WrappedOutHeldItemSlot(object, data.getPlayer()); + + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Server.ENTITY_EFFECT: { + WrappedOutEntityEffectPacket packet = new WrappedOutEntityEffectPacket(object, data.getPlayer()); + + if(packet.entityId == data.getPlayer().getEntityId()) { + data.potionProcessor.onPotionEffect(packet); + data.checkManager.runPacket(packet, timeStamp); + } + break; + } + case Packet.Server.ENTITY_METADATA: { + WrappedOutEntityMetadata packet = new WrappedOutEntityMetadata(object, data.getPlayer()); + + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Server.CLOSE_WINDOW: { + WrappedOutCloseWindowPacket packet = new WrappedOutCloseWindowPacket(object, data.getPlayer()); + data.playerInfo.inventoryOpen = false; + data.playerInfo.inventoryId = 0; + + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Server.OPEN_WINDOW: { + WrappedOutOpenWindow packet = new WrappedOutOpenWindow(object, data.getPlayer()); + + data.playerInfo.inventoryOpen = true; + data.playerInfo.inventoryId = packet.getId(); + break; + } + case Packet.Server.ENTITY_VELOCITY: { + WrappedOutVelocityPacket packet = new WrappedOutVelocityPacket(object, data.getPlayer()); + + if (packet.getId() == data.getPlayer().getEntityId()) { + //Setting velocity action. + data.playerInfo.velocityX = (float) packet.getX(); + data.playerInfo.velocityY = (float) packet.getY(); + data.playerInfo.velocityZ = (float) packet.getZ(); + data.runKeepaliveAction(d -> { + data.playerInfo.lastVelocity.reset(); + data.playerInfo.lastVelocityTimestamp = System.currentTimeMillis(); + data.predictionService.rmotionX = data.playerInfo.velocityX; + data.predictionService.rmotionZ = data.playerInfo.velocityZ; + data.predictionService.velocity = true; + }); + } + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Server.ENTITY_HEAD_ROTATION: { + WrappedOutEntityHeadRotation packet = new WrappedOutEntityHeadRotation(object, data.getPlayer()); + + data.playerInfo.headYaw = packet.getPlayer().getLocation().getYaw(); + data.playerInfo.headPitch = packet.getPlayer().getLocation().getPitch(); + data.checkManager.runPacket(packet, timeStamp); + break; + } + case "PacketPlayOutRelEntityMove": + case "PacketPlayOutEntityLook": + case Packet.Server.ENTITY: + case "PacketPlayOutRelEntityMoveLook": + case "PacketPlayOutEntity$PacketPlayOutRelEntityMove": + case "PacketPlayOutEntity$PacketPlayOutRelEntityMoveLook": + case "PacketPlayOutEntity$PacketPlayOutEntityLook": { + WrappedOutRelativePosition packet = new WrappedOutRelativePosition(object, data.getPlayer()); + + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Server.KEEP_ALIVE: { + WrappedOutKeepAlivePacket packet = new WrappedOutKeepAlivePacket(object, data.getPlayer()); + + data.keepAlives.put(packet.getTime(), event.getTimeStamp()); + data.lagInfo.lastKeepAlive = event.getTimeStamp(); + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Server.TRANSACTION: { + WrappedOutTransaction packet = new WrappedOutTransaction(object, data.getPlayer()); + + if (packet.getAction() == (short)69) { + data.lagInfo.lastTrans = event.getTimeStamp(); + } + + data.checkManager.runPacket(packet, timeStamp); + break; + } + case Packet.Server.POSITION: { + WrappedOutPositionPacket packet = new WrappedOutPositionPacket(object, data.getPlayer()); + + KLocation loc = new KLocation(packet.getX(), packet.getY(), packet.getZ(), + packet.getYaw(), packet.getPitch()); + + if(packet.getFlags().contains(WrappedOutPositionPacket.EnumPlayerTeleportFlags.X)) { + loc.x+= data.playerInfo.to.x; + } + if(packet.getFlags().contains(WrappedOutPositionPacket.EnumPlayerTeleportFlags.Y)) { + loc.y+= data.playerInfo.to.y; + } + if(packet.getFlags().contains(WrappedOutPositionPacket.EnumPlayerTeleportFlags.Z)) { + loc.z+= data.playerInfo.to.z; + } + if(packet.getFlags().contains(WrappedOutPositionPacket.EnumPlayerTeleportFlags.X_ROT)) { + loc.pitch+= data.playerInfo.to.pitch; + } + if(packet.getFlags().contains(WrappedOutPositionPacket.EnumPlayerTeleportFlags.Y_ROT)) { + loc.yaw+= data.playerInfo.to.yaw; + } + + data.playerInfo.posLocs.add(loc); + data.playerInfo.lastServerPos = timeStamp; + data.checkManager.runPacket(packet, timeStamp); + break; + } + } + Kauri.INSTANCE.profiler.stop("packet:server:" + type); + } + + private static String getType(String type) { + switch (type) { + case Packet.Client.FLYING: + case Packet.Client.POSITION: + case Packet.Client.POSITION_LOOK: + case Packet.Client.LOOK: { + return "PacketPlayInFlying"; + } + default: { + return type; + } + } + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/PotionProcessor.java b/Impl/src/main/java/dev/brighten/anticheat/processing/PotionProcessor.java new file mode 100644 index 000000000..12537c026 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/PotionProcessor.java @@ -0,0 +1,54 @@ +package dev.brighten.anticheat.processing; + +import cc.funkemunky.api.tinyprotocol.api.TinyProtocolHandler; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInFlyingPacket; +import cc.funkemunky.api.tinyprotocol.packet.in.WrappedInTransactionPacket; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutEntityEffectPacket; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutTransaction; +import dev.brighten.anticheat.data.ObjectData; +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +@RequiredArgsConstructor +public class PotionProcessor { + private final ObjectData data; + + public List potionEffects = new CopyOnWriteArrayList<>(); + + public void onFlying(WrappedInFlyingPacket packet) { + for (PotionEffect effect : potionEffects) { + if(packet.getPlayer().hasPotionEffect(effect.getType())) continue; + + data.runKeepaliveAction(d -> { + data.potionProcessor.potionEffects.remove(effect); + }); + } + } + + public void onPotionEffect(WrappedOutEntityEffectPacket packet) { + data.runKeepaliveAction(d -> { + val type = PotionEffectType.getById(packet.effectId); + data.potionProcessor.potionEffects.stream().filter(pe -> pe.getType().equals(type)) + .forEach(data.potionProcessor.potionEffects::remove); + data.potionProcessor.potionEffects + .add(new PotionEffect(type, packet.duration, packet.amplifier, + (packet.flags & 1) == 1, (packet.flags & 2) == 2)); + }); + } + + public boolean hasPotionEffect(PotionEffectType type) { + return potionEffects.stream().anyMatch(effect -> effect.getType().equals(type)); + } + + public PotionEffect getEffectByType(PotionEffectType type) { + return potionEffects.stream().filter(effect -> effect.getType().equals(type)).findFirst().orElse(null); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/TagsBuilder.java b/Impl/src/main/java/dev/brighten/anticheat/processing/TagsBuilder.java new file mode 100644 index 000000000..1809cc79c --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/TagsBuilder.java @@ -0,0 +1,18 @@ +package dev.brighten.anticheat.processing; + +import java.util.ArrayList; +import java.util.List; + +public class TagsBuilder { + private final List tags = new ArrayList<>(); + + public TagsBuilder addTag(String string) { + tags.add(string); + + return this; + } + + public String build() { + return String.join(", ", tags); + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/keepalive/KeepAlive.java b/Impl/src/main/java/dev/brighten/anticheat/processing/keepalive/KeepAlive.java new file mode 100644 index 000000000..5dea2975f --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/keepalive/KeepAlive.java @@ -0,0 +1,40 @@ +package dev.brighten.anticheat.processing.keepalive; + +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import lombok.RequiredArgsConstructor; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +public class KeepAlive { + + public final int start; + public final short id; + public long startStamp; + + public KeepAlive(int start) { + this.start = start; + id = (short) ThreadLocalRandom.current().nextInt(Short.MIN_VALUE, Short.MAX_VALUE); + } + + public final Map receivedKeepalive = new HashMap<>(); + + protected void received(ObjectData data) { + receivedKeepalive.put(data.uuid, new KAReceived(data, Kauri.INSTANCE.keepaliveProcessor.tick)); + } + + public Optional getReceived(UUID uuid) { + return Optional.ofNullable(receivedKeepalive.getOrDefault(uuid, null)); + } + + @RequiredArgsConstructor + public static class KAReceived { + public final ObjectData data; + public final int stamp; + public long receivedStamp; + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/processing/keepalive/KeepaliveProcessor.java b/Impl/src/main/java/dev/brighten/anticheat/processing/keepalive/KeepaliveProcessor.java new file mode 100644 index 000000000..b111099c3 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/processing/keepalive/KeepaliveProcessor.java @@ -0,0 +1,95 @@ +package dev.brighten.anticheat.processing.keepalive; + +import cc.funkemunky.api.tinyprotocol.api.TinyProtocolHandler; +import cc.funkemunky.api.tinyprotocol.packet.out.WrappedOutTransaction; +import cc.funkemunky.api.utils.KLocation; +import cc.funkemunky.api.utils.RunUtils; +import cc.funkemunky.api.utils.objects.evicting.ConcurrentEvictingMap; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.data.ObjectData; +import org.bukkit.scheduler.BukkitTask; + +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.*; + +public class KeepaliveProcessor implements Runnable { + + private BukkitTask task; + + public KeepAlive currentKeepalive; + public int tick; + + public final Map keepAlives = new ConcurrentEvictingMap<>(30); + + public ConcurrentHashMap lastResponses = new ConcurrentHashMap<>(); + + public KeepaliveProcessor() { + start(); + } + + @Override + public void run() { + tick++; + synchronized (keepAlives) { + currentKeepalive = new KeepAlive(tick); + keepAlives.put(currentKeepalive.id, currentKeepalive); + } + + WrappedOutTransaction packet = new WrappedOutTransaction(0, currentKeepalive.id, false); + + currentKeepalive.startStamp = System.currentTimeMillis(); + for (ObjectData value : Kauri.INSTANCE.dataManager.dataMap.values()) { + if(value.target != null) { + value.targetPastLocation.addLocation(value.target.getLocation()); + value.runKeepaliveAction(ka -> { + value.targetLoc = new KLocation(value.target.getLocation()); + }); + } + + TinyProtocolHandler.sendPacket(value.getPlayer(), packet); + /*value.getThread().execute(() -> { + for (Runnable runnable : value.tasksToRun) { + runnable.run(); + value.tasksToRun.remove(runnable); + } + });*/ + } + } + + public Optional getKeepByTick(int tick) { + return keepAlives.values().parallelStream().filter(ka -> ka.start == tick).findFirst(); + } + + public Optional getKeepById(short id) { + return Optional.ofNullable(keepAlives.get(id)); + } + + public Optional getResponse(ObjectData data) { + if(!lastResponses.containsKey(data.uuid)) + return Optional.empty(); + + return getKeepById(lastResponses.get(data.uuid)); + } + + public void start() { + if(task == null) { + task = RunUtils.taskTimer(this, 0L, 0L); + } + } + + public void addResponse(ObjectData data, short id) { + getKeepById(id).ifPresent(ka -> { + lastResponses.put(data.uuid, id); + ka.received(data); + }); + } + + public void stop() { + if(task != null) { + task.cancel(); + task = null; + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/AxisAlignedBB.java b/Impl/src/main/java/dev/brighten/anticheat/utils/AxisAlignedBB.java new file mode 100644 index 000000000..58aaf833c --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/AxisAlignedBB.java @@ -0,0 +1,320 @@ +package dev.brighten.anticheat.utils; + +import cc.funkemunky.api.tinyprotocol.packet.types.enums.WrappedEnumDirection; +import cc.funkemunky.api.utils.BoundingBox; +import cc.funkemunky.api.utils.world.types.RayCollision; +import cc.funkemunky.api.utils.world.types.SimpleCollisionBox; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +public class AxisAlignedBB { + + public final double minX; + public final double minY; + public final double minZ; + public final double maxX; + public final double maxY; + public final double maxZ; + + public AxisAlignedBB(double d0, double d1, double d2, double d3, double d4, double d5) { + this.minX = Math.min(d0, d3); + this.minY = Math.min(d1, d4); + this.minZ = Math.min(d2, d5); + this.maxX = Math.max(d0, d3); + this.maxY = Math.max(d1, d4); + this.maxZ = Math.max(d2, d5); + } + + public AxisAlignedBB(BoundingBox box) { + this.minX = box.minX; + this.minY = box.minY; + this.minZ = box.minZ; + this.maxX = box.maxX; + this.maxY = box.maxY; + this.maxZ = box.maxZ; + } + + public AxisAlignedBB(SimpleCollisionBox box) { + this.minX = box.xMin; + this.minY = box.yMin; + this.minZ = box.zMin; + this.maxX = box.xMax; + this.maxY = box.yMax; + this.maxZ = box.zMax; + } + + public AxisAlignedBB(Location blockposition, Location blockposition1) { + this.minX = (double) blockposition.getBlockX(); + this.minY = (double) blockposition.getBlockY(); + this.minZ = (double) blockposition.getBlockZ(); + this.maxX = (double) blockposition1.getBlockX(); + this.maxY = (double) blockposition1.getBlockY(); + this.maxZ = (double) blockposition1.getBlockZ(); + } + + public AxisAlignedBB a(double d0, double d1, double d2) { + double d3 = this.minX; + double d4 = this.minY; + double d5 = this.minZ; + double d6 = this.maxX; + double d7 = this.maxY; + double d8 = this.maxZ; + + if (d0 < 0.0D) { + d3 += d0; + } else if (d0 > 0.0D) { + d6 += d0; + } + + if (d1 < 0.0D) { + d4 += d1; + } else if (d1 > 0.0D) { + d7 += d1; + } + + if (d2 < 0.0D) { + d5 += d2; + } else if (d2 > 0.0D) { + d8 += d2; + } + + return new AxisAlignedBB(d3, d4, d5, d6, d7, d8); + } + + public AxisAlignedBB grow(double d0, double d1, double d2) { + double d3 = this.minX - d0; + double d4 = this.minY - d1; + double d5 = this.minZ - d2; + double d6 = this.maxX + d0; + double d7 = this.maxY + d1; + double d8 = this.maxZ + d2; + + return new AxisAlignedBB(d3, d4, d5, d6, d7, d8); + } + + public AxisAlignedBB a(AxisAlignedBB axisalignedbb) { + double d0 = Math.min(this.minX, axisalignedbb.minX); + double d1 = Math.min(this.minY, axisalignedbb.minY); + double d2 = Math.min(this.minZ, axisalignedbb.minZ); + double d3 = Math.max(this.maxX, axisalignedbb.maxX); + double d4 = Math.max(this.maxY, axisalignedbb.maxY); + double d5 = Math.max(this.maxZ, axisalignedbb.maxZ); + + return new AxisAlignedBB(d0, d1, d2, d3, d4, d5); + } + + public AxisAlignedBB c(double d0, double d1, double d2) { + return new AxisAlignedBB(this.minX + d0, this.minY + d1, + this.minZ + d2, this.maxX + d0, this.maxY + d1, this.maxZ + d2); + } + + public double a(AxisAlignedBB axisalignedbb, double d0) { + if (axisalignedbb.maxY > this.minY && axisalignedbb.minY < this.maxY && axisalignedbb.maxZ > this.minZ + && axisalignedbb.minZ < this.maxZ) { + double d1; + + if (d0 > 0.0D && axisalignedbb.maxX <= this.minX) { + d1 = this.minX - axisalignedbb.maxX; + if (d1 < d0) { + d0 = d1; + } + } else if (d0 < 0.0D && axisalignedbb.minX >= this.maxX) { + d1 = this.maxX - axisalignedbb.minX; + if (d1 > d0) { + d0 = d1; + } + } + + return d0; + } else { + return d0; + } + } + + public double b(AxisAlignedBB axisalignedbb, double d0) { + if (axisalignedbb.maxX > this.minX && axisalignedbb.minX < this.maxX + && axisalignedbb.maxZ > this.minZ && axisalignedbb.minZ < this.maxZ) { + double d1; + + if (d0 > 0.0D && axisalignedbb.maxY <= this.minY) { + d1 = this.minY - axisalignedbb.maxY; + if (d1 < d0) { + d0 = d1; + } + } else if (d0 < 0.0D && axisalignedbb.minY >= this.maxY) { + d1 = this.maxY - axisalignedbb.minY; + if (d1 > d0) { + d0 = d1; + } + } + + return d0; + } else { + return d0; + } + } + + public double c(AxisAlignedBB axisalignedbb, double d0) { + if (axisalignedbb.maxX > this.minX && axisalignedbb.minX < this.maxX + && axisalignedbb.maxY > this.minY && axisalignedbb.minY < this.maxY) { + double d1; + + if (d0 > 0.0D && axisalignedbb.maxZ <= this.minZ) { + d1 = this.minZ - axisalignedbb.maxZ; + if (d1 < d0) { + d0 = d1; + } + } else if (d0 < 0.0D && axisalignedbb.minZ >= this.maxZ) { + d1 = this.maxZ - axisalignedbb.minZ; + if (d1 > d0) { + d0 = d1; + } + } + + return d0; + } else { + return d0; + } + } + + public boolean b(AxisAlignedBB axisalignedbb) { + return (axisalignedbb.maxX > this.minX && axisalignedbb.minX < this.maxX) + && ((axisalignedbb.maxY > this.minY && axisalignedbb.minY < this.maxY) + && (axisalignedbb.maxZ > this.minZ && axisalignedbb.minZ < this.maxZ)); + } + + public boolean a(Vec3D vec3d) { + return (vec3d.x > this.minX && vec3d.x < this.maxX) && ((vec3d.y > this.minY && vec3d.y < this.maxY) + && (vec3d.z > this.minZ && vec3d.z < this.maxZ)); + } + + public double a() { + double d0 = this.maxX - this.minX; + double d1 = this.maxY - this.minY; + double d2 = this.maxZ - this.minZ; + + return (d0 + d1 + d2) / 3.0D; + } + + public AxisAlignedBB shrink(double d0, double d1, double d2) { + double d3 = this.minX + d0; + double d4 = this.minY + d1; + double d5 = this.minZ + d2; + double d6 = this.maxX - d0; + double d7 = this.maxY - d1; + double d8 = this.maxZ - d2; + + return new AxisAlignedBB(d3, d4, d5, d6, d7, d8); + } + + public Vec3D rayTrace(RayCollision collision, double distance) { + Vec3D origin = new Vec3D(collision.originX, collision.originY, collision.originZ); + Vec3D dir = origin.clone().add(collision.directionX * distance, collision.directionY * distance, collision.directionZ * distance); + return rayTrace(origin, dir); + } + + public Vec3D rayTrace(Vector vorigin, Vector vdirection, double distance) { + Vec3D origin = new Vec3D(vorigin.getX(), vorigin.getY(), vorigin.getZ()); + Vector direction = vdirection.clone().multiply(distance); + Vec3D dir = origin.clone().add(direction.getX(), direction.getY(), direction.getZ()); + + return rayTrace(origin, dir); + } + + public Vec3D rayTrace(Vec3D vec3d, Vec3D vec3d1) { + Vec3D vec3d2 = vec3d.a(vec3d1, this.minX); + Vec3D vec3d3 = vec3d.a(vec3d1, this.maxX); + Vec3D vec3d4 = vec3d.b(vec3d1, this.minY); + Vec3D vec3d5 = vec3d.b(vec3d1, this.maxY); + Vec3D vec3d6 = vec3d.c(vec3d1, this.minZ); + Vec3D vec3d7 = vec3d.c(vec3d1, this.maxZ); + + if (!this.b(vec3d2)) { + vec3d2 = null; + } + + if (!this.b(vec3d3)) { + vec3d3 = null; + } + + if (!this.c(vec3d4)) { + vec3d4 = null; + } + + if (!this.c(vec3d5)) { + vec3d5 = null; + } + + if (!this.d(vec3d6)) { + vec3d6 = null; + } + + if (!this.d(vec3d7)) { + vec3d7 = null; + } + + Vec3D vec3d8 = null; + + if (vec3d2 != null) { + vec3d8 = vec3d2; + } + + if (vec3d3 != null && (vec3d8 == null || vec3d.distanceSquared(vec3d3) < vec3d.distanceSquared(vec3d8))) { + vec3d8 = vec3d3; + } + + if (vec3d4 != null && (vec3d8 == null || vec3d.distanceSquared(vec3d4) < vec3d.distanceSquared(vec3d8))) { + vec3d8 = vec3d4; + } + + if (vec3d5 != null && (vec3d8 == null || vec3d.distanceSquared(vec3d5) < vec3d.distanceSquared(vec3d8))) { + vec3d8 = vec3d5; + } + + if (vec3d6 != null && (vec3d8 == null || vec3d.distanceSquared(vec3d6) < vec3d.distanceSquared(vec3d8))) { + vec3d8 = vec3d6; + } + + if (vec3d7 != null && (vec3d8 == null || vec3d.distanceSquared(vec3d7) < vec3d.distanceSquared(vec3d8))) { + vec3d8 = vec3d7; + } + + if (vec3d8 == null) { + return null; + } else { + WrappedEnumDirection enumdirection = null; + + if (vec3d8 == vec3d2) { + enumdirection = WrappedEnumDirection.WEST; + } else if (vec3d8 == vec3d3) { + enumdirection = WrappedEnumDirection.EAST; + } else if (vec3d8 == vec3d4) { + enumdirection = WrappedEnumDirection.DOWN; + } else if (vec3d8 == vec3d5) { + enumdirection = WrappedEnumDirection.UP; + } else if (vec3d8 == vec3d6) { + enumdirection = WrappedEnumDirection.NORTH; + } else { + enumdirection = WrappedEnumDirection.SOUTH; + } + + return vec3d8; + } + } + + private boolean b(Vec3D vec3d) { + return vec3d != null && (vec3d.y >= this.minY && vec3d.y <= this.maxY && vec3d.z >= this.minZ && vec3d.z <= this.maxZ); + } + + private boolean c(Vec3D vec3d) { + return vec3d != null && (vec3d.x >= this.minX && vec3d.x <= this.maxX && vec3d.z >= this.minZ && vec3d.z <= this.maxZ); + } + + private boolean d(Vec3D vec3d) { + return vec3d != null && (vec3d.x >= this.minX && vec3d.x <= this.maxX && vec3d.y >= this.minY && vec3d.y <= this.maxY); + } + + public String toString() { + return "box[" + this.minX + ", " + this.minY + ", " + this.minZ + " -> " + this.maxX + ", " + this.maxY + ", " + this.maxZ + "]"; + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/CacheList.java b/Impl/src/main/java/dev/brighten/anticheat/utils/CacheList.java new file mode 100644 index 000000000..638ccb97d --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/CacheList.java @@ -0,0 +1,81 @@ +package dev.brighten.anticheat.utils; + +import dev.brighten.anticheat.Kauri; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +public class CacheList extends CopyOnWriteArrayList { + + private final Map cachedTimes = new ConcurrentHashMap<>(); + private final long expireTime; + + public CacheList(long millis) { + this.expireTime = millis; + runTask(); + } + + public CacheList(long time, TimeUnit unit) { + this.expireTime = unit.toMillis(time); + runTask(); + } + + @Override + public boolean addAll(Collection extends E> c) { + c.forEach(this::updateValue); + return super.addAll(c); + } + + @Override + public boolean addAll(int index, Collection extends E> c) { + c.forEach(this::updateValue); + return super.addAll(index, c); + } + + @Override + public boolean add(E e) { + updateValue(e); + return super.add(e); + } + + @Override + public void add(int index, E element) { + updateValue(element); + super.add(index, element); + } + + @Override + public E set(int index, E element) { + updateValue(element); + return super.set(index, element); + } + + @Override + public boolean addIfAbsent(E e) { + if(super.addIfAbsent(e)) { + updateValue(e); + return true; + } + return false; + } + + private void runTask() { + Kauri.INSTANCE.executor.scheduleAtFixedRate(() -> { + long now = System.currentTimeMillis(); + cachedTimes.forEach((key, time) -> { + if(!contains(key)) cachedTimes.remove(key); + else if(now - time > expireTime) { + remove(key); + cachedTimes.remove(key); + } + }); + }, 500L, 150L, TimeUnit.MILLISECONDS); + } + + private void updateValue(E val) { + this.cachedTimes.put(val, System.currentTimeMillis()); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/CacheMap.java b/Impl/src/main/java/dev/brighten/anticheat/utils/CacheMap.java new file mode 100644 index 000000000..f71fd6393 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/CacheMap.java @@ -0,0 +1,48 @@ +package dev.brighten.anticheat.utils; + +import dev.brighten.anticheat.Kauri; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class CacheMap extends ConcurrentHashMap { + + private final Map cachedTimes = new ConcurrentHashMap<>(); + private final long expireTime; + + public CacheMap(long millis) { + this.expireTime = millis; + runTask(); + } + + public CacheMap(long time, TimeUnit unit) { + this.expireTime = unit.toMillis(time); + runTask(); + } + + @Override + public V put(K key, V value) { + cachedTimes.put(key, System.currentTimeMillis()); + return super.put(key, value); + } + + @Override + public void putAll(Map extends K, ? extends V> m) { + m.forEach((k, v) -> cachedTimes.put(k, System.currentTimeMillis())); + super.putAll(m); + } + + private void runTask() { + Kauri.INSTANCE.executor.scheduleAtFixedRate(() -> { + long now = System.currentTimeMillis(); + cachedTimes.forEach((key, time) -> { + if(!containsKey(key)) cachedTimes.remove(key); + else if(now - time > expireTime) { + remove(key); + cachedTimes.remove(key); + } + }); + }, 500L, 150L, TimeUnit.MILLISECONDS); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/CollisionHandler.java b/Impl/src/main/java/dev/brighten/anticheat/utils/CollisionHandler.java new file mode 100644 index 000000000..d0cc1741a --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/CollisionHandler.java @@ -0,0 +1,141 @@ +package dev.brighten.anticheat.utils; + +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.utils.KLocation; +import cc.funkemunky.api.utils.Materials; +import cc.funkemunky.api.utils.MathUtils; +import cc.funkemunky.api.utils.MiscUtils; +import cc.funkemunky.api.utils.world.BlockData; +import cc.funkemunky.api.utils.world.CollisionBox; +import cc.funkemunky.api.utils.world.types.SimpleCollisionBox; +import dev.brighten.anticheat.data.ObjectData; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +@Getter +public class CollisionHandler { + private List blocks = new CopyOnWriteArrayList<>(); + private List entities; + private ObjectData data; + private KLocation location; + + private double width, height; + private double shift; + @Setter + private boolean single = false; + @Setter + private boolean debugging; + + public CollisionHandler(List blocks, List entities, KLocation to) { + this.blocks.addAll(blocks); + this.entities = entities; + this.location = to; + } + + public void setSize(double width, double height) { + this.width = width; + this.height = height; + } + + public void setOffset(double shift) { + this.shift = shift; + } + + public boolean containsFlag(int bitmask) { + for (Block b : blocks) { + if (Materials.checkFlag(b.getType(), bitmask)) return true; + } + return false; + } + + public boolean contains(EntityType type) { + return entities.stream().anyMatch(e -> e.getType() == type); + } + + public boolean isCollidedWith(SimpleCollisionBox playerBox, int bitmask) { + for (Block b : blocks) { + Location block = b.getLocation(); + if (Materials.checkFlag(b.getType(), bitmask) + && (!single || (block.getBlockX() == MathUtils.floor(location.x) + && block.getBlockZ() == MathUtils.floor(location.z)))) { + if (BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()).isCollided(playerBox)) { + return true; + } + } + } + + return false; + } + + public boolean isCollidedWith(int bitmask) { + SimpleCollisionBox playerBox = new SimpleCollisionBox() + .offset(location.x, location.y, location.z) + .expandMin(0, shift, 0) + .expandMax(0, height, 0) + .expand(width / 2, 0, width / 2); + + return isCollidedWith(playerBox, bitmask); + } + + public List getCollisionBoxes(SimpleCollisionBox playerBox) { + List collided = new ArrayList<>(); + + for (Block b : blocks) { + Location block = b.getLocation(); + + CollisionBox box; + if((box = BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion())).isCollided(playerBox)) { + collided.add(box); + } + } + return collided; + } + public List getCollisionBoxes() { + SimpleCollisionBox playerBox = new SimpleCollisionBox() + .offset(location.x, location.y, location.z) + .expandMin(0, shift, 0) + .expandMax(0, height, 0) + .expand(width / 2, 0, width / 2); + + return getCollisionBoxes(playerBox); + } + + public boolean isCollidedWith(CollisionBox box) { + SimpleCollisionBox playerBox = new SimpleCollisionBox() + .offset(location.x, location.y, location.z) + .expandMin(0, shift, 0) + .expandMax(0, height, 0) + .expand(width / 2, 0, width / 2); + + return box.isCollided(playerBox); + } + + public boolean isCollidedWith(SimpleCollisionBox playerBox, Material... materials) { + for (Block b : blocks) { + if (MiscUtils.contains(materials, b.getType())) { + if (BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()).isCollided(playerBox)) + return true; + } + } + + return false; + } + public boolean isCollidedWith(Material... materials) { + SimpleCollisionBox playerBox = new SimpleCollisionBox() + .offset(location.x, location.y, location.z) + .expandMin(0, shift, 0) + .expandMax(0, height, 0) + .expand(width / 2, 0, width / 2); + + return isCollidedWith(playerBox, materials); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/GeneralHash.java b/Impl/src/main/java/dev/brighten/anticheat/utils/GeneralHash.java new file mode 100644 index 000000000..d69b5ebe2 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/GeneralHash.java @@ -0,0 +1,45 @@ +package dev.brighten.anticheat.utils; + +public class GeneralHash { + + /*public static String getSHAHash(byte[] data, SHAType type) { + String result = null; + try { + MessageDigest digest = MessageDigest.getInstance(type.equals(SHAType.SHA1) ? "SHA-1" :"SHA-256"); + byte[] hash = digest.digest(data); + return bytesToHex(hash); // make it printable + }catch(Exception ex) { + ex.printStackTrace(); + } + return result; + } + + public static String getMD5Hash(byte[] data) { + String result = null; + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] hash = digest.digest(data); + return bytesToHex(hash); // make it printable + }catch(Exception ex) { + ex.printStackTrace(); + } + return result; + } + + public static MessageDigest getMessageDigest(String type) { + try { + return MessageDigest.getInstance(type); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return null; + } + + public static String bytesToHex(byte[] hash) { + return DatatypeConverter.printHexBinary(hash).toLowerCase(); + } + + public enum SHAType { + SHA256, SHA1 + }*/ +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/GraphUtil.java b/Impl/src/main/java/dev/brighten/anticheat/utils/GraphUtil.java new file mode 100644 index 000000000..e082cb3fd --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/GraphUtil.java @@ -0,0 +1,122 @@ +package dev.brighten.anticheat.utils; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.bukkit.ChatColor; + +import java.util.Deque; +import java.util.List; + +public class GraphUtil { + + @Getter @Setter + @RequiredArgsConstructor + + public static class GraphResult { + private final String graph; + private final int positives, negatives; + } + + public static GraphResult getGraph(List values) { + StringBuilder graph = new StringBuilder(); + + double largest = 0; + + for (double value : values) { + if (value > largest) + largest = value; + } + + int GRAPH_HEIGHT = 2; + int positives = 0, negatives = 0; + + for (int i = GRAPH_HEIGHT - 1; i > 0; i -= 1) { + StringBuilder sb = new StringBuilder(); + + for (double index : values) { + double value = GRAPH_HEIGHT * index / largest; + + if (value > i && value < i + 1) { + ++positives; + sb.append(String.format("%s+", ChatColor.GREEN)); + } else { + ++negatives; + sb.append(String.format("%s-", ChatColor.RED)); + } + } + + graph.append(sb.toString()); + } + + return new GraphResult(graph.toString(), positives, negatives); + } + + public static GraphResult getGraph(Deque values) { + StringBuilder graph = new StringBuilder(); + + double largest = 0; + + for (double value : values) { + if (value > largest) + largest = value; + } + + int GRAPH_HEIGHT = 2; + int positives = 0, negatives = 0; + + for (int i = GRAPH_HEIGHT - 1; i > 0; i -= 1) { + StringBuilder sb = new StringBuilder(); + + for (double index : values) { + double value = GRAPH_HEIGHT * index / largest; + + if (value > i && value < i + 1) { + ++positives; + sb.append(String.format("%s+", ChatColor.GREEN)); + } else { + ++negatives; + sb.append(String.format("%s-", ChatColor.RED)); + } + } + + graph.append(sb.toString()); + } + + return new GraphResult(graph.toString(), positives, negatives); + } + + public static GraphResult getGraphLong(Deque values) { + StringBuilder graph = new StringBuilder(); + + double largest = 0; + + for (double value : values) { + if (value > largest) + largest = value; + } + + int GRAPH_HEIGHT = 2; + int positives = 0, negatives = 0; + + for (int i = GRAPH_HEIGHT - 1; i > 0; i -= 1) { + StringBuilder sb = new StringBuilder(); + + for (double index : values) { + double value = GRAPH_HEIGHT * index / largest; + + if (value > i && value < i + 1) { + ++positives; + sb.append(String.format("%s+", ChatColor.GREEN)); + } else { + ++negatives; + sb.append(String.format("%s-", ChatColor.RED)); + } + } + + graph.append(sb.toString()); + } + + return new GraphResult(graph.toString(), positives, negatives); + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/Helper.java b/Impl/src/main/java/dev/brighten/anticheat/utils/Helper.java new file mode 100644 index 000000000..abd0032d9 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/Helper.java @@ -0,0 +1,225 @@ +package dev.brighten.anticheat.utils; + + +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.utils.BlockUtils; +import cc.funkemunky.api.utils.Materials; +import cc.funkemunky.api.utils.XMaterial; +import cc.funkemunky.api.utils.handlers.PlayerSizeHandler; +import cc.funkemunky.api.utils.world.BlockData; +import cc.funkemunky.api.utils.world.CollisionBox; +import cc.funkemunky.api.utils.world.types.RayCollision; +import cc.funkemunky.api.utils.world.types.SimpleCollisionBox; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class Helper { + + public static int angularDistance(double alpha, double beta) { + while (alpha < 0) alpha += 360; + while (beta < 0) beta += 360; + double phi = Math.abs(beta - alpha) % 360; + return (int) (phi > 180 ? 360 - phi : phi); + } + + public static Vector vector(double yaw, double pitch) { + Vector vector = new Vector(); + vector.setY(-Math.sin(Math.toRadians(pitch))); + double xz = Math.cos(Math.toRadians(pitch)); + vector.setX(-xz * Math.sin(Math.toRadians(yaw))); + vector.setZ(xz * Math.cos(Math.toRadians(yaw))); + return vector; + } + + public static SimpleCollisionBox getMovementHitbox(Player player, double x, double y, double z) { + return PlayerSizeHandler.instance.bounds(player, x, y, z); + } + + public static SimpleCollisionBox getMovementHitbox(Player player) { + return PlayerSizeHandler.instance.bounds(player); + } + + public static SimpleCollisionBox getCombatHitbox(Player player, ProtocolVersion version) { + return version.isBelow(ProtocolVersion.V1_9) ? PlayerSizeHandler.instance.bounds(player).expand(.1, 0, .1) : PlayerSizeHandler.instance.bounds(player); + } + + private static Block getBlockAt(World world, int x, int y, int z) { + return world.isChunkLoaded(x >> 4, z >> 4) ? world.getChunkAt(x >> 4, z >> 4).getBlock(x & 15, y, z & 15) : null; + } + + public static SimpleCollisionBox wrap(SimpleCollisionBox a, SimpleCollisionBox b) { + double minX = a.xMin < b.xMin ? a.xMin : b.xMin; + double minY = a.yMin < b.yMin ? a.yMin : b.yMin; + double minZ = a.zMin < b.zMin ? a.zMin : b.zMin; + double maxX = a.xMax > b.xMax ? a.xMax : b.xMax; + double maxY = a.yMax > b.yMax ? a.yMax : b.yMax; + double maxZ = a.zMax > b.zMax ? a.zMax : b.zMax; + return new SimpleCollisionBox(minX, minY, minZ, maxX, maxY, maxZ); + } + + public static SimpleCollisionBox wrap(List box) { + if (!box.isEmpty()) { + SimpleCollisionBox wrap = box.get(0).copy(); + for (int i = 1; i < box.size(); i++) { + SimpleCollisionBox a = box.get(i); + if (wrap.xMin > a.xMin) wrap.xMin = a.xMin; + if (wrap.yMin > a.yMin) wrap.yMin = a.yMin; + if (wrap.zMin > a.zMin) wrap.zMin = a.zMin; + if (wrap.xMax < a.xMax) wrap.xMax = a.xMax; + if (wrap.yMax < a.yMax) wrap.yMax = a.yMax; + if (wrap.zMax < a.zMax) wrap.zMax = a.zMax; + } + return wrap; + } + return null; + } + + public static List blockCollisions(List blocks, CollisionBox box) { + return blocks.stream() + .filter(b -> BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()).isCollided(box)) + .collect(Collectors.toCollection(LinkedList::new)); + } + + public static boolean isCollided(SimpleCollisionBox toCheck, CollisionBox other) { + List downcasted = new ArrayList<>(); + + other.downCast(downcasted); + + return downcasted.stream().anyMatch(box -> box.xMax >= toCheck.xMin && box.xMin <= toCheck.xMax + && box.yMax >= toCheck.yMin && box.yMin <= toCheck.yMax && box.zMax >= toCheck.zMin + && box.zMin <= toCheck.zMax); + } + + public static List blockCollisions(List blocks, CollisionBox box, int material) { + return blocks.stream().filter(b -> Materials.checkFlag(b.getType(), material)) + .filter(b -> BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()).isCollided(box)) + .collect(Collectors.toCollection(LinkedList::new)); + } + + + public static List collisions(List boxes, CollisionBox box) { + return boxes.stream().filter(b -> b.isCollided(box)) + .collect(Collectors.toCollection(LinkedList::new)); + } + + public static List getBlocksNearby(CollisionHandler handler, CollisionBox collisionBox) { + try { + return handler.getBlocks().stream().filter(b -> b.getType() != XMaterial.AIR.parseMaterial() + && BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()) + .isCollided(collisionBox)) + .collect(Collectors.toList()); + } catch (NullPointerException e) { + return new ArrayList<>(); + } + } + + public static List getBlocksNearby2(World world, SimpleCollisionBox collisionBox, int mask) { + int x1 = (int) Math.floor(collisionBox.xMin); + int y1 = (int) Math.floor(collisionBox.yMin); + int z1 = (int) Math.floor(collisionBox.zMin); + int x2 = (int) Math.ceil(collisionBox.xMax); + int y2 = (int) Math.ceil(collisionBox.yMax); + int z2 = (int) Math.ceil(collisionBox.zMax); + List blocks = new LinkedList<>(); + Block block; + for (int x = x1; x <= x2; x++) + for (int y = y1; y <= y2; y++) + for (int z = z1; z <= z2; z++) + if ((block = getBlockAt(world, x, y, z)) != null + && block.getType()!= XMaterial.AIR.parseMaterial()) + if (Materials.checkFlag(block.getType(),mask)) + blocks.add(block); + return blocks; + } + + public static List getBlocksNearby(CollisionHandler handler, SimpleCollisionBox collisionBox, int mask) { + return handler.getBlocks().stream().filter(b -> b.getType() != XMaterial.AIR.parseMaterial() + && Materials.checkFlag(b.getType(), mask) + && BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()) + .isCollided(collisionBox)) + .collect(Collectors.toList()); + } + + private static final int[] decimalPlaces = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; + + public static double format(double d, int dec) { + return (long) (d * decimalPlaces[dec] + 0.5) / (double) decimalPlaces[dec]; + } + + public static String drawUsage(long max, long time) { + double chunk = max / 50.; + String line = IntStream.range(0, 50).mapToObj(i -> (chunk * i < time ? "§c" : "§7") + "❘") + .collect(Collectors.joining("", "[", "")); + String zeros = "00"; + String nums = Integer.toString((int) ((time / (double) max) * 100)); + return line + "§f] §c" + zeros.substring(0, 3 - nums.length()) + nums + "% §f❘"; + } + + public static String drawUsage(long max, double time) { + double chunk = max / 50.; + String line = IntStream.range(0, 50).mapToObj(i -> (chunk * i < time ? "§c" : "§7") + "❘") + .collect(Collectors.joining("", "[", "")); + String nums = String.valueOf(format((time / (double) max) * 100, 3)); + return line + "§f] §c" + nums + "%"; + } + + public static List getBlocks(CollisionHandler handler, SimpleCollisionBox collisionBox) { + return Helper.blockCollisions(getBlocksNearby(handler, collisionBox), collisionBox); + } + + public static List getBlocks(CollisionHandler handler, SimpleCollisionBox collisionBox, int material) { + return Helper.blockCollisions(getBlocksNearby(handler, collisionBox), collisionBox, material); + } + + public static List toCollisions(List blocks) { + return blocks.stream().map(b -> BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion())).collect(Collectors.toCollection(LinkedList::new)); + } + + public static List toCollisionsDowncasted(List blocks) { + List collisions = new LinkedList<>(); + blocks.forEach(b -> BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()).downCast(collisions)); + return collisions; + } + + public static List getCollisionsOnRay(RayCollision collision, World world, double distance, double resolution) { + int amount = Math.round((float)(distance / resolution)); + Location[] locs = new Location[Math.max(2, amount)]; //We do a max to prevent NegativeArraySizeException. + for (int i = 0; i < locs.length; i++) { + double ix = i / 2d; + + double fx = (collision.originX + (collision.directionX * ix)); + double fy = (collision.originY + (collision.directionY * ix)); + double fz = (collision.originZ + (collision.directionZ * ix)); + + locs[i] = new Location(world, fx, fy, fz); + } + return Arrays.stream(locs) + .map(loc -> { + Block block = BlockUtils.getBlock(loc); + + if(block == null) return null; + if(Materials.checkFlag(block.getType(), Materials.SOLID)) { + return BlockData.getData(block.getType()).getBox(block, ProtocolVersion.getGameVersion()); + } + return null; + }) + .filter(box -> { + if(box == null) return false; + return collision.isCollided(box); + }).collect(Collectors.toList()); + } + + public static CollisionBox toCollisions(Block b) { + return BlockData.getData(b.getType()).getBox(b, ProtocolVersion.getGameVersion()); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/MiscUtils.java b/Impl/src/main/java/dev/brighten/anticheat/utils/MiscUtils.java new file mode 100644 index 000000000..2f600c835 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/MiscUtils.java @@ -0,0 +1,448 @@ +package dev.brighten.anticheat.utils; + +import cc.funkemunky.api.reflections.impl.CraftReflection; +import cc.funkemunky.api.reflections.impl.MinecraftReflection; +import cc.funkemunky.api.reflections.types.WrappedField; +import cc.funkemunky.api.tinyprotocol.packet.types.MathHelper; +import cc.funkemunky.api.tinyprotocol.packet.types.enums.WrappedEnumAnimation; +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.KLocation; +import cc.funkemunky.api.utils.MathUtils; +import cc.funkemunky.api.utils.Tuple; +import dev.brighten.anticheat.commands.KauriCommand; +import dev.brighten.anticheat.processing.MovementProcessor; +import lombok.val; +import org.bukkit.Location; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.util.Vector; + +import java.io.Closeable; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +public class MiscUtils { + + private final static Set> NUMBER_REFLECTED_PRIMITIVES; + + public static String deltaSymbol = "\u0394"; + + static { + Set> s = new HashSet<>(); + s.add(byte.class); + s.add(short.class); + s.add(int.class); + s.add(long.class); + s.add(float.class); + s.add(double.class); + NUMBER_REFLECTED_PRIMITIVES = s; + } + + public static double max(double... values) { + return Arrays.stream(values).max().orElse(Double.MAX_VALUE); + } + + public static boolean isInteger(String string) { + try { + Integer.parseInt(string); + return true; + } catch(NumberFormatException e) { + return false; + } + } + + public static void testMessage(String message) { + KauriCommand.getTesters().forEach(pl -> pl.sendMessage(Color.translate(message))); + } + + public static void close(Closeable... closeables) { + try { + for (Closeable closeable : closeables) if (closeable != null) closeable.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static WrappedField ticksField = MinecraftReflection.minecraftServer.getFieldByName("ticks"); + private static Object minecraftServer = null; + //TODO Make this use the new abstraction system. + public static int currentTick() { + if(minecraftServer == null) minecraftServer = CraftReflection.getMinecraftServer(); + return ticksField.get(minecraftServer); + } + + public static void close(AutoCloseable... closeables) { + try { + for (AutoCloseable closeable : closeables) if (closeable != null) closeable.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static boolean isAnimated(HumanEntity entity) { + Object itemInUse = MinecraftReflection.getItemInUse(entity); + + if(itemInUse == null) return false; + + Object animation = MinecraftReflection.getItemAnimation(itemInUse); + + return !WrappedEnumAnimation.fromNMS(animation).equals(WrappedEnumAnimation.NONE); + } + + //Skidded from Luke. + public static double getAngle(Location loc1, Location loc2) { + if (loc1 == null || loc2 == null) return -1; + Vector playerRotation = new Vector(loc1.getYaw(), loc1.getPitch(), 0.0f); + loc1.setY(0); + loc2.setY(0); + val rot = MathUtils.getRotations(loc1, loc2); + Vector expectedRotation = new Vector(rot[0], rot[1], 0); + return MathUtils.yawTo180D(playerRotation.getX() - expectedRotation.getX()); + } + + + public static > Map sortByValue(Map map) { + List> list = new ArrayList<>(map.entrySet()); + list.sort(Map.Entry.comparingByValue()); + + return list.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b, LinkedHashMap::new)); + } + + public static LongStream listToStream(Collection collection) { + LongStream.Builder longBuilder = LongStream.builder(); + collection.forEach(longBuilder::add); + return longBuilder.build(); + } + + //Args: Tuple (a) is low outliers, Tupe (B) is high outliers + public static Tuple, List> getOutliers(Collection extends Number> collection) { + List values = new ArrayList<>(); + + for (Number number : collection) { + values.add(number.doubleValue()); + } + + if(values.size() < 4) return new Tuple<>(new ArrayList<>(), new ArrayList<>()); + + double q1 = getMedian(values.subList(0, values.size() / 2)), + q3 = getMedian(values.subList(values.size() / 2, values.size())); + double iqr = Math.abs(q1 - q3); + + double lowThreshold = q1 - 1.5 * iqr, highThreshold = q3 + 1.5 * iqr; + + val tuple = new Tuple, List>(new ArrayList<>(), new ArrayList<>()); + + for (Double value : values) { + if(value < lowThreshold) tuple.one.add(value); + else if(value > highThreshold) tuple.two.add(value); + } + + return tuple; + } + + public static double getMedian(List data) { + if(data.size() > 1) { + if (data.size() % 2 == 0) + return (data.get(data.size() / 2) + data.get(data.size() / 2 - 1)) / 2; + else + return data.get(Math.round(data.size() / 2f)); + } + return 0; + } + + public static double getMedian(Iterable extends Number> iterable) { + List data = new ArrayList<>(); + + for (Number number : iterable) { + data.add(number.doubleValue()); + } + + return getMedian(data); + } + + //Copied from apache math Kurtosis class. + public static double getKurtosisApache(Iterable extends Number> iterable) { + List values = new ArrayList<>(); + + double total = 0; + double kurt = Double.NaN; + for (Number number : iterable) { + double v = number.doubleValue(); + total+= v; + values.add(v); + } + + if(values.size() < 2) return kurt; + + double mean = total / values.size(); + double stdDev = MathUtils.stdev(values); + double accum3 = 0.0D; + + for (Double value : values) { + accum3 += Math.pow(value - mean, 4.0D); + } + + accum3 /= Math.pow(stdDev, 4.0D); + double n0 = values.size(); + double coefficientOne = n0 * (n0 + 1.0D) / ((n0 - 1.0D) * (n0 - 2.0D) * (n0 - 3.0D)); + double termTwo = 3.0D * Math.pow(n0 - 1.0D, 2.0D) / ((n0 - 2.0D) * (n0 - 3.0D)); + kurt = coefficientOne * accum3 - termTwo; + + return kurt; + } + + public static double getKurtosis(final Iterable extends Number> iterable) { + double n = 0.0; + double n2 = 0.0; + + for (Number number : iterable) { + n += number.doubleValue(); + ++n2; + } + + if (n2 < 3.0) { + return 0.0; + } + final double n3 = n2 * (n2 + 1.0) / ((n2 - 1.0) * (n2 - 2.0) * (n2 - 3.0)); + final double n4 = 3.0 * Math.pow(n2 - 1.0, 2.0) / ((n2 - 2.0) * (n2 - 3.0)); + final double n5 = n / n2; + double n6 = 0.0; + double n7 = 0.0; + for (final Number n8 : iterable) { + n6 += Math.pow(n5 - n8.doubleValue(), 2.0); + n7 += Math.pow(n5 - n8.doubleValue(), 4.0); + } + return n3 * (n7 / Math.pow(n6 / n2, 2.0)) - n4; + } + + public static float pow(float number, int times) { + float answer = number; + + if(times <= 0) return 0; + + for(int i = 1 ; i < times ; i++) { + answer*= number; + } + + return answer; + } + + public static double varianceSquared(final Number n, final Iterable extends Number> iterable) { + double n2 = 0.0; + int n3 = 0; + + for (Number number : iterable) { + n2 += Math.pow((number).doubleValue() - n.doubleValue(), 2.0); + ++n3; + } + + return (n2 == 0.0) ? 0.0 : (n2 / (n3 - 1)); + } + + public static List getModes(final Iterable extends Number> iterable) { + List numbers = new ArrayList<>(); + + for (Number number : iterable) { + numbers.add(number.doubleValue()); + } + final Map countFrequencies = numbers.stream() + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + + final double maxFrequency = countFrequencies.values().stream() + .mapToDouble(count -> count) + .max().orElse(-1); + + return countFrequencies.entrySet().stream() + .filter(tuple -> tuple.getValue() == maxFrequency) + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + } + + //Copied from apache math Skewness class. + public static double getSkewnessApache(Iterable extends Number> iterable) { + List values = new ArrayList<>(); + + double total = 0; + double skew = Double.NaN; + for (Number number : iterable) { + double v = number.doubleValue(); + total+= v; + values.add(v); + } + + if(values.size() < 2) return skew; + + double m = total / values.size(); + double accum = 0.0D; + double accum2 = 0.0D; + + for (Double value : values) { + double d = value - m; + accum += d * d; + accum2 += d; + } + + double variance = (accum - accum2 * accum2 / values.size()) / (values.size() - 1); + double accum3 = 0.0D; + + for (Double value : values) { + double d = value - m; + accum3 += d * d * d; + } + + accum3 /= variance * Math.sqrt(variance); + double n0 = values.size(); + skew = n0 / ((n0 - 1.0D) * (n0 - 2.0D)) * accum3; + + return skew; + } + + public static double getSkewness(final Iterable extends Number> iterable) { + double sum = 0; + int buffer = 0; + + final List numberList = new ArrayList<>(); + + for (Number num : iterable) { + sum += num.doubleValue(); + buffer++; + + numberList.add(num.doubleValue()); + } + + Collections.sort(numberList); + + final double mean = sum / buffer; + final double median = (buffer % 2 != 0) ? numberList.get(buffer / 2) : (numberList.get((buffer - 1) / 2) + numberList.get(buffer / 2)) / 2; + + return 3 * (mean - median) / deviationSquared(iterable); + } + + public static double stdev(final Iterable extends Number> iterable) { + double sum = 0.0f; + double num = 0.0f; + + final List list = new ArrayList<>(); + + for (Number number : iterable) { + list.add(number.doubleValue()); + } + + for (Double v : list) { + sum+= v; + } + + double mean = sum / (float)list.size(); + + for (Double v : list) { + num+= Math.pow(v - mean, 2.0D); + } + + return MathHelper.sqrt(num / (double)list.size()); + } + + public static float normalizeAngle(float angle) { + while (angle > 360.0F) + angle -= 360.0F; + while (angle < 0.0F) + angle += 360.0F; + return angle; + } + + public static double deviationSquared(final Iterable extends Number> iterable) { + double n = 0.0; + int n2 = 0; + + for (Number anIterable : iterable) { + n += (anIterable).doubleValue(); + ++n2; + } + final double n3 = n / n2; + double n4 = 0.0; + + for (Number anIterable : iterable) { + n4 += Math.pow(anIterable.doubleValue() - n3, 2.0); + } + + return (n4 == 0.0) ? 0.0 : (n4 / (n2 - 1)); + } + + public static float getYawChangeToEntity(Player player, LivingEntity entity, KLocation from, KLocation to) { + double deltaX = entity.getLocation().getX() - player.getLocation().getX(); + double deltaZ = entity.getLocation().getZ() - player.getLocation().getZ(); + double yawToEntity; + if(deltaZ < 0.0D && deltaX < 0.0D) { + yawToEntity = 90.0D + Math.toDegrees(Math.atan(deltaZ / deltaX)); + } else if(deltaZ < 0.0D && deltaX > 0.0D) { + yawToEntity = -90.0D + Math.toDegrees(Math.atan(deltaZ / deltaX)); + } else { + yawToEntity = Math.toDegrees(-Math.atan(deltaX / deltaZ)); + } + + return -MathUtils.getAngleDelta(from.yaw, to.yaw) - (float)yawToEntity; + } + + public static float getPitchChangeToEntity(Player player, LivingEntity entity, KLocation from, KLocation to) { + double deltaX = entity.getLocation().getX() - player.getLocation().getX(); + double deltaZ = entity.getLocation().getZ() - player.getLocation().getZ(); + double deltaY = player.getLocation().getY() - 1.6D + 2.0D - 0.4D - entity.getLocation().getY(); + double distanceXZ = Math.sqrt(deltaX * deltaX + deltaZ * deltaZ); + double pitchToEntity = -Math.toDegrees(Math.atan(deltaY / distanceXZ)); + return MathUtils.yawTo180F((float)(-(MathUtils.getAngleDelta(from.yaw, to.yaw) - (double)((float)pitchToEntity)))); + } + + public static boolean isReflectedAsNumber(Class> type) { + return Number.class.isAssignableFrom(type) || NUMBER_REFLECTED_PRIMITIVES.contains(type); + } + + public static int gcd(int current, int previous) { + return (Math.abs(previous) <= 16384L) ? Math.abs(current) : gcd(previous, current % previous); + } + + public static float gcd(float current, float previous) { + val sens = MovementProcessor.getSensitivityFromYawGCD(current / MovementProcessor.offset); + + return sens >= 0 || sens <= 1 ? Math.abs(current) : gcd(previous, current % previous); + } + + public static int getDecimalCount(float number) { + return String.valueOf(number).split("\\.")[1].length(); + } + + public static int getDecimalCount(double number) { + return String.valueOf(number).split("\\.")[1].length(); + } + + public static long gcd(long current, long previous) { + return (previous <= 16384L) ? current : gcd(previous, current % previous); + } + + public static long gcdPrevious(long current, long previous) { + return (previous <= 16384L) ? previous : gcdPrevious(previous, current % previous); + } + + public static long lcm(long a, long b) + { + return a * (b / gcd(a, b)); + } + + public static long lcm(long[] input) + { + long result = input[0]; + for(int i = 1; i < input.length; i++) result = lcm(result, input[i]); + return result; + } + + public static String timeStampToDate(long timeStamp) { + SimpleDateFormat format = new SimpleDateFormat("MM/dd/YYYY (hh:mm)"); + + format.setTimeZone(TimeZone.getTimeZone("America/New_York")); + Date date = new Date(timeStamp); + + return format.format(date); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/MouseFilter.java b/Impl/src/main/java/dev/brighten/anticheat/utils/MouseFilter.java new file mode 100644 index 000000000..d0e912c99 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/MouseFilter.java @@ -0,0 +1,29 @@ +package dev.brighten.anticheat.utils; + +public class MouseFilter { + public float x; + public float y; + public float z; + + /** + * Smooths mouse input + */ + public float smooth(float p_76333_1_, float p_76333_2_) { + this.x += p_76333_1_; + p_76333_1_ = (this.x - this.y) * p_76333_2_; + this.z += (p_76333_1_ - this.z) * 0.5F; + + if (p_76333_1_ > 0.0F && p_76333_1_ > this.z || p_76333_1_ < 0.0F && p_76333_1_ < this.z) { + p_76333_1_ = this.z; + } + + this.y += p_76333_1_; + return p_76333_1_; + } + + public void reset() { + this.x = 0.0F; + this.y = 0.0F; + this.z = 0.0F; + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/MovementUtils.java b/Impl/src/main/java/dev/brighten/anticheat/utils/MovementUtils.java new file mode 100644 index 000000000..45efce8b7 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/MovementUtils.java @@ -0,0 +1,96 @@ +package dev.brighten.anticheat.utils; + +import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion; +import cc.funkemunky.api.utils.*; +import dev.brighten.anticheat.data.ObjectData; +import lombok.val; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffectType; + +public class MovementUtils { + + private static Enchantment DEPTH; + + public static float getJumpHeight(ObjectData data) { + float baseHeight = 0.42f; + + if(data.potionProcessor.hasPotionEffect(PotionEffectType.JUMP)) { + baseHeight+= (data.potionProcessor.getEffectByType(PotionEffectType.JUMP).getAmplifier() + 1) * 0.1f; + } + + return baseHeight; + } + + public static boolean isOnLadder(ObjectData data) { + try { + int i = MathHelper.floor_double(data.playerInfo.to.x); + int j = MathHelper.floor_double(data.box.yMin); + int k = MathHelper.floor_double(data.playerInfo.to.z); + Block block = BlockUtils.getBlock(new Location(data.getPlayer().getWorld(), i, j, k)); + + return Materials.checkFlag(block.getType(), Materials.LADDER); + } catch(NullPointerException e) { + return false; + } + } + + public static int getDepthStriderLevel(Player player) { + if(DEPTH == null) return 0; + + val boots = player.getInventory().getBoots(); + + if(boots == null) return 0; + + return boots.getEnchantmentLevel(DEPTH); + } + + public static double getHorizontalDistance(KLocation one, KLocation two) { + return MathUtils.hypot(one.x - two.x, one.z - two.z); + } + + public static double getBaseSpeed(ObjectData data) { + return 0.2806 + (PlayerUtils.getPotionEffectLevel(data.getPlayer(), PotionEffectType.SPEED) + * (data.playerInfo.clientGround ? 0.062 : 0.04)) + (data.getPlayer().getWalkSpeed() - 0.2) * 2.5; + } + + public static float getFriction(ObjectData data) { + float friction = 0.6f; + + if(data.blockInfo.onSlime) { + friction = 0.8f; + } else if(data.blockInfo.onIce) { + friction = 0.98f; + } + return friction; + } + + public static float getTotalHeight(float initial) { + return getTotalHeight(ProtocolVersion.V1_8_9, initial); + } + + public static float getTotalHeight(ProtocolVersion version, float initial) { + float nextCalc = initial, total = initial; + int count = 0; + while ((nextCalc = (nextCalc - 0.08f) * 0.98f) > (version.isOrBelow(ProtocolVersion.V1_8_9) ? 0.005 : 0)) { + total+= nextCalc; + if(count++ > 15) { + return total * 4; + } + } + + return total; + } + + static { + try { + if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_8)) { + DEPTH = Enchantment.getByName("DEPTH_STRIDER"); + } + } catch(Exception e) { + DEPTH = null; + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/PastLocation.java b/Impl/src/main/java/dev/brighten/anticheat/utils/PastLocation.java new file mode 100644 index 000000000..fcd7df11c --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/PastLocation.java @@ -0,0 +1,67 @@ +package dev.brighten.anticheat.utils; + +import cc.funkemunky.api.utils.KLocation; +import org.bukkit.Location; + +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +public class PastLocation { + public List previousLocations = new CopyOnWriteArrayList<>(); + + public KLocation getPreviousLocation(long time) { + return (this.previousLocations.stream() + .min(Comparator.comparing(loc -> Math.abs(time - loc.timeStamp))) + .orElse(this.previousLocations.get(0))); + } + + public List getEstimatedLocation(long time, long ping, long delta) { + return this.previousLocations + .stream() + .filter(loc -> time - loc.timeStamp > 0 && Math.abs(time - loc.timeStamp - ping) < delta) + .collect(Collectors.toList()); + } + + public List getEstimatedLocation(long time, long ping) { + return this.previousLocations.stream() + .filter(loc -> time - loc.timeStamp > 0 + && time - loc.timeStamp <= ping + (ping < 50 ? 100 : 50)) + .collect(Collectors.toList()); + } + + public List getPreviousRange(long delta) { + long stamp = System.currentTimeMillis(); + + return this.previousLocations.stream() + .filter(loc -> stamp - loc.timeStamp < delta) + .collect(Collectors.toList()); + } + + public void addLocation(Location location) { + if (previousLocations.size() >= 20) { + previousLocations.remove(0); + } + + previousLocations.add(new KLocation(location)); + } + + public KLocation getLast() { + if(previousLocations.size() == 0) return null; + return previousLocations.get(previousLocations.size() - 1); + } + + public KLocation getFirst() { + if(previousLocations.size() == 0) return null; + return previousLocations.get(0); + } + + public void addLocation(KLocation location) { + if (previousLocations.size() >= 20) { + previousLocations.remove(0); + } + + previousLocations.add(location.clone()); + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/Pastebin.java b/Impl/src/main/java/dev/brighten/anticheat/utils/Pastebin.java new file mode 100644 index 000000000..f3e8aa251 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/Pastebin.java @@ -0,0 +1,113 @@ +package dev.brighten.anticheat.utils; + + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; + +public class Pastebin { + static String pasteURL = "https://funkemunky.cc/pastebin/make"; + + public Pastebin() { + + } + + static String checkResponse(String response) { + if (response.substring(0, 15).equals("Bad API request")) { + return response.substring(17); + } + return ""; + } + + static public String makePaste(String body, String name, Privacy privacy) + throws UnsupportedEncodingException { + String content = URLEncoder.encode(body, "UTF-8"); + String title = URLEncoder.encode(name + " report", "UTF-8"); + String data = "body=" + content + "&name=" + title + "&privacy=" + privacy.name(); + String response = Pastebin.page(Pastebin.pasteURL, data); + + if(response == null) return ""; + String check = Pastebin.checkResponse(response); + if (!check.equals("")) { + return check; + } + return response; + } + + static public String makePaste(String body, String name, Privacy privacy, String expire) + throws UnsupportedEncodingException { + String content = URLEncoder.encode(body, "UTF-8"); + String title = URLEncoder.encode(name + " report", "UTF-8"); + String data = "body=" + content + "&name=" + title + "&privacy=" + privacy.name() + "&expire=" + expire; + String response = Pastebin.page(Pastebin.pasteURL, data); + String check = Pastebin.checkResponse(response); + if (!check.equals("")) { + return check; + } + return response; + } + + public static String page(String uri, String urlParameters) { + URL url; + HttpURLConnection connection = null; + try { + // Create connection + url = new URL(uri); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", + "application/x-www-form-urlencoded"); + + connection.setRequestProperty("Content-Length", + "" + Integer.toString(urlParameters.getBytes().length)); + connection.setRequestProperty("Content-Language", "en-US"); + + connection.setUseCaches(false); + connection.setDoInput(true); + connection.setDoOutput(true); + + // Send request + DataOutputStream wr = new DataOutputStream( + connection.getOutputStream()); + wr.writeBytes(urlParameters); + wr.flush(); + wr.close(); + + // Get Response + InputStream is = connection.getInputStream(); + BufferedReader rd = new BufferedReader(new InputStreamReader(is)); + String line; + StringBuffer response = new StringBuffer(); + while ((line = rd.readLine()) != null) { + response.append(line); + } + rd.close(); + return response.toString(); + + } catch (Exception e) { + e.printStackTrace(); + return null; + + } finally { + + if (connection != null) { + connection.disconnect(); + } + } + } + + public static enum Privacy { + PUBLIC(0), UNLISTED(1), PRIVATE(2); + + private int privacy; + + Privacy(int privacy) { + this.privacy = privacy; + } + + public int getPrivacy() { + return privacy; + } + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/Pattern.java b/Impl/src/main/java/dev/brighten/anticheat/utils/Pattern.java new file mode 100644 index 000000000..0760cccd5 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/Pattern.java @@ -0,0 +1,92 @@ +package dev.brighten.anticheat.utils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class Pattern { + private int amount = 0; + private int low = 0; + private int high = 0; + private List allHighs = new ArrayList(); + private List allLows = new ArrayList(); + private List patternHigh = new ArrayList(); + private List patternLow = new ArrayList(); + + public int getAmount() { + return this.amount; + } + + public void setAmount(int amount) { + this.amount = amount; + } + + public int getLow() { + return this.low; + } + + public void setLow(int low) { + this.low = low; + } + + public int getHigh() { + return this.high; + } + + public void setHigh(int high) { + this.high = high; + } + + public void addHighs(int number) { + this.allHighs.add(number); + } + + public void addLows(int number) { + this.allLows.add(number); + } + + public static int getOscillation(List numbers) { + int highest = -1; + int lowest = -1; + Iterator iterator = numbers.iterator(); + while (iterator.hasNext()) { + int number = iterator.next(); + if (highest == -1) { + highest = number; + } + if (lowest == -1) { + lowest = number; + } + if (number > highest) { + highest = number; + } + if (number >= lowest) continue; + lowest = number; + } + return highest - lowest; + } + + public List getAllHighs() { + return this.allHighs; + } + + public List getAllLows() { + return this.allLows; + } + + public void addPatternHigh(int number) { + this.patternHigh.add(number); + } + + public void addPatternLow(int number) { + this.patternLow.add(number); + } + + public List getPatternHigh() { + return this.patternHigh; + } + + public List getPatternLow() { + return this.patternLow; + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/SystemUtil.java b/Impl/src/main/java/dev/brighten/anticheat/utils/SystemUtil.java new file mode 100644 index 000000000..d1556e64d --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/SystemUtil.java @@ -0,0 +1,83 @@ +package dev.brighten.anticheat.utils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.zip.CRC32; + +public class SystemUtil { + + public static final CRC32 CRC_32 = new CRC32(); + private static final Class[] parameters = new Class[]{URL.class}; + + public static void addPath(String s) throws Exception { + File f = new File(s); + URI u = f.toURI(); + URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Class urlClass = URLClassLoader.class; + Method method = urlClass.getDeclaredMethod("addURL", URL.class); + method.setAccessible(true); + method.invoke(urlClassLoader, u.toURL()); + } + + + + public static void addNativesPath(String pathToAdd) throws Exception { + Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths"); + usrPathsField.setAccessible(true); + + //get array of paths + String[] paths = (String[]) usrPathsField.get(null); + + //check if the path to add is already present + for (String path : paths) { + if (path.equals(pathToAdd)) { + return; + } + } + + //add the new path + String[] newPaths = Arrays.copyOf(paths, paths.length + 1); + newPaths[newPaths.length - 1] = pathToAdd; + usrPathsField.set(null, newPaths); + } + + public static void addURLToClassPath(URL u) throws IOException { + URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Class sysclass = URLClassLoader.class; + try { + Method method = sysclass.getDeclaredMethod("addURL", parameters); + method.setAccessible(true); + method.invoke(sysloader, u); + } catch (Throwable t) { + t.printStackTrace(); + throw new IOException("Error, could not add URL to system classloader"); + } + + } + + public static void addDir(String s) { + try { + Field field = ClassLoader.class.getDeclaredField("usr_paths"); + field.setAccessible(true); + String[] paths = (String[])field.get(null); + for (int i = 0; i < paths.length; i++) { + if (s.equals(paths[i])) { + return; + } + } + String[] tmp = new String[paths.length+1]; + System.arraycopy(paths,0,tmp,0,paths.length); + tmp[paths.length] = s; + field.set(null,tmp); + System.setProperty("java.library.path", System.getProperty("java.library.path") + File.pathSeparator + s); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/TickTimer.java b/Impl/src/main/java/dev/brighten/anticheat/utils/TickTimer.java new file mode 100644 index 000000000..05ffd9cb6 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/TickTimer.java @@ -0,0 +1,35 @@ +package dev.brighten.anticheat.utils; + +import dev.brighten.anticheat.Kauri; + +public class TickTimer { + private int ticks = Kauri.INSTANCE.keepaliveProcessor.tick, defaultPassed; + + public TickTimer(int defaultPassed) { + this.defaultPassed = defaultPassed; + } + + public void reset() { + ticks = Kauri.INSTANCE.keepaliveProcessor.tick; + } + + public boolean hasPassed() { + return Kauri.INSTANCE.keepaliveProcessor.tick - ticks > defaultPassed; + } + + public boolean hasPassed(int amount) { + return Kauri.INSTANCE.keepaliveProcessor.tick - ticks > amount; + } + + public boolean hasNotPassed() { + return Kauri.INSTANCE.keepaliveProcessor.tick - ticks <= defaultPassed; + } + + public boolean hasNotPassed(int amount) { + return Kauri.INSTANCE.keepaliveProcessor.tick - ticks <= amount; + } + + public int getPassed() { + return Kauri.INSTANCE.keepaliveProcessor.tick - ticks; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/Vec3D.java b/Impl/src/main/java/dev/brighten/anticheat/utils/Vec3D.java new file mode 100644 index 000000000..9f61608a7 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/Vec3D.java @@ -0,0 +1,190 @@ +// +// Source code recreated from a .class file by IntelliJ IDEA +// (powered by Fernflower decompiler) +// + +package dev.brighten.anticheat.utils; + +import cc.funkemunky.api.tinyprotocol.packet.types.MathHelper; +import org.bukkit.Location; + +import javax.annotation.Nullable; + +public class Vec3D { + public static final Vec3D a = new Vec3D(0.0D, 0.0D, 0.0D); + public final double x; + public final double y; + public final double z; + + public Vec3D(double var1, double var3, double var5) { + if (var1 == -0.0D) { + var1 = 0.0D; + } + + if (var3 == -0.0D) { + var3 = 0.0D; + } + + if (var5 == -0.0D) { + var5 = 0.0D; + } + + this.x = var1; + this.y = var3; + this.z = var5; + } + + public Vec3D(Location var1) { + this((double)var1.getX(), (double)var1.getY(), (double)var1.getZ()); + } + + public Vec3D a(Vec3D var1) { + return new Vec3D(var1.x - this.x, var1.y - this.y, var1.z - this.z); + } + + public Vec3D a() { + double var1 = (double) MathHelper.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + return var1 < 1.0E-4D ? a : new Vec3D(this.x / var1, this.y / var1, this.z / var1); + } + + public Vec3D clone() { + return new Vec3D(x, y, z); + } + + public double b(Vec3D var1) { + return this.x * var1.x + this.y * var1.y + this.z * var1.z; + } + + public Vec3D d(Vec3D var1) { + return this.a(var1.x, var1.y, var1.z); + } + + public Vec3D a(double var1, double var3, double var5) { + return this.add(-var1, -var3, -var5); + } + + public Vec3D e(Vec3D var1) { + return this.add(var1.x, var1.y, var1.z); + } + + public Vec3D add(double var1, double var3, double var5) { + return new Vec3D(this.x + var1, this.y + var3, this.z + var5); + } + + public double f(Vec3D var1) { + double var2 = var1.x - this.x; + double var4 = var1.y - this.y; + double var6 = var1.z - this.z; + return (double)MathHelper.sqrt(var2 * var2 + var4 * var4 + var6 * var6); + } + + public double distanceSquared(Vec3D var1) { + double var2 = var1.x - this.x; + double var4 = var1.y - this.y; + double var6 = var1.z - this.z; + return var2 * var2 + var4 * var4 + var6 * var6; + } + + public double c(double var1, double var3, double var5) { + double var7 = var1 - this.x; + double var9 = var3 - this.y; + double var11 = var5 - this.z; + return var7 * var7 + var9 * var9 + var11 * var11; + } + + public Vec3D a(double var1) { + return new Vec3D(this.x * var1, this.y * var1, this.z * var1); + } + + public double b() { + return (double)MathHelper.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + } + + @Nullable + public Vec3D a(Vec3D var1, double var2) { + double var4 = var1.x - this.x; + double var6 = var1.y - this.y; + double var8 = var1.z - this.z; + if (var4 * var4 < 1.0000000116860974E-7D) { + return null; + } else { + double var10 = (var2 - this.x) / var4; + return var10 >= 0.0D && var10 <= 1.0D ? new Vec3D(this.x + var4 * var10, this.y + var6 * var10, this.z + var8 * var10) : null; + } + } + + @Nullable + public Vec3D b(Vec3D var1, double var2) { + double var4 = var1.x - this.x; + double var6 = var1.y - this.y; + double var8 = var1.z - this.z; + if (var6 * var6 < 1.0000000116860974E-7D) { + return null; + } else { + double var10 = (var2 - this.y) / var6; + return var10 >= 0.0D && var10 <= 1.0D ? new Vec3D(this.x + var4 * var10, this.y + var6 * var10, this.z + var8 * var10) : null; + } + } + + @Nullable + public Vec3D c(Vec3D var1, double var2) { + double var4 = var1.x - this.x; + double var6 = var1.y - this.y; + double var8 = var1.z - this.z; + if (var8 * var8 < 1.0000000116860974E-7D) { + return null; + } else { + double var10 = (var2 - this.z) / var8; + return var10 >= 0.0D && var10 <= 1.0D ? new Vec3D(this.x + var4 * var10, this.y + var6 * var10, this.z + var8 * var10) : null; + } + } + + public boolean equals(Object var1) { + if (this == var1) { + return true; + } else if (!(var1 instanceof Vec3D)) { + return false; + } else { + Vec3D var2 = (Vec3D)var1; + if (Double.compare(var2.x, this.x) != 0) { + return false; + } else if (Double.compare(var2.y, this.y) != 0) { + return false; + } else { + return Double.compare(var2.z, this.z) == 0; + } + } + } + + public int hashCode() { + long var2 = Double.doubleToLongBits(this.x); + int var1 = (int)(var2 ^ var2 >>> 32); + var2 = Double.doubleToLongBits(this.y); + var1 = 31 * var1 + (int)(var2 ^ var2 >>> 32); + var2 = Double.doubleToLongBits(this.z); + var1 = 31 * var1 + (int)(var2 ^ var2 >>> 32); + return var1; + } + + public String toString() { + return "(" + this.x + ", " + this.y + ", " + this.z + ")"; + } + + public Vec3D a(float var1) { + float var2 = MathHelper.cos(var1); + float var3 = MathHelper.sin(var1); + double var4 = this.x; + double var6 = this.y * (double)var2 + this.z * (double)var3; + double var8 = this.z * (double)var2 - this.y * (double)var3; + return new Vec3D(var4, var6, var8); + } + + public Vec3D b(float var1) { + float var2 = MathHelper.cos(var1); + float var3 = MathHelper.sin(var1); + double var4 = this.x * (double)var2 + this.z * (double)var3; + double var6 = this.y; + double var8 = this.z * (double)var2 - this.x * (double)var3; + return new Vec3D(var4, var6, var8); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/Verbose.java b/Impl/src/main/java/dev/brighten/anticheat/utils/Verbose.java new file mode 100644 index 000000000..cbaa34726 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/Verbose.java @@ -0,0 +1,31 @@ +package dev.brighten.anticheat.utils; + +import cc.funkemunky.api.utils.math.cond.MaxDouble; + +public class Verbose { + private MaxDouble vl; + private double maxVl; + private TickTimer lastFlag; + + public Verbose(double maxVl, int resetTicks) { + vl = new MaxDouble(this.maxVl = maxVl); + lastFlag = new TickTimer(resetTicks); + } + + public boolean flag(double toAdd, double max) { + if(lastFlag.hasPassed()) { + vl.subtract(maxVl * 1.5); + } + lastFlag.reset(); + + return vl.add(toAdd) > max; + } + + public void subtract(double amount) { + vl.subtract(amount); + } + + public double value() { + return vl.value(); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/file/FileDownloader.java b/Impl/src/main/java/dev/brighten/anticheat/utils/file/FileDownloader.java new file mode 100644 index 000000000..c2b8f6318 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/file/FileDownloader.java @@ -0,0 +1,80 @@ +package dev.brighten.anticheat.utils.file; + +import lombok.Getter; +import lombok.Setter; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.ThreadLocalRandom; + +@Getter +@Setter +public class FileDownloader { + + private URL link; + private File realFile; + + private int downloadCounter; + + private boolean silentDownload; + + public FileDownloader() { + // + } + + public FileDownloader(String link) { + try { + this.link = new URL(link); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + } + + public File download() { + + InputStream inputStream = null; + File file = null; + FileOutputStream fos = null; + try { + + file = File.createTempFile(String.valueOf(ThreadLocalRandom.current().nextInt(99999999)), ""); + fos = new FileOutputStream(file); + URLConnection urlConn = link.openConnection(); + urlConn.setConnectTimeout(1000); + urlConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11"); + + inputStream = urlConn.getInputStream(); + + byte[] buffer = new byte[4096]; + int length; + while ((length = inputStream.read(buffer)) > 0) { + fos.write(buffer, 0, length); + } + + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + this.realFile = file; + return file; + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/file/JarUtil.java b/Impl/src/main/java/dev/brighten/anticheat/utils/file/JarUtil.java new file mode 100644 index 000000000..5d93a1824 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/file/JarUtil.java @@ -0,0 +1,96 @@ +package dev.brighten.anticheat.utils.file; + +import java.io.*; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class JarUtil { + + private JarUtil() { + } + + public static Map loadJar(File jarFile) { + try { + Map classes = new HashMap<>(); + JarFile jar = new JarFile(jarFile); + Enumeration enumeration = jar.entries(); + while (enumeration.hasMoreElements()) { + JarEntry entry = enumeration.nextElement(); + readJar(jar, entry, classes, null); + } + jar.close(); + return classes; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + private static Map readJar(JarFile jar, JarEntry en, Map classes, List ignored) { + String name = en.getName(); + try (InputStream jis = jar.getInputStream(en)) { + if (name.endsWith(".class")) { + if (ignored != null) { + for (String s : ignored) { + if (name.startsWith(s)) { + return classes; + } + } + } + byte[] bytes = getBytes(jis); + try { + classes.put(name, bytes); + } catch (Exception e) { + e.printStackTrace(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return classes; + } + + public static Map loadNonClassEntries(File jarFile) { + Map entries = new HashMap<>(); + try { + ZipInputStream jis = new ZipInputStream(new FileInputStream(jarFile)); + ZipEntry entry; + while ((entry = jis.getNextEntry()) != null) { + try { + String name = entry.getName(); + if (!name.endsWith(".class")) { + entries.put(name, getBytes(jis)); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + jis.closeEntry(); + } + } + jis.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return entries; + } + + private static byte[] getBytes(InputStream inputStream) { + try { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[16384]; + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + return buffer.toByteArray(); + } catch (IOException e) { + return new byte[0]; + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/Menu.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/Menu.java new file mode 100644 index 000000000..8e1c61714 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/Menu.java @@ -0,0 +1,145 @@ +package dev.brighten.anticheat.utils.menu; + +import dev.brighten.anticheat.utils.menu.button.Button; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.bukkit.entity.Player; + +import java.util.Optional; +import java.util.function.BiConsumer; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 3/28/2018 + */ +public interface Menu extends Iterable { + + /** + * Returns the Dimensions of the menu (R, C) + * + * @return The menu's row and column count + */ + MenuDimension getMenuDimension(); + + /** + * Allows for a 'parent' menu + * (e.g. You have a GUI based punishment history system + * and the main menu shows: Bans, Mutes, and Warnings. + * When you choose the option you open a new GUI that lists the information, + * then on close it re-opens the list menu) + * + * @return the {@link Optional< Menu >} + */ + Optional getParent(); + + /** + * Add a {@link Button} to the menu with no specified index + * + * @param button The button to be added + */ + void addItem(Button button); + + /** + * Sets an {@link Button} in the menu with a specified index + * + * @param index The location of the button to be added + * @param button The button to be added + */ + void setItem(int index, Button button); + + /** + * Fills the entire menu with the specified {@link Button} + * This is backed by {@link #fillRange(int, int, Button)} + * + * @param button The button to use for the procedure + */ + void fill(Button button); + + /** + * Fills the menu from a index to the ending index with a {@link Button} + * + * @param startingIndex The index to start the procedure + * @param endingIndex The index at which the procedure terminates + * @param button The button to be used in the procedure + */ + void fillRange(int startingIndex, int endingIndex, Button button); + + /** + * Gets the first empty slot in the inventory + * + * @return the first empty slot or else -1 + */ + int getFirstEmptySlot(); + + /** + * Checks to see if the index is within the bounds of the {@link MenuDimension} + * + * @param index The index to check with + * @throws IndexOutOfBoundsException If the index is out of bounds this is thrown + */ + + public void checkBounds(int index) throws IndexOutOfBoundsException; + + /** + * Gets the {@link Button} at the specified index + * + * @param index Location to get the button from + * @return A {@link Optional} that may or may not contain the requested button + */ + Optional getButtonByIndex(int index); + + /** + * Method used to construct the inventory in conjunction with {@link BukkitInventoryHolder} + * + * @param initial Is the first build of the menu which needs Bukkit inventory stuff done. + */ + void buildInventory(boolean initial); + + /** + * Open's the menu for the specified {@link Player} + * + * @param player The player to open the menu for + */ + void showMenu(Player player); + + /** + * Closes the menu for the specified {@link Player} + * Note: This method should call the {@link CloseHandler} if it is present + * + * @param player The player to close the menu for + */ + void close(Player player); + + /** + * Setter method for the {@link CloseHandler} + * + * @param handler The handler to set with + */ + void setCloseHandler(CloseHandler handler); + + /** + * Calls the {@link CloseHandler} + * Note: Should only be called from the {@link #close(Player)} method + * + * @param player The player to provide to the backing {@link BiConsumer} + */ + void handleClose(Player player); + + /** + * A blank interface that extends {@link BiConsumer} for our usage. + */ + interface CloseHandler extends BiConsumer { + } + + @Getter + @EqualsAndHashCode + @AllArgsConstructor + class MenuDimension { + private final int rows, columns; + + public int getSize() { + return rows * columns; + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/MenuListener.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/MenuListener.java new file mode 100644 index 000000000..a746e9265 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/MenuListener.java @@ -0,0 +1,116 @@ +package dev.brighten.anticheat.utils.menu; + +import cc.funkemunky.api.utils.Color; +import cc.funkemunky.api.utils.Init; +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.Kauri; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.button.ClickAction; +import dev.brighten.anticheat.utils.menu.type.BukkitInventoryHolder; +import dev.brighten.anticheat.utils.menu.type.impl.ValueMenu; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 2/21/2018 + */ +@Init +public class MenuListener implements Listener { + + public static Map anvils = new HashMap<>(); + + @EventHandler(priority = EventPriority.LOW) + private void onInventoryClick(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player)) return; + + final InventoryView inventoryView = event.getView(); + final Inventory inventory = inventoryView.getTopInventory(); + + if (inventory.getHolder() instanceof BukkitInventoryHolder && ((Player) event.getWhoClicked()).isOnline()) { + Menu menu = ((BukkitInventoryHolder) inventory.getHolder()).getMenu(); + + event.setCancelled(true); + + if (menu != null) { + final ItemStack stack = event.getCurrentItem(); + if ((stack == null || stack.getType() == XMaterial.AIR.parseMaterial())) + return; + + int slot = event.getSlot(); + if (slot >= 0 && slot <= menu.getMenuDimension().getSize()) { + + Optional buttonOptional = menu.getButtonByIndex(slot); + + buttonOptional.ifPresent(button -> { + + if (button.getConsumer() == null) { // Allows for Buttons to not have an action. + return; + } + button.getConsumer().accept((Player) event.getWhoClicked(), + new ClickAction.InformationPair(button, event.getClick(), menu)); + + if (!button.isMoveable()) { + event.setResult(Event.Result.DENY); + event.setCancelled(true); + } + }); + } + } + } + if(!event.getAction().equals(InventoryAction.NOTHING) + && inventory instanceof AnvilInventory + && anvils.containsKey(inventory)) { + event.setResult(Event.Result.DENY); + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + private void onInventoryClose(InventoryCloseEvent event) { + if (!(event.getPlayer() instanceof Player)) return; + + final InventoryView inventoryView = event.getView(); + final Inventory inventory = inventoryView.getTopInventory(); + + if (inventory.getHolder() instanceof BukkitInventoryHolder) { + Menu menu = ((BukkitInventoryHolder) inventory.getHolder()).getMenu(); + + if (menu != null) { + menu.handleClose((Player) event.getPlayer()); + + menu.getParent().ifPresent(buttons -> new BukkitRunnable() { + public void run() { + if (event.getPlayer().getOpenInventory() == null + || (!(event.getPlayer().getOpenInventory().getTopInventory().getHolder() + instanceof BukkitInventoryHolder))) { + buttons.showMenu((Player) event.getPlayer()); + this.cancel(); + } + } + }.runTaskTimer(Kauri.INSTANCE, 2L, 0L)); + } + } + if(inventory instanceof AnvilInventory && anvils.containsKey(inventory)) { + AnvilInventory anvil = (AnvilInventory) inventory; + + anvils.get(anvil).consumer.accept(event.getPlayer(), Color.translate(anvil.getName())); + anvils.remove(anvil); + } + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/button/Button.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/button/Button.java new file mode 100644 index 000000000..26d6c1770 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/button/Button.java @@ -0,0 +1,30 @@ +package dev.brighten.anticheat.utils.menu.button; + +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.bukkit.inventory.ItemStack; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 2/21/2018 + */ +@Getter +public class Button { + + @Setter + private boolean moveable; + @Setter + private ItemStack stack; + private ClickAction consumer; + + public Button(boolean moveable, @NonNull ItemStack stack, ClickAction consumer) { + this.moveable = moveable; + this.stack = stack; + this.consumer = consumer; + } + + public Button(boolean moveable, ItemStack stack) { + this(moveable, stack, null); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/button/ClickAction.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/button/ClickAction.java new file mode 100644 index 000000000..f6d950c8d --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/button/ClickAction.java @@ -0,0 +1,34 @@ +package dev.brighten.anticheat.utils.menu.button; + +import dev.brighten.anticheat.utils.menu.Menu; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 3/2/2018 + */ +@FunctionalInterface +public interface ClickAction { + + /** + * Preforms an operation on the given argument(s) + * + * @param player The {@link Player} that has acted + * @param buttonClickTypeInformationPair The {@link InformationPair} that contains the {@link Button} clicked, the {@link Menu}, and the {@link ClickType} + */ + void accept(Player player, InformationPair buttonClickTypeInformationPair); + + @Getter + @Setter + @AllArgsConstructor + class InformationPair { + private Button button; + private ClickType clickType; + private Menu menu; + } + +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/mask/Mask.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/mask/Mask.java new file mode 100644 index 000000000..d8eb76a79 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/mask/Mask.java @@ -0,0 +1,17 @@ +package dev.brighten.anticheat.utils.menu.mask; + +import dev.brighten.anticheat.utils.menu.Menu; +import dev.brighten.anticheat.utils.menu.button.Button; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 5/17/2018 + */ +public interface Mask { + + Mask setButton(char key, Button button); + + Mask setMaskPattern(String... maskPattern); + + void applyTo(Menu menu); +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/mask/Mask2D.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/mask/Mask2D.java new file mode 100644 index 000000000..dbb786153 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/mask/Mask2D.java @@ -0,0 +1,63 @@ +package dev.brighten.anticheat.utils.menu.mask; + +import dev.brighten.anticheat.utils.menu.Menu; +import dev.brighten.anticheat.utils.menu.button.Button; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.IntStream; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 5/17/2018 + * + * Note: The use of a blank space ( ) or underscore (_) will remove the item. + */ +public class Mask2D implements Mask { + + private final Map maskButtonKey; + private String maskPattern; + + public Mask2D() { + this.maskButtonKey = new HashMap<>(); + } + + @Override + public Mask setButton(char key, Button button) { + maskButtonKey.put(key, button); + return this; + } + + @Override + public Mask setMaskPattern(String... maskPattern) { + String concatPattern = String.join("", maskPattern); + for (char c : concatPattern.toCharArray()) { + if (Character.isWhitespace(c)) { + continue; + } + if (!maskButtonKey.containsKey(c)) { + throw new IllegalArgumentException(String.format("%s is a unrecognized mapping for the Mask.", c)); + } + } + this.maskPattern = concatPattern.replace("\n", ""); + return this; + } + + @Override + public void applyTo(Menu menu) { + if (maskButtonKey.isEmpty()) { + throw new IllegalArgumentException("The maskButtonKey map is empty!"); + } + if (maskPattern == null) { + throw new IllegalArgumentException("The maskPattern is null!"); + } + if (maskPattern.length() > menu.getMenuDimension().getSize()) { + throw new IllegalArgumentException(String.format("The maskPattern length: %d is longer than the menu dimension: %d", maskPattern.length(), menu.getMenuDimension().getSize())); + } + + IntStream.range(0, maskPattern.length()).forEach(i -> { + char ch = maskPattern.charAt(i); + menu.setItem(i, ch == ' ' || ch == '_' ? null : maskButtonKey.get(ch)); + }); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/ConfirmationMenu.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/ConfirmationMenu.java new file mode 100644 index 000000000..503e1d1ab --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/ConfirmationMenu.java @@ -0,0 +1,31 @@ +package dev.brighten.anticheat.utils.menu.preset; + +import cc.funkemunky.api.utils.ItemBuilder; +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.preset.button.FillerButton; +import dev.brighten.anticheat.utils.menu.type.impl.ChestMenu; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.function.BiConsumer; + +public class ConfirmationMenu extends ChestMenu { + + public ConfirmationMenu(String message, BiConsumer function) { + super(message, 1); + fill(new FillerButton()); + setItem(2, new Button(false, new ItemBuilder(XMaterial.LIME_DYE.parseMaterial()).durability(10) + .name(ChatColor.GREEN + "Accept").build(), ((player, buttonClickTypeInformationPair) -> { + function.accept(player, true); + setCloseHandler(null); + close(player); + }))); + setItem(6, new Button(false, new ItemBuilder(XMaterial.ROSE_RED.parseMaterial()) + .durability(1).name(ChatColor.RED + "Deny").build(), (((player, buttonClickTypeInformationPair) -> { + function.accept(player, false); + close(player); + })))); + setCloseHandler((player, buttons) -> function.accept(player, false)); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/button/FillerButton.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/button/FillerButton.java new file mode 100644 index 000000000..d107d6ba5 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/button/FillerButton.java @@ -0,0 +1,13 @@ +package dev.brighten.anticheat.utils.menu.preset.button; + +import cc.funkemunky.api.utils.ItemBuilder; +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.utils.menu.button.Button; + +public class FillerButton extends Button { + + public FillerButton() { + super(false, new ItemBuilder(XMaterial.GRAY_STAINED_GLASS_PANE.parseMaterial()).name(" ") + .durability(15).build()); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/BukkitInventoryHolder.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/BukkitInventoryHolder.java new file mode 100644 index 000000000..596ccde53 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/BukkitInventoryHolder.java @@ -0,0 +1,37 @@ +package dev.brighten.anticheat.utils.menu.type; + +import dev.brighten.anticheat.utils.menu.Menu; +import lombok.NonNull; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 3/28/2018 + */ +public class BukkitInventoryHolder implements InventoryHolder { + + private Menu menu; + private Inventory inventory; + + public BukkitInventoryHolder(Menu menu) { + this.menu = menu; + } + + public Menu getMenu() { + return menu; + } + + public void setMenu(@NonNull Menu menu) { + this.menu = menu; + } + + @Override + public Inventory getInventory() { + return inventory; + } + + public void setInventory(@NonNull Inventory inventory) { + this.inventory = inventory; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/BookMenu.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/BookMenu.java new file mode 100644 index 000000000..e51879a47 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/BookMenu.java @@ -0,0 +1,14 @@ +package dev.brighten.anticheat.utils.menu.type.impl; + +import org.bukkit.entity.Player; + +public class BookMenu { + + public BookMenu(String... contents) { + + } + + public void openBook(Player player) { + + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/ChestMenu.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/ChestMenu.java new file mode 100644 index 000000000..a508c3148 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/ChestMenu.java @@ -0,0 +1,148 @@ +package dev.brighten.anticheat.utils.menu.type.impl; + +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.utils.menu.Menu; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.type.BukkitInventoryHolder; +import dev.brighten.anticheat.utils.menu.util.ArrayIterator; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.Iterator; +import java.util.Optional; +import java.util.stream.IntStream; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 3/28/2018 + */ +public class ChestMenu implements Menu { + + @Getter + @Setter + String title; + final MenuDimension dimension; + @Setter + private Menu parent; + @Getter + BukkitInventoryHolder holder; + public Button[] contents; + private CloseHandler closeHandler; + + public ChestMenu(@NonNull String title, int size) { + this.title = title.length() > 32 ? title.substring(0, 32) : title; + if (size <= 0 || size > 6) { + throw new IndexOutOfBoundsException("A menu can only have between 1 & 6 for a size (rows)"); + } + this.dimension = new MenuDimension(size, 9); + this.contents = new Button[size * 9]; + } + + @Override + public MenuDimension getMenuDimension() { + return dimension; + } + + @Override + public void addItem(Button button) { + setItem(getFirstEmptySlot(), button); + } + + @Override + public void setItem(int index, Button button) { + checkBounds(index); + contents[index] = button; + } + + @Override + public void fill(Button button) { + fillRange(0, dimension.getSize(), button); + } + + @Override + public void fillRange(int startingIndex, int endingIndex, Button button) { + IntStream.range(startingIndex, endingIndex) + .filter(i -> contents[i] == null || contents[i].getStack().getType() + .equals(XMaterial.AIR.parseMaterial())) + .forEach(i -> setItem(i, button)); + } + + @Override + public int getFirstEmptySlot() { + for (int i = 0; i < contents.length; i++) { + Button button = contents[i]; + if (button == null) { + return i; + } + } + return -1; // Will throw when #checkBounds is called. + } + + @Override + public void checkBounds(int index) throws IndexOutOfBoundsException { + if (index < 0 || index > (dimension.getSize())) { + throw new IndexOutOfBoundsException(String.format("setItem(); %s is out of bounds!", index)); + } + } + + @Override + public Optional getButtonByIndex(int index) { + return Optional.ofNullable(contents[index]); + } + + @Override + public void buildInventory(boolean initial) { + if (initial) { + this.holder = new BukkitInventoryHolder(this); + holder.setInventory(Bukkit.createInventory(holder, dimension.getSize(), title)); + } + holder.getInventory().clear(); + IntStream.range(0, contents.length).forEach(i -> { + Button button = contents[i]; + if (button != null) { + holder.getInventory().setItem(i, button.getStack()); + } + }); + } + + @Override + public void showMenu(Player player) { + if (holder == null) { + buildInventory(true); + } else { + buildInventory(false); + } + player.openInventory(holder.getInventory()); + } + + @Override + public void close(Player player) { + player.closeInventory(); + handleClose(player); + } + + @Override + public void setCloseHandler(CloseHandler handler) { + this.closeHandler = handler; + } + + @Override + public void handleClose(Player player) { + if (closeHandler != null) { + closeHandler.accept(player, this); + } + } + + @Override + public Optional getParent() { + return Optional.ofNullable(parent); + } + + @Override + public Iterator iterator() { + return new ArrayIterator<>(contents); + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/PagedMenu.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/PagedMenu.java new file mode 100644 index 000000000..87a4394bd --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/PagedMenu.java @@ -0,0 +1,151 @@ +package dev.brighten.anticheat.utils.menu.type.impl; + +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.utils.menu.Menu; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.type.BukkitInventoryHolder; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +public class PagedMenu implements Menu { + @Getter + @Setter + String title; + final MenuDimension dimension; + @Setter + private Menu parent; + @Getter + @Setter + private int currentPage; + @Getter + BukkitInventoryHolder holder; + public List contents; + private CloseHandler closeHandler; + public PagedMenu(@NonNull String title, int size) { + this.title = title.length() > 32 ? title.substring(0, 32) : title; + if (size <= 0 || size > 6) { + throw new IndexOutOfBoundsException("A menu can only have between 1 & 6 for a size (rows)"); + } + this.dimension = new MenuDimension(size, 9); + this.contents = new ArrayList<>(); + } + + @Override + public MenuDimension getMenuDimension() { + return dimension; + } + + @Override + public void addItem(Button button) { + setItem(getFirstEmptySlot(), button); + } + + @Override + public void setItem(int index, Button button) { + checkBounds(index); + contents.add(index, button); + } + + @Override + public void fill(Button button) { + fillRange(0, dimension.getSize(), button); + } + + @Override + public void fillRange(int startingIndex, int endingIndex, Button button) { + IntStream.range(startingIndex, endingIndex) + .filter(i -> contents.get(i) == null || contents.get(i).getStack().getType() + .equals(XMaterial.AIR.parseMaterial())) + .forEach(i -> setItem(i, button)); + } + + @Override + public int getFirstEmptySlot() { + for (int i = 0; i < contents.size(); i++) { + Button button = contents.get(i); + if (button == null) { + return i; + } + } + return -1; // Will throw when #checkBounds is called. + } + + @Override + public void checkBounds(int index) throws IndexOutOfBoundsException { + if (index < 0 || index > (dimension.getSize())) { + throw new IndexOutOfBoundsException(String.format("setItem(); %s is out of bounds!", index)); + } + } + + @Override + public Optional getButtonByIndex(int index) { + if(index >= contents.size() - 1) return Optional.empty(); + + return Optional.ofNullable(contents.get(index)); + } + + @Override + public void buildInventory(boolean initial) { + if (initial) { + this.holder = new BukkitInventoryHolder(this); + holder.setInventory(Bukkit.createInventory(holder, dimension.getSize(), title)); + } + holder.getInventory().clear(); + int size = (dimension.getRows() - 1) * dimension.getColumns(); + IntStream.range(Math.min(contents.size(), size * (currentPage - 1)), + Math.min(contents.size(), size * currentPage)) + .forEach(i -> { + Button button = contents.get(i); + if (button != null) { + holder.getInventory().setItem(i, button.getStack()); + } + }); + } + + @Override + public void showMenu(Player player) { + if (holder == null) { + buildInventory(true); + } else { + buildInventory(false); + } + player.openInventory(holder.getInventory()); + } + + @Override + public void close(Player player) { + player.closeInventory(); + handleClose(player); + } + + @Override + public void setCloseHandler(CloseHandler handler) { + this.closeHandler = handler; + } + + @Override + public void handleClose(Player player) { + if (closeHandler != null) { + closeHandler.accept(player, this); + } + } + + @Override + public Optional
+ * Note: The use of a blank space ( ) or underscore (_) will remove the item. + */ +public class Mask2D implements Mask { + + private final Map maskButtonKey; + private String maskPattern; + + public Mask2D() { + this.maskButtonKey = new HashMap<>(); + } + + @Override + public Mask setButton(char key, Button button) { + maskButtonKey.put(key, button); + return this; + } + + @Override + public Mask setMaskPattern(String... maskPattern) { + String concatPattern = String.join("", maskPattern); + for (char c : concatPattern.toCharArray()) { + if (Character.isWhitespace(c)) { + continue; + } + if (!maskButtonKey.containsKey(c)) { + throw new IllegalArgumentException(String.format("%s is a unrecognized mapping for the Mask.", c)); + } + } + this.maskPattern = concatPattern.replace("\n", ""); + return this; + } + + @Override + public void applyTo(Menu menu) { + if (maskButtonKey.isEmpty()) { + throw new IllegalArgumentException("The maskButtonKey map is empty!"); + } + if (maskPattern == null) { + throw new IllegalArgumentException("The maskPattern is null!"); + } + if (maskPattern.length() > menu.getMenuDimension().getSize()) { + throw new IllegalArgumentException(String.format("The maskPattern length: %d is longer than the menu dimension: %d", maskPattern.length(), menu.getMenuDimension().getSize())); + } + + IntStream.range(0, maskPattern.length()).forEach(i -> { + char ch = maskPattern.charAt(i); + menu.setItem(i, ch == ' ' || ch == '_' ? null : maskButtonKey.get(ch)); + }); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/ConfirmationMenu.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/ConfirmationMenu.java new file mode 100644 index 000000000..503e1d1ab --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/ConfirmationMenu.java @@ -0,0 +1,31 @@ +package dev.brighten.anticheat.utils.menu.preset; + +import cc.funkemunky.api.utils.ItemBuilder; +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.preset.button.FillerButton; +import dev.brighten.anticheat.utils.menu.type.impl.ChestMenu; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.function.BiConsumer; + +public class ConfirmationMenu extends ChestMenu { + + public ConfirmationMenu(String message, BiConsumer function) { + super(message, 1); + fill(new FillerButton()); + setItem(2, new Button(false, new ItemBuilder(XMaterial.LIME_DYE.parseMaterial()).durability(10) + .name(ChatColor.GREEN + "Accept").build(), ((player, buttonClickTypeInformationPair) -> { + function.accept(player, true); + setCloseHandler(null); + close(player); + }))); + setItem(6, new Button(false, new ItemBuilder(XMaterial.ROSE_RED.parseMaterial()) + .durability(1).name(ChatColor.RED + "Deny").build(), (((player, buttonClickTypeInformationPair) -> { + function.accept(player, false); + close(player); + })))); + setCloseHandler((player, buttons) -> function.accept(player, false)); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/button/FillerButton.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/button/FillerButton.java new file mode 100644 index 000000000..d107d6ba5 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/preset/button/FillerButton.java @@ -0,0 +1,13 @@ +package dev.brighten.anticheat.utils.menu.preset.button; + +import cc.funkemunky.api.utils.ItemBuilder; +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.utils.menu.button.Button; + +public class FillerButton extends Button { + + public FillerButton() { + super(false, new ItemBuilder(XMaterial.GRAY_STAINED_GLASS_PANE.parseMaterial()).name(" ") + .durability(15).build()); + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/BukkitInventoryHolder.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/BukkitInventoryHolder.java new file mode 100644 index 000000000..596ccde53 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/BukkitInventoryHolder.java @@ -0,0 +1,37 @@ +package dev.brighten.anticheat.utils.menu.type; + +import dev.brighten.anticheat.utils.menu.Menu; +import lombok.NonNull; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 3/28/2018 + */ +public class BukkitInventoryHolder implements InventoryHolder { + + private Menu menu; + private Inventory inventory; + + public BukkitInventoryHolder(Menu menu) { + this.menu = menu; + } + + public Menu getMenu() { + return menu; + } + + public void setMenu(@NonNull Menu menu) { + this.menu = menu; + } + + @Override + public Inventory getInventory() { + return inventory; + } + + public void setInventory(@NonNull Inventory inventory) { + this.inventory = inventory; + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/BookMenu.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/BookMenu.java new file mode 100644 index 000000000..e51879a47 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/BookMenu.java @@ -0,0 +1,14 @@ +package dev.brighten.anticheat.utils.menu.type.impl; + +import org.bukkit.entity.Player; + +public class BookMenu { + + public BookMenu(String... contents) { + + } + + public void openBook(Player player) { + + } +} diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/ChestMenu.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/ChestMenu.java new file mode 100644 index 000000000..a508c3148 --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/ChestMenu.java @@ -0,0 +1,148 @@ +package dev.brighten.anticheat.utils.menu.type.impl; + +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.utils.menu.Menu; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.type.BukkitInventoryHolder; +import dev.brighten.anticheat.utils.menu.util.ArrayIterator; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.Iterator; +import java.util.Optional; +import java.util.stream.IntStream; + +/** + * @author Missionary (missionarymc@gmail.com) + * @since 3/28/2018 + */ +public class ChestMenu implements Menu { + + @Getter + @Setter + String title; + final MenuDimension dimension; + @Setter + private Menu parent; + @Getter + BukkitInventoryHolder holder; + public Button[] contents; + private CloseHandler closeHandler; + + public ChestMenu(@NonNull String title, int size) { + this.title = title.length() > 32 ? title.substring(0, 32) : title; + if (size <= 0 || size > 6) { + throw new IndexOutOfBoundsException("A menu can only have between 1 & 6 for a size (rows)"); + } + this.dimension = new MenuDimension(size, 9); + this.contents = new Button[size * 9]; + } + + @Override + public MenuDimension getMenuDimension() { + return dimension; + } + + @Override + public void addItem(Button button) { + setItem(getFirstEmptySlot(), button); + } + + @Override + public void setItem(int index, Button button) { + checkBounds(index); + contents[index] = button; + } + + @Override + public void fill(Button button) { + fillRange(0, dimension.getSize(), button); + } + + @Override + public void fillRange(int startingIndex, int endingIndex, Button button) { + IntStream.range(startingIndex, endingIndex) + .filter(i -> contents[i] == null || contents[i].getStack().getType() + .equals(XMaterial.AIR.parseMaterial())) + .forEach(i -> setItem(i, button)); + } + + @Override + public int getFirstEmptySlot() { + for (int i = 0; i < contents.length; i++) { + Button button = contents[i]; + if (button == null) { + return i; + } + } + return -1; // Will throw when #checkBounds is called. + } + + @Override + public void checkBounds(int index) throws IndexOutOfBoundsException { + if (index < 0 || index > (dimension.getSize())) { + throw new IndexOutOfBoundsException(String.format("setItem(); %s is out of bounds!", index)); + } + } + + @Override + public Optional getButtonByIndex(int index) { + return Optional.ofNullable(contents[index]); + } + + @Override + public void buildInventory(boolean initial) { + if (initial) { + this.holder = new BukkitInventoryHolder(this); + holder.setInventory(Bukkit.createInventory(holder, dimension.getSize(), title)); + } + holder.getInventory().clear(); + IntStream.range(0, contents.length).forEach(i -> { + Button button = contents[i]; + if (button != null) { + holder.getInventory().setItem(i, button.getStack()); + } + }); + } + + @Override + public void showMenu(Player player) { + if (holder == null) { + buildInventory(true); + } else { + buildInventory(false); + } + player.openInventory(holder.getInventory()); + } + + @Override + public void close(Player player) { + player.closeInventory(); + handleClose(player); + } + + @Override + public void setCloseHandler(CloseHandler handler) { + this.closeHandler = handler; + } + + @Override + public void handleClose(Player player) { + if (closeHandler != null) { + closeHandler.accept(player, this); + } + } + + @Override + public Optional getParent() { + return Optional.ofNullable(parent); + } + + @Override + public Iterator iterator() { + return new ArrayIterator<>(contents); + } +} \ No newline at end of file diff --git a/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/PagedMenu.java b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/PagedMenu.java new file mode 100644 index 000000000..87a4394bd --- /dev/null +++ b/Impl/src/main/java/dev/brighten/anticheat/utils/menu/type/impl/PagedMenu.java @@ -0,0 +1,151 @@ +package dev.brighten.anticheat.utils.menu.type.impl; + +import cc.funkemunky.api.utils.XMaterial; +import dev.brighten.anticheat.utils.menu.Menu; +import dev.brighten.anticheat.utils.menu.button.Button; +import dev.brighten.anticheat.utils.menu.type.BukkitInventoryHolder; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +public class PagedMenu implements Menu { + @Getter + @Setter + String title; + final MenuDimension dimension; + @Setter + private Menu parent; + @Getter + @Setter + private int currentPage; + @Getter + BukkitInventoryHolder holder; + public List contents; + private CloseHandler closeHandler; + public PagedMenu(@NonNull String title, int size) { + this.title = title.length() > 32 ? title.substring(0, 32) : title; + if (size <= 0 || size > 6) { + throw new IndexOutOfBoundsException("A menu can only have between 1 & 6 for a size (rows)"); + } + this.dimension = new MenuDimension(size, 9); + this.contents = new ArrayList<>(); + } + + @Override + public MenuDimension getMenuDimension() { + return dimension; + } + + @Override + public void addItem(Button button) { + setItem(getFirstEmptySlot(), button); + } + + @Override + public void setItem(int index, Button button) { + checkBounds(index); + contents.add(index, button); + } + + @Override + public void fill(Button button) { + fillRange(0, dimension.getSize(), button); + } + + @Override + public void fillRange(int startingIndex, int endingIndex, Button button) { + IntStream.range(startingIndex, endingIndex) + .filter(i -> contents.get(i) == null || contents.get(i).getStack().getType() + .equals(XMaterial.AIR.parseMaterial())) + .forEach(i -> setItem(i, button)); + } + + @Override + public int getFirstEmptySlot() { + for (int i = 0; i < contents.size(); i++) { + Button button = contents.get(i); + if (button == null) { + return i; + } + } + return -1; // Will throw when #checkBounds is called. + } + + @Override + public void checkBounds(int index) throws IndexOutOfBoundsException { + if (index < 0 || index > (dimension.getSize())) { + throw new IndexOutOfBoundsException(String.format("setItem(); %s is out of bounds!", index)); + } + } + + @Override + public Optional getButtonByIndex(int index) { + if(index >= contents.size() - 1) return Optional.empty(); + + return Optional.ofNullable(contents.get(index)); + } + + @Override + public void buildInventory(boolean initial) { + if (initial) { + this.holder = new BukkitInventoryHolder(this); + holder.setInventory(Bukkit.createInventory(holder, dimension.getSize(), title)); + } + holder.getInventory().clear(); + int size = (dimension.getRows() - 1) * dimension.getColumns(); + IntStream.range(Math.min(contents.size(), size * (currentPage - 1)), + Math.min(contents.size(), size * currentPage)) + .forEach(i -> { + Button button = contents.get(i); + if (button != null) { + holder.getInventory().setItem(i, button.getStack()); + } + }); + } + + @Override + public void showMenu(Player player) { + if (holder == null) { + buildInventory(true); + } else { + buildInventory(false); + } + player.openInventory(holder.getInventory()); + } + + @Override + public void close(Player player) { + player.closeInventory(); + handleClose(player); + } + + @Override + public void setCloseHandler(CloseHandler handler) { + this.closeHandler = handler; + } + + @Override + public void handleClose(Player player) { + if (closeHandler != null) { + closeHandler.accept(player, this); + } + } + + @Override + public Optional