diff --git a/Examples/TinyProtocol/.classpath b/Examples/TinyProtocol/.classpath new file mode 100644 index 00000000..658fc2e3 --- /dev/null +++ b/Examples/TinyProtocol/.classpath @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/TinyProtocol/.gitignore b/Examples/TinyProtocol/.gitignore new file mode 100644 index 00000000..ac3f702e --- /dev/null +++ b/Examples/TinyProtocol/.gitignore @@ -0,0 +1,2 @@ +target/ +class/ \ No newline at end of file diff --git a/Examples/TinyProtocol/.project b/Examples/TinyProtocol/.project new file mode 100644 index 00000000..c8c14dfc --- /dev/null +++ b/Examples/TinyProtocol/.project @@ -0,0 +1,23 @@ + + + TinyProtocol + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/Examples/TinyProtocol/pom.xml b/Examples/TinyProtocol/pom.xml new file mode 100644 index 00000000..d57c7fe7 --- /dev/null +++ b/Examples/TinyProtocol/pom.xml @@ -0,0 +1,49 @@ + + 4.0.0 + com.comphenix.tinyprotocol + TinyProtocol + 0.0.1-SNAPSHOT + TinyProtocol + Intercept packets without ProtocolLib. + + + + bukkit-rep + http://repo.bukkit.org/content/groups/public + + + comphenix-rep + Comphenix Maven Releases + http://repo.comphenix.net/content/groups/public + + + + + src/main/java + + + src/main/resources + + + + + + maven-compiler-plugin + 3.0 + + 1.6 + 1.6 + + + + + + + + org.bukkit + craftbukkit + 1.7.2-R0.1-SNAPSHOT + provided + + + \ No newline at end of file diff --git a/Examples/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/ExamplePlugin.java b/Examples/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/ExamplePlugin.java new file mode 100644 index 00000000..c9550908 --- /dev/null +++ b/Examples/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/ExamplePlugin.java @@ -0,0 +1,28 @@ +package com.comphenix.tinyprotocol; + +import net.minecraft.server.v1_7_R1.PacketPlayInChat; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import com.google.common.base.Function; + +public class ExamplePlugin extends JavaPlugin { + private Function CHAT_MESSAGE = TinyProtocol.getFieldAccessor( + PacketPlayInChat.class, String.class, 0); + + @Override + public void onEnable() { + new TinyProtocol(this) { + @Override + public Object onPacketInAsync(Player sender, Object packet) { + // Cancel chat packets + if (packet instanceof PacketPlayInChat) { + if (CHAT_MESSAGE.apply(packet).contains("dirty")) + return null; + } + return super.onPacketInAsync(sender, packet); + } + }; + } +} diff --git a/Examples/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java b/Examples/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java new file mode 100644 index 00000000..7f64636d --- /dev/null +++ b/Examples/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java @@ -0,0 +1,151 @@ +package com.comphenix.tinyprotocol; + +import java.lang.reflect.Field; +import javax.annotation.Nullable; + +import net.minecraft.server.v1_7_R1.NetworkManager; +import net.minecraft.util.io.netty.channel.Channel; +import net.minecraft.util.io.netty.channel.ChannelDuplexHandler; +import net.minecraft.util.io.netty.channel.ChannelHandlerContext; +import net.minecraft.util.io.netty.channel.ChannelPromise; + +import org.bukkit.craftbukkit.v1_7_R1.entity.CraftPlayer; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.plugin.Plugin; + +import com.google.common.base.Function; + +public abstract class TinyProtocol implements Listener { + private static Function CHANNEL_ACCESSOR = getFieldAccessor(NetworkManager.class, Channel.class, 0); + + private boolean closed; + private Plugin plugin; + + public TinyProtocol(Plugin plugin) { + this.plugin = plugin; + this.plugin.getServer().getPluginManager().registerEvents(this, plugin); + + // Prepare existing players + for (Player player : plugin.getServer().getOnlinePlayers()) { + injectPlayer(player); + } + } + + @EventHandler + public final void onPlayerJoin(PlayerJoinEvent e) { + if (closed) + return; + injectPlayer(e.getPlayer()); + } + + private void injectPlayer(final Player player) { + // Inject our packet interceptor + getChannel(player).pipeline().addBefore("packet_handler", getHandlerName(), new ChannelDuplexHandler() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Object result = onPacketInAsync(player, msg); + + if (result != null) { + super.channelRead(ctx, result); + } + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + Object result = onPacketOutAsync(player, msg); + + if (result != null) { + super.write(ctx, result, promise); + } + } + }); + } + + private String getHandlerName() { + return "tiny-" + plugin.getName(); + } + + @EventHandler + public final void onPluginDisable(PluginDisableEvent e) { + if (e.getPlugin().equals(plugin)) { + close(); + } + } + + private Channel getChannel(Player player) { + NetworkManager manager = ((CraftPlayer) player.getPlayer()).getHandle().playerConnection.networkManager; + return CHANNEL_ACCESSOR.apply(manager); + } + + /** + * Invoked when the server is starting to send a packet to a player. + * + * Note that this is not executed on the main thread. + * @param reciever - the receiving player. + * @param packet - the packet being sent. + * @return The packet to send instead, or NULL to cancel the transmission. + */ + public Object onPacketOutAsync(Player reciever, Object packet) { + return packet; + } + + /** + * Invoked when the server has received a packet from a given player. + * @param sender - the player that sent the packet. + * @param packet - the packet being sent. + * @return The packet to recieve instead, or NULL to cancel. + */ + public Object onPacketInAsync(Player sender, Object packet) { + return packet; + } + + /** + * Retrieve a field accessor for a specific field type and index. + * @param target - the target type. + * @param fieldType - the field type. + * @param index - the index. + * @return The field accessor. + */ + public static Function getFieldAccessor(Class> target, Class fieldType, int index) { + for (Field field : target.getDeclaredFields()) { + if (fieldType.isAssignableFrom(field.getType()) && index-- <= 0) { + final Field targetField = field; + field.setAccessible(true); + + // A function for retrieving a specific field value + return new Function() { + @SuppressWarnings("unchecked") + @Override + public T apply(@Nullable Object instance) { + try { + return (T) targetField.get(instance); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access reflection.", e); + } + } + }; + } + } + + // Search in parent classes + if (target.getSuperclass() != null) + return getFieldAccessor(target.getSuperclass(), fieldType, index); + throw new IllegalArgumentException("Cannot find field with type " + fieldType); + } + + public final void close() { + if (!closed) { + closed = true; + + // Remove our handlers + for (Player player : plugin.getServer().getOnlinePlayers()) { + getChannel(player).pipeline().remove(getHandlerName()); + } + } + } +} diff --git a/Examples/TinyProtocol/src/main/resources/plugin.yml b/Examples/TinyProtocol/src/main/resources/plugin.yml new file mode 100644 index 00000000..a7cc3e22 --- /dev/null +++ b/Examples/TinyProtocol/src/main/resources/plugin.yml @@ -0,0 +1,9 @@ +name: TinyProtocol +version: 0.0.1-SNAPSHOT +description: Intercept packets without ProtocolLib +author: Comphenix + +load: startup + +main: com.comphenix.tinyprotocol.ExamplePlugin +database: false \ No newline at end of file
+ * Note that this is not executed on the main thread. + * @param reciever - the receiving player. + * @param packet - the packet being sent. + * @return The packet to send instead, or NULL to cancel the transmission. + */ + public Object onPacketOutAsync(Player reciever, Object packet) { + return packet; + } + + /** + * Invoked when the server has received a packet from a given player. + * @param sender - the player that sent the packet. + * @param packet - the packet being sent. + * @return The packet to recieve instead, or NULL to cancel. + */ + public Object onPacketInAsync(Player sender, Object packet) { + return packet; + } + + /** + * Retrieve a field accessor for a specific field type and index. + * @param target - the target type. + * @param fieldType - the field type. + * @param index - the index. + * @return The field accessor. + */ + public static Function getFieldAccessor(Class> target, Class fieldType, int index) { + for (Field field : target.getDeclaredFields()) { + if (fieldType.isAssignableFrom(field.getType()) && index-- <= 0) { + final Field targetField = field; + field.setAccessible(true); + + // A function for retrieving a specific field value + return new Function() { + @SuppressWarnings("unchecked") + @Override + public T apply(@Nullable Object instance) { + try { + return (T) targetField.get(instance); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access reflection.", e); + } + } + }; + } + } + + // Search in parent classes + if (target.getSuperclass() != null) + return getFieldAccessor(target.getSuperclass(), fieldType, index); + throw new IllegalArgumentException("Cannot find field with type " + fieldType); + } + + public final void close() { + if (!closed) { + closed = true; + + // Remove our handlers + for (Player player : plugin.getServer().getOnlinePlayers()) { + getChannel(player).pipeline().remove(getHandlerName()); + } + } + } +} diff --git a/Examples/TinyProtocol/src/main/resources/plugin.yml b/Examples/TinyProtocol/src/main/resources/plugin.yml new file mode 100644 index 00000000..a7cc3e22 --- /dev/null +++ b/Examples/TinyProtocol/src/main/resources/plugin.yml @@ -0,0 +1,9 @@ +name: TinyProtocol +version: 0.0.1-SNAPSHOT +description: Intercept packets without ProtocolLib +author: Comphenix + +load: startup + +main: com.comphenix.tinyprotocol.ExamplePlugin +database: false \ No newline at end of file