Skip to content

Commit

Permalink
feat: add ProtocolLib support for deeper-level packet cancellation (#274
Browse files Browse the repository at this point in the history
)

* feat: add support for ProtocolLib packet-level state cancelling

* refactor: move commands to event listener, document ProtocolLib support

* docs: make Setup less claustrophobic

* fix: remove `@Getter` on `PlayerPacketAdapter`

* build: add missing license headers

* fix: inaccessible method on Paper

* test: add ProtocolLib to network spin test

* fix: whoops I targeted the wrong packets

* fix: bad command disabled check logic

* fix: final protocollib adjustments
  • Loading branch information
WiIIiam278 authored Apr 10, 2024
1 parent 4dfbc0e commit 2f5ddf6
Show file tree
Hide file tree
Showing 14 changed files with 308 additions and 120 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ allprojects {
mavenLocal()
mavenCentral()
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
maven { url "https://repo.dmulloy2.net/repository/public/" }
maven { url 'https://repo.codemc.io/repository/maven-public/' }
maven { url 'https://repo.minebench.de/' }
maven { url 'https://repo.alessiodp.com/releases/' }
Expand Down
1 change: 1 addition & 0 deletions bukkit/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies {
implementation 'de.tr7zw:item-nbt-api:2.12.3'

compileOnly 'org.spigotmc:spigot-api:1.17.1-R0.1-SNAPSHOT'
compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0'
compileOnly 'org.projectlombok:lombok:1.18.32'
compileOnly 'commons-io:commons-io:2.16.0'
compileOnly 'org.json:json:20240303'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,37 @@
import net.william278.husksync.data.BukkitData;
import net.william278.husksync.user.BukkitUser;
import net.william278.husksync.user.OnlineUser;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.Cancellable;
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.EntityDamageEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.PrepareItemCraftEvent;
import org.bukkit.event.player.*;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.server.MapInitializeEvent;
import org.bukkit.event.world.WorldSaveEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.stream.Collectors;

public class BukkitEventListener extends EventListener implements BukkitJoinEventListener, BukkitQuitEventListener,
BukkitDeathEventListener, Listener {
protected final List<String> blacklistedCommands;

public BukkitEventListener(@NotNull BukkitHuskSync huskSync) {
super(huskSync);
this.blacklistedCommands = huskSync.getSettings().getSynchronization().getBlacklistedCommandsWhileLocked();
Bukkit.getServer().getPluginManager().registerEvents(this, huskSync);
protected final LockedHandler lockedHandler;

public BukkitEventListener(@NotNull BukkitHuskSync plugin) {
super(plugin);
plugin.getServer().getPluginManager().registerEvents(this, plugin);
this.lockedHandler = createLockedHandler(plugin);
}

@NotNull
private LockedHandler createLockedHandler(@NotNull BukkitHuskSync plugin) {
if (getPlugin().isDependencyLoaded("ProtocolLib") && getPlugin().getSettings().isCancelPackets()) {
return new BukkitLockedPacketListener(plugin);
} else {
return new BukkitLockedEventListener(plugin);
}
}

@Override
Expand Down Expand Up @@ -88,7 +84,7 @@ public void handlePlayerDeath(@NotNull PlayerDeathEvent event) {
final OnlineUser user = BukkitUser.adapt(event.getEntity(), plugin);

// If the player is locked or the plugin disabling, clear their drops
if (cancelPlayerEvent(user.getUuid())) {
if (lockedHandler.cancelPlayerEvent(user.getUuid())) {
event.getDrops().clear();
return;
}
Expand Down Expand Up @@ -125,91 +121,13 @@ public void onMapInitialize(@NotNull MapInitializeEvent event) {
}
}


/*
* Events to cancel if the player has not been set yet
*/

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onProjectileLaunch(@NotNull ProjectileLaunchEvent event) {
final Projectile projectile = event.getEntity();
if (projectile.getShooter() instanceof Player player) {
cancelPlayerEvent(player.getUniqueId(), event);
}
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onDropItem(@NotNull PlayerDropItemEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPickupItem(@NotNull EntityPickupItemEvent event) {
if (event.getEntity() instanceof Player player) {
cancelPlayerEvent(player.getUniqueId(), event);
}
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerInteractEntity(@NotNull PlayerInteractEntityEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerInteractArmorStand(@NotNull PlayerArmorStandManipulateEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockPlace(@NotNull BlockPlaceEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockBreak(@NotNull BlockBreakEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInventoryOpen(@NotNull InventoryOpenEvent event) {
if (event.getPlayer() instanceof Player player) {
cancelPlayerEvent(player.getUniqueId(), event);
}
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInventoryClick(@NotNull InventoryClickEvent event) {
cancelPlayerEvent(event.getWhoClicked().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onCraftItem(@NotNull PrepareItemCraftEvent event) {
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
if (event.getEntity() instanceof Player player) {
cancelPlayerEvent(player.getUniqueId(), event);
}
}

// We handle commands here to allow specific command handling on ProtocolLib servers
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPermissionCommand(@NotNull PlayerCommandPreprocessEvent event) {
final String[] commandArgs = event.getMessage().substring(1).split(" ");
final String commandLabel = commandArgs[0].toLowerCase(Locale.ENGLISH);

if (blacklistedCommands.contains("*") || blacklistedCommands.contains(commandLabel)) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
public void onCommandProcessed(@NotNull PlayerCommandPreprocessEvent event) {
if (!lockedHandler.isCommandDisabled(event.getMessage().substring(1).split(" ")[0])) {
return;
}
}

private void cancelPlayerEvent(@NotNull UUID uuid, @NotNull Cancellable event) {
if (cancelPlayerEvent(uuid)) {
if (lockedHandler.cancelPlayerEvent(event.getPlayer().getUniqueId())) {
event.setCancelled(true);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* This file is part of HuskSync, licensed under the Apache License 2.0.
*
* Copyright (c) William278 <[email protected]>
* Copyright (c) contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.william278.husksync.listener;

import lombok.Getter;
import net.william278.husksync.BukkitHuskSync;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.Cancellable;
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.EntityDamageEvent;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.entity.ProjectileLaunchEvent;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.jetbrains.annotations.NotNull;

import java.util.UUID;

@Getter
public class BukkitLockedEventListener implements LockedHandler, Listener {

protected final BukkitHuskSync plugin;

protected BukkitLockedEventListener(@NotNull BukkitHuskSync plugin) {
this.plugin = plugin;
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onProjectileLaunch(@NotNull ProjectileLaunchEvent event) {
final Projectile projectile = event.getEntity();
if (projectile.getShooter() instanceof Player player) {
cancelPlayerEvent(player.getUniqueId(), event);
}
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onDropItem(@NotNull PlayerDropItemEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPickupItem(@NotNull EntityPickupItemEvent event) {
if (event.getEntity() instanceof Player player) {
cancelPlayerEvent(player.getUniqueId(), event);
}
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerInteractEntity(@NotNull PlayerInteractEntityEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerInteractArmorStand(@NotNull PlayerArmorStandManipulateEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockPlace(@NotNull BlockPlaceEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBlockBreak(@NotNull BlockBreakEvent event) {
cancelPlayerEvent(event.getPlayer().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInventoryOpen(@NotNull InventoryOpenEvent event) {
if (event.getPlayer() instanceof Player player) {
cancelPlayerEvent(player.getUniqueId(), event);
}
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onInventoryClick(@NotNull InventoryClickEvent event) {
cancelPlayerEvent(event.getWhoClicked().getUniqueId(), event);
}

@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
if (event.getEntity() instanceof Player player) {
cancelPlayerEvent(player.getUniqueId(), event);
}
}

private void cancelPlayerEvent(@NotNull UUID uuid, @NotNull Cancellable event) {
if (cancelPlayerEvent(uuid)) {
event.setCancelled(true);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* This file is part of HuskSync, licensed under the Apache License 2.0.
*
* Copyright (c) William278 <[email protected]>
* Copyright (c) contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.william278.husksync.listener;

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.google.common.collect.Sets;
import net.william278.husksync.BukkitHuskSync;
import org.jetbrains.annotations.NotNull;

import java.util.Set;
import java.util.logging.Level;

import static com.comphenix.protocol.PacketType.Play.Client;

public class BukkitLockedPacketListener extends BukkitLockedEventListener implements LockedHandler {

protected BukkitLockedPacketListener(@NotNull BukkitHuskSync plugin) {
super(plugin);
ProtocolLibrary.getProtocolManager().addPacketListener(new PlayerPacketAdapter(this));
plugin.log(Level.INFO, "Using ProtocolLib to cancel packets for locked players");
}

private static class PlayerPacketAdapter extends PacketAdapter {

// Packets we want the player to still be able to send/receiver to/from the server
private static final Set<PacketType> ALLOWED_PACKETS = Set.of(
Client.KEEP_ALIVE, Client.PONG, // Connection packets
Client.CHAT_COMMAND, Client.CHAT, Client.CHAT_SESSION_UPDATE, // Chat / command packets
Client.POSITION, Client.POSITION_LOOK, Client.LOOK
);

private final BukkitLockedPacketListener listener;

public PlayerPacketAdapter(@NotNull BukkitLockedPacketListener listener) {
super(listener.getPlugin(), ListenerPriority.HIGHEST, getPacketsToListenFor());
this.listener = listener;
}

@Override
public void onPacketReceiving(@NotNull PacketEvent event) {
if (listener.cancelPlayerEvent(event.getPlayer().getUniqueId()) && !event.isReadOnly()) {
event.setCancelled(true);
}
}

@Override
public void onPacketSending(PacketEvent event) {
if (listener.cancelPlayerEvent(event.getPlayer().getUniqueId()) && !event.isReadOnly()) {
event.setCancelled(true);
}
}

// Returns the set of ALL Server packets, excluding the set of allowed packets
@NotNull
private static Set<PacketType> getPacketsToListenFor() {
return Sets.difference(Client.getInstance().values(), ALLOWED_PACKETS);
}

}

}
1 change: 1 addition & 0 deletions bukkit/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ author: 'William278'
description: '${description}'
website: 'https://william278.net'
softdepend:
- 'ProtocolLib'
- 'MysqlPlayerDataBridge'
- 'Plan'
libraries:
Expand Down
Loading

0 comments on commit 2f5ddf6

Please sign in to comment.