From 4503f15d6c7cfaea9f51f9e079b2133cdddeb43d Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sun, 17 Nov 2024 09:48:38 +0100 Subject: [PATCH] wip --- pom.xml | 10 +++ reflections.txt | 8 ++ .../java/eu/m724/tweaks/TweaksPlugin.java | 7 ++ .../eu/m724/tweaks/ping/F3NameListener.java | 90 +++++++++++++++++++ .../java/eu/m724/tweaks/ping/MsptChecker.java | 19 ++++ .../java/eu/m724/tweaks/ping/PingChecker.java | 63 +++++++++++++ .../eu/m724/tweaks/ping/PingCommands.java | 24 +++++ .../java/eu/m724/tweaks/ping/PlayerPing.java | 30 +++++++ src/main/resources/plugin.yml | 3 + 9 files changed, 254 insertions(+) create mode 100644 reflections.txt create mode 100644 src/main/java/eu/m724/tweaks/ping/F3NameListener.java create mode 100644 src/main/java/eu/m724/tweaks/ping/MsptChecker.java create mode 100644 src/main/java/eu/m724/tweaks/ping/PingChecker.java create mode 100644 src/main/java/eu/m724/tweaks/ping/PingCommands.java create mode 100644 src/main/java/eu/m724/tweaks/ping/PlayerPing.java diff --git a/pom.xml b/pom.xml index 88ad922..76e29b6 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,10 @@ spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + @@ -43,5 +47,11 @@ 24.1.0 compile + + com.comphenix.protocol + ProtocolLib + 5.3.0 + provided + \ No newline at end of file diff --git a/reflections.txt b/reflections.txt new file mode 100644 index 0000000..0d65a28 --- /dev/null +++ b/reflections.txt @@ -0,0 +1,8 @@ +usage of: + +reflections: +- ping/F3NameListener net.minecraft.network.protocol.common.custom.BrandPayload + +protocollib: +- ping/F3NameListener sending and intercepting CUSTOM_PAYLOADs to modify brand +- ping/PingChecker sending and intercepting COOKIE_RESPONSEs to measure ping diff --git a/src/main/java/eu/m724/tweaks/TweaksPlugin.java b/src/main/java/eu/m724/tweaks/TweaksPlugin.java index 312199f..588924f 100644 --- a/src/main/java/eu/m724/tweaks/TweaksPlugin.java +++ b/src/main/java/eu/m724/tweaks/TweaksPlugin.java @@ -3,6 +3,9 @@ package eu.m724.tweaks; import eu.m724.tweaks.chat.ChatCommands; import eu.m724.tweaks.chat.ChatManager; import eu.m724.tweaks.door.DoorListener; +import eu.m724.tweaks.ping.F3NameListener; +import eu.m724.tweaks.ping.PingChecker; +import eu.m724.tweaks.ping.PingCommands; import org.bukkit.plugin.java.JavaPlugin; import java.util.Objects; @@ -18,5 +21,9 @@ public class TweaksPlugin extends JavaPlugin { Objects.requireNonNull(getCommand("chatmanage")).setExecutor(chatCommands); getServer().getPluginManager().registerEvents(new DoorListener(), this); + + new F3NameListener(this).init(); + new PingChecker(this).init(); + Objects.requireNonNull(getCommand("ping")).setExecutor(new PingCommands()); } } diff --git a/src/main/java/eu/m724/tweaks/ping/F3NameListener.java b/src/main/java/eu/m724/tweaks/ping/F3NameListener.java new file mode 100644 index 0000000..ca16aa4 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/ping/F3NameListener.java @@ -0,0 +1,90 @@ +package eu.m724.tweaks.ping; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.*; +import com.comphenix.protocol.reflect.StructureModifier; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class F3NameListener { + private final Plugin plugin; + private final Class brandPayloadClass; + private final Constructor brandPayloadConstructor; + + public F3NameListener(Plugin plugin) { + this.plugin = plugin; + try { + this.brandPayloadClass = Class.forName("net.minecraft.network.protocol.common.custom.BrandPayload"); + this.brandPayloadConstructor = brandPayloadClass.getConstructor(String.class); + } catch (ClassNotFoundException | NoSuchMethodException e) { + throw new BrandPayloadReflectionException(e); + } + } + + public void init() { + new BukkitRunnable() { + @Override + public void run() { + double mspt = PlayerPing.getMillisPerTick(); + boolean showMspt = mspt > 50.05; // mspt is at least like 50.01 because of some measuring overheads (I think) + for (Player player : plugin.getServer().getOnlinePlayers()) { + double ping = PlayerPing.getPingMillis(player); + String brand; + if (ping == 0) { + brand = "wait"; + } else { + if (showMspt) { + brand = "%.2f mspt | %.2f ms".formatted(mspt, PlayerPing.getPingMillis(player)); + } else { + brand = "%.2f ms".formatted(PlayerPing.getPingMillis(player)); + } + } + changeBrand(player, brand); + } + } + }.runTaskTimerAsynchronously(plugin, 0, 200); // 10 sec + + // this is just to make the server not send a brand on login, it doesn't ever run after login + ProtocolLibrary.getProtocolManager().addPacketListener(new PacketAdapter( + plugin, + ListenerPriority.NORMAL, + PacketType.Configuration.Server.CUSTOM_PAYLOAD + ) { + @Override + public void onPacketSending(PacketEvent event) { + try { + PacketContainer packet = event.getPacket(); + Object brandPayload = brandPayloadConstructor.newInstance("wait"); + InternalStructure structure = new InternalStructure(brandPayload, new StructureModifier<>(brandPayloadClass)); + packet.getStructures().write(0, structure); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new BrandPayloadReflectionException(e); + } + } + }); + } + + private void changeBrand(Player player, String brand) { + PacketContainer packet = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD); + Object brandPayload; + try { + brandPayload = brandPayloadConstructor.newInstance(brand); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new BrandPayloadReflectionException(e); + } + InternalStructure structure = new InternalStructure(brandPayload, new StructureModifier<>(brandPayloadClass)); + packet.getStructures().write(0, structure); + ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); + } + + public static class BrandPayloadReflectionException extends RuntimeException { + public BrandPayloadReflectionException(Exception e) { + super(e); + } + } +} diff --git a/src/main/java/eu/m724/tweaks/ping/MsptChecker.java b/src/main/java/eu/m724/tweaks/ping/MsptChecker.java new file mode 100644 index 0000000..c9d03f5 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/ping/MsptChecker.java @@ -0,0 +1,19 @@ +package eu.m724.tweaks.ping; + +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; + +public class MsptChecker extends BukkitRunnable { + private long lastLoop = 0; + + @Override + public void run() { + long now = System.nanoTime(); + PlayerPing.nspt = (now - lastLoop) / 100; + lastLoop = now; + } + + public void init(Plugin plugin) { + this.runTaskTimerAsynchronously(plugin, 0, 100); // 5 secs + } +} diff --git a/src/main/java/eu/m724/tweaks/ping/PingChecker.java b/src/main/java/eu/m724/tweaks/ping/PingChecker.java new file mode 100644 index 0000000..5f305e3 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/ping/PingChecker.java @@ -0,0 +1,63 @@ +package eu.m724.tweaks.ping; + +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.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.wrappers.MinecraftKey; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class PingChecker extends BukkitRunnable { + private final Plugin plugin; + + // TODO concurrnet too? + // this is in nanoseconds + private final Map pending = new ConcurrentHashMap<>(); + + public PingChecker(Plugin plugin) { + this.plugin = plugin; + } + + public void init() { + ProtocolLibrary.getProtocolManager().addPacketListener(new PacketAdapter( + plugin, + ListenerPriority.NORMAL, + PacketType.Play.Client.COOKIE_RESPONSE + ) { + @Override + public void onPacketReceiving(PacketEvent event) { + // below line checks, whether the first (and sole) identifier field in the packet is minecraft:chk_ping + if (event.getPacket().getMinecraftKeys().read(0).getKey().equals("chk_ping")) { + Player player = event.getPlayer(); + + long start = pending.remove(player); + PlayerPing.pings.put(player, System.nanoTime() - start); + // gotta cancel because the server will kick + event.setCancelled(true); + } + } + }); + + this.runTaskTimerAsynchronously(plugin, 0, 200); // 10 secs + new MsptChecker().init(plugin); // TODO should this be here + } + + @Override + public void run() { + plugin.getServer().getOnlinePlayers().forEach(player -> { + if (pending.containsKey(player)) return; + pending.put(player, System.nanoTime()); // here or at the bottom? probably doesn't matter + + PacketContainer packet = new PacketContainer(PacketType.Play.Server.COOKIE_REQUEST); + packet.getMinecraftKeys().write(0, new MinecraftKey("chk_ping")); + ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); + }); + } +} diff --git a/src/main/java/eu/m724/tweaks/ping/PingCommands.java b/src/main/java/eu/m724/tweaks/ping/PingCommands.java new file mode 100644 index 0000000..eda7e00 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/ping/PingCommands.java @@ -0,0 +1,24 @@ +package eu.m724.tweaks.ping; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class PingCommands implements CommandExecutor { + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (command.getName().equals("ping")) { + Player player = (Player) sender; + BaseComponent[] component = new ComponentBuilder("Ping: ").color(ChatColor.GOLD) + .append("%.2fms".formatted(PlayerPing.getPingMillis(player))).color(ChatColor.AQUA) + .create(); + player.spigot().sendMessage(component); + } + return true; + } +} diff --git a/src/main/java/eu/m724/tweaks/ping/PlayerPing.java b/src/main/java/eu/m724/tweaks/ping/PlayerPing.java new file mode 100644 index 0000000..bbf18c2 --- /dev/null +++ b/src/main/java/eu/m724/tweaks/ping/PlayerPing.java @@ -0,0 +1,30 @@ +package eu.m724.tweaks.ping; + +import org.bukkit.entity.Player; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class PlayerPing { + // TODO concurrnet? + // in nanos + static final Map pings = new ConcurrentHashMap<>(); + // nanos per tick + static long nspt = 0L; + + public static long getPingNanos(Player player) { + return pings.getOrDefault(player, -1L); + } + + public static double getPingMillis(Player player) { + return getPingNanos(player) / 1000000.0; // a mil ns in ms + } + + public static long getNanosPerTick() { + return nspt; + } + + public static double getMillisPerTick() { + return nspt / 1000000.0; // a mil ns in ms + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 07486d6..f87310d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,6 +3,7 @@ version: ${project.version} main: eu.m724.tweaks.TweaksPlugin api-version: 1.21.1 +depend: [ProtocolLib] commands: chat: @@ -12,4 +13,6 @@ commands: chatmanage: description: Chatroom user management commands aliases: [cm, crm] + ping: + description: Your ping