Add SteamWar adaption for TinyProtocol

This commit is contained in:
2026-05-16 11:52:06 +02:00
parent 604657a084
commit b216438a47
2 changed files with 48 additions and 43 deletions
@@ -24,6 +24,7 @@ import de.steamwar.Reflection;
import de.steamwar.core.Core;
import io.netty.channel.*;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.login.ServerboundHelloPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConnectionListener;
@@ -39,6 +40,8 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import java.util.logging.Level;
/**
@@ -57,7 +60,7 @@ public class TinyProtocol {
private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().<Channel, Boolean>makeMap());
// List of network markers
private List<Connection> networkManagers;
public List<Connection> networkManagers;
// Injected channel handlers
private List<Channel> serverChannels = new ArrayList<>();
@@ -72,6 +75,11 @@ public class TinyProtocol {
protected Plugin plugin;
public static final TinyProtocol instance = new TinyProtocol(Core.getInstance());
private final Map<Class<?>, List<BiFunction<Player, Object, Object>>> packetFilters = new HashMap<>();
public static void init() {
// enforce init
}
/**
* Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients.
@@ -235,79 +243,63 @@ public class TinyProtocol {
}
}
/**
* Invoked when the server is starting to send a packet to a player.
* <p>
* Note that this is not executed on the main thread.
*
* @param receiver - the receiving player, NULL for early login/status packets.
* @param channel - the channel that received the packet. Never NULL.
* @param packet - the packet being sent.
* @return The packet to send instead, or NULL to cancel the transmission.
*/
public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) {
return packet;
public <T> void addTypedFilter(Class<T> packetType, BiFunction<Player, T, Object> filter) {
packetFilters.computeIfAbsent(packetType, c -> new CopyOnWriteArrayList<>()).add((BiFunction) filter);
}
/**
* Invoked when the server has received a packet from a given player.
* <p>
* Use {@link Channel#remoteAddress()} to get the remote address of the client.
*
* @param sender - the player that sent the packet, NULL for early login/status packets.
* @param channel - channel that received the packet. Never NULL.
* @param packet - the packet being received.
* @return The packet to recieve instead, or NULL to cancel.
*/
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
return packet;
@Deprecated
public void addFilter(Class<?> packetType, BiFunction<Player, Object, Object> filter) {
packetFilters.computeIfAbsent(packetType, c -> new CopyOnWriteArrayList<>()).add(filter);
}
public void removeFilter(Class<?> packetType, BiFunction<Player, Object, Object> filter) {
packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter);
}
/**
* Send a packet to a particular player.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the destination player.
* @param packet - the packet to send.
*/
public void sendPacket(Player player, Object packet) {
public void sendPacket(Player player, Packet<?> packet) {
sendPacket(getChannel(player), packet);
}
@Deprecated
public void sendPacket(Player player, Object object) {
if (object instanceof Packet<?> packet) {
sendPacket(getChannel(player), packet);
}
}
/**
* Send a packet to a particular client.
* <p>
* Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet to send.
*/
public void sendPacket(Channel channel, Object packet) {
public void sendPacket(Channel channel, Packet<?> packet) {
channel.pipeline().writeAndFlush(packet);
}
/**
* Pretend that a given packet has been received from a player.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param player - the player that sent the packet.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Player player, Object packet) {
public void receivePacket(Player player, Packet<?> packet) {
receivePacket(getChannel(player), packet);
}
/**
* Pretend that a given packet has been received from a given client.
* <p>
* Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
*
* @param channel - client identified by a channel.
* @param packet - the packet that will be received by the server.
*/
public void receivePacket(Channel channel, Object packet) {
public void receivePacket(Channel channel, Packet<?> packet) {
channel.pipeline().context("encoder").fireChannelRead(packet);
}
@@ -461,7 +453,7 @@ public class TinyProtocol {
handleLoginStart(channel, msg);
try {
msg = onPacketInAsync(player, channel, msg);
msg = filterPacket(player, msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e);
}
@@ -474,7 +466,7 @@ public class TinyProtocol {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
try {
msg = onPacketOutAsync(player, ctx.channel(), msg);
msg = filterPacket(player, msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
}
@@ -489,5 +481,16 @@ public class TinyProtocol {
channelLookup.put(name, channel);
}
}
private Object filterPacket(Player player, Object packet) {
List<BiFunction<Player, Object, Object>> filters = packetFilters.getOrDefault(packet.getClass(), Collections.emptyList());
for(BiFunction<Player, Object, Object> filter : filters) {
packet = filter.apply(player, packet);
if(packet == null) break;
}
return packet;
}
}
}
@@ -23,6 +23,8 @@ import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import de.steamwar.sql.internal.Statement;
import io.netty.channel.ChannelFuture;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerConnectionListener;
import org.bukkit.Bukkit;
import org.eclipse.openj9.criu.CRIUSupport;
import org.eclipse.openj9.criu.JVMCRIUException;
@@ -56,7 +58,7 @@ class CheckpointUtilsJ9 {
Bukkit.getOnlinePlayers().forEach(player -> player.kickPlayer(null));
List<?> networkManagers = TinyProtocol.networkManagers.get(TinyProtocol.getServerConnection(Core.getInstance()));
List<?> networkManagers = TinyProtocol.instance.networkManagers;
if(!Bukkit.getOnlinePlayers().isEmpty() || !networkManagers.isEmpty()) {
Core.getInstance().getLogger().log(Level.INFO, "Waiting for players to disconnect for checkpointing");
Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1);
@@ -90,8 +92,8 @@ class CheckpointUtilsJ9 {
}
private static final Reflection.Field<List> channelFutures = Reflection.getField(TinyProtocol.serverConnection, List.class, 0, ChannelFuture.class);
private static final Reflection.Method bind = Reflection.getMethod(TinyProtocol.serverConnection, null, InetAddress.class, int.class);
private static final Reflection.Field<List> channelFutures = Reflection.getField(ServerConnectionListener.class, List.class, 0, ChannelFuture.class);
private static final Reflection.Method bind = Reflection.getMethod(ServerConnectionListener.class, null, InetAddress.class, int.class);
private static void freezeInternal(Path path) throws Exception {
Bukkit.getPluginManager().callEvent(new CRIUSleepEvent());
@@ -99,7 +101,7 @@ class CheckpointUtilsJ9 {
Statement.closeAll();
// Close socket
Object serverConnection = TinyProtocol.getServerConnection(Core.getInstance());
ServerConnectionListener serverConnection = MinecraftServer.getServer().getConnection();
List<?> channels = channelFutures.get(serverConnection);
for(Object future : channels) {
((ChannelFuture) future).channel().close().syncUninterruptibly();