Skip to content

Commit

Permalink
update default config and improve compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
xGinko committed Aug 4, 2024
1 parent 9abef08 commit 6b8f4bf
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,33 @@
import com.github.retrooper.packetevents.event.PacketSendEvent;
import com.github.retrooper.packetevents.netty.buffer.ByteBufHelper;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.packettype.PacketTypeCommon;
import me.xginko.aef.utils.EntityUtil;
import me.xginko.aef.utils.MaterialUtil;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;

import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

public class InventoryLag extends PacketModule implements Listener {

private final Cache<UUID, PlayerData> playerDataCache;
private final Set<PacketTypeCommon> measuredPacketTypes;
private final long rateLimitBytes, lockoutBytes, lockoutMillis;
private final int screenOpenLimit, screenOpenDelay;
private final boolean closeInventory, log;
Expand All @@ -40,10 +52,10 @@ public InventoryLag() {
Note: Closing has to be scheduled so it will take a bit if the server is heavily\s
lagging.""");
this.playerDataCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofMillis(Math.max(1L,
config.getLong(configPath + ".byte-data-keep-time-millis", 18000, """
config.getLong(configPath + ".byte-data-keep-time-millis", 30_000, """
The time in millis in which to check if the player exceeded the limit.\s
Needs to be at least as long as your lockout duration millis.""")))).build();
this.rateLimitBytes = config.getLong(configPath + ".rate-limit.bytesize-limit", 10240000, """
this.rateLimitBytes = config.getLong(configPath + ".rate-limit.bytesize-limit", 24_000_000, """
The limit in bytes the server has sent the server in the form of ItemStacks,\s
before the player will be put on a rate-limit.\s
Should always be lower than your lockout bytesize limit.""");
Expand All @@ -52,14 +64,26 @@ public InventoryLag() {
but not more.""");
this.screenOpenLimit = config.getInt(configPath + ".rate-limit.max-window-opens-per-timeframe", 2, """
The amount of windows that can be opened during the timeframe-millis.""");
this.lockoutBytes = config.getLong(configPath + ".lockout.bytesize-limit", 20480000, """
this.lockoutBytes = config.getLong(configPath + ".lockout.bytesize-limit", 48_000_000, """
The upper limit in bytes a player is allowed to request from the server\s
within the configured timeframe before he will be put on cooldown.\s
During the cooldown, he will not be able to open any inventory screens\s
or interact with items.""");
this.lockoutMillis = config.getLong(configPath + ".lockout.duration-millis", 10000, """
this.lockoutMillis = config.getLong(configPath + ".lockout.duration-millis", 15_000, """
The time in milliseconds the player will have to wait before\s
being able to open an inventory again after he exceeded the limit.""");
this.measuredPacketTypes = config.getList(configPath + ".check-packets", List.of("SET_SLOT", "WINDOW_ITEMS"))
.stream()
.map(configuredPacketType -> {
try {
return PacketType.Play.Server.valueOf(configuredPacketType);
} catch (IllegalArgumentException e) {
notRecognized(PacketType.Play.Server.class, configuredPacketType);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toCollection(HashSet::new));
}

@Override
Expand All @@ -80,66 +104,95 @@ public void disable() {
}

@Override
@SuppressWarnings("DataFlowIssue")
public void onPacketSend(PacketSendEvent event) {
if (event.isCancelled()) return;
if (event.isCancelled() && !measuredPacketTypes.contains(event.getPacketType())) return;

if (event.getPacketType() == PacketType.Play.Server.OPEN_WINDOW) {
PlayerData data = playerDataCache.get(event.getUser().getUUID(), PlayerData::new);
PlayerData data = playerDataCache.get(event.getUser().getUUID(), PlayerData::new);

if (data.screenNextAllowedOpen.get() > System.currentTimeMillis()) {
event.setCancelled(true);
if (log) info("Player '" + event.getUser().getName() + "' could not open screen because they are on cooldown.");
return;
}
if (data.cooldownResumeTime.get() > System.currentTimeMillis()) {
event.setCancelled(true);
return;
}

long servedBytes = data.servedSetSlotBytes.addAndGet(ByteBufHelper.readableBytes(event.getByteBuf()));
if (log) info("Player '" + event.getUser().getName() + "' requested " + servedBytes + " bytes in ItemStacks.");

if (servedBytes <= lockoutBytes) {
return;
}

data.cooldownResumeTime.set(System.currentTimeMillis() + lockoutMillis);

long servedBytes = data.servedSetSlotBytes.get();
if (log) warn("Player '" + event.getUser().getName() + "' is now on LOCKOUT as they exceeded" +
" the set limit (now " + servedBytes + " bytes).");

if (
servedBytes > rateLimitBytes && servedBytes < lockoutBytes
&& data.screenOpenCount.getAndIncrement() > screenOpenLimit
) {
data.screenNextAllowedOpen.set(System.currentTimeMillis() + screenOpenDelay);
if (closeInventory && event.getPlayer() != null) {
Player player = (Player) event.getPlayer();
player.getScheduler().execute(plugin, player::closeInventory, null, 1L);
}
}

if (log) info("Player '" + event.getUser().getName() + "' is now on RATELIMIT as they exceeded" +
" the set limit (now " + servedBytes + " bytes).");
return;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onInventoryOpen(InventoryOpenEvent event) {
PlayerData data = playerDataCache.get(event.getPlayer().getUniqueId(), PlayerData::new);

if (data.cooldownResumeTime.get() > System.currentTimeMillis()) {
event.setCancelled(true);
if (log) info("Player '" + event.getPlayer().getName() + "' could not open screen because they are on cooldown.");
return;
}

if (event.getPacketType() == PacketType.Play.Server.SET_SLOT) {
PlayerData data = playerDataCache.get(event.getUser().getUUID(), PlayerData::new);
long servedBytes = data.servedSetSlotBytes.get();

if (
servedBytes > rateLimitBytes && servedBytes < lockoutBytes
&& data.screenOpenCount.getAndIncrement() > screenOpenLimit
) {
event.setCancelled(true);
data.cooldownResumeTime.set(System.currentTimeMillis() + screenOpenDelay);
data.screenOpenCount.set(0);
if (log) warn("Player '" + event.getPlayer().getName() + "' is now on RATE-LIMIT as they exceeded" +
" the set limit (now " + servedBytes + " bytes).");
}
}

if (data.screenNextAllowedOpen.get() > System.currentTimeMillis()) {
event.setCancelled(true);
return;
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onPlayerInteract(PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return;
if (!MaterialUtil.INVENTORY_HOLDER_ITEMS.contains(event.getClickedBlock().getType())) return;

long servedBytes = data.servedSetSlotBytes.addAndGet(ByteBufHelper.readableBytes(event.getByteBuf()));
PlayerData data = playerDataCache.get(event.getPlayer().getUniqueId(), PlayerData::new);

if (servedBytes <= lockoutBytes) {
return;
}
if (data.cooldownResumeTime.get() > System.currentTimeMillis()) {
event.setCancelled(true);
if (log) info("Player '" + event.getPlayer().getName() + "' could not open screen because they are on cooldown.");
return;
}

data.screenNextAllowedOpen.set(System.currentTimeMillis() + lockoutMillis);
long servedBytes = data.servedSetSlotBytes.get();

if (log) info("Player '" + event.getUser().getName() + "' is now on LOCKOUT as they exceeded" +
if (
servedBytes > rateLimitBytes && servedBytes < lockoutBytes
&& data.screenOpenCount.getAndIncrement() > screenOpenLimit
) {
event.setCancelled(true);
data.cooldownResumeTime.set(System.currentTimeMillis() + screenOpenDelay);
data.screenOpenCount.set(0);
if (log) warn("Player '" + event.getPlayer().getName() + "' is now on RATE-LIMIT as they exceeded" +
" the set limit (now " + servedBytes + " bytes).");

if (closeInventory && event.getPlayer() != null) {
Player player = (Player) event.getPlayer();
player.getScheduler().execute(plugin, player::closeInventory, null, 1L);
}
}
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
private void onInventoryOpen(InventoryOpenEvent event) {
private void onPlayerInteract(PlayerInteractEntityEvent event) {
if (!EntityUtil.isInventoryHolder(event.getRightClicked())) return;

PlayerData data = playerDataCache.get(event.getPlayer().getUniqueId(), PlayerData::new);

if (data.screenNextAllowedOpen.get() > System.currentTimeMillis()) {
if (data.cooldownResumeTime.get() > System.currentTimeMillis()) {
event.setCancelled(true);
if (log) info("Player '" + event.getPlayer().getName() + "' could not open screen because they are on cooldown.");
return;
}

Expand All @@ -149,23 +202,24 @@ private void onInventoryOpen(InventoryOpenEvent event) {
servedBytes > rateLimitBytes && servedBytes < lockoutBytes
&& data.screenOpenCount.getAndIncrement() > screenOpenLimit
) {
data.screenNextAllowedOpen.set(System.currentTimeMillis() + screenOpenDelay);

if (log) info("Player '" + event.getPlayer().getName() + "' is now on RATELIMIT as they exceeded" +
event.setCancelled(true);
data.cooldownResumeTime.set(System.currentTimeMillis() + screenOpenDelay);
data.screenOpenCount.set(0);
if (log) warn("Player '" + event.getPlayer().getName() + "' is now on RATE-LIMIT as they exceeded" +
" the set limit (now " + servedBytes + " bytes).");
}
}

private static class PlayerData {

public final UUID uuid;
public final AtomicLong servedSetSlotBytes, screenNextAllowedOpen;
public final AtomicLong servedSetSlotBytes, cooldownResumeTime;
public final AtomicInteger screenOpenCount;

public PlayerData(UUID uuid) {
this.uuid = uuid;
this.servedSetSlotBytes = new AtomicLong(0L);
this.screenNextAllowedOpen = new AtomicLong(0L);
this.cooldownResumeTime = new AtomicLong(0L);
this.screenOpenCount = new AtomicInteger(0);
}
}
Expand Down
Loading

0 comments on commit 6b8f4bf

Please sign in to comment.