mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-30 22:53:26 +01:00
Added the ability to intercept serialized input packets on CraftBukkit.
Spigot will be added later.
This commit is contained in:
parent
8f30199a22
commit
6527c0a7f5
@ -97,8 +97,11 @@ public class ListeningWhitelist {
|
||||
this.priority = priority;
|
||||
this.whitelist = Sets.newHashSet(whitelist);
|
||||
this.gamePhase = gamePhase;
|
||||
|
||||
if (options != null) {
|
||||
this.options.addAll(Arrays.asList(options));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this whitelist has any enabled packets.
|
||||
|
@ -130,4 +130,23 @@ public class NetworkMarker {
|
||||
public static boolean hasOutputHandlers(NetworkMarker marker) {
|
||||
return marker != null && !marker.getOutputHandlers().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the byte buffer stored in the given marker.
|
||||
* @param marker - the marker.
|
||||
* @return The byte buffer, or NULL if not found.
|
||||
*/
|
||||
public static byte[] getByteBuffer(NetworkMarker marker) {
|
||||
if (marker != null) {
|
||||
ByteBuffer buffer = marker.getInputBuffer();
|
||||
|
||||
if (buffer != null) {
|
||||
byte[] data = new byte[buffer.remaining()];
|
||||
|
||||
buffer.get(data, 0, data.length);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ import com.comphenix.protocol.injector.GamePhase;
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class PacketAdapter implements PacketListener {
|
||||
|
||||
protected Plugin plugin;
|
||||
protected ConnectionSide connectionSide;
|
||||
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
@ -95,6 +94,17 @@ public abstract class PacketAdapter implements PacketListener {
|
||||
this(plugin, connectionSide, listenerPriority, GamePhase.PLAYING, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param options - which listener options to use.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerOptions[] options, Integer... packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, options, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
@ -117,6 +127,23 @@ public abstract class PacketAdapter implements PacketListener {
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, Integer... packets) {
|
||||
this(plugin, connectionSide, listenerPriority, gamePhase, new ListenerOptions[0], packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener for a single connection side.
|
||||
* <p>
|
||||
* The game phase is used to optmize performance. A listener should only choose BOTH or LOGIN if it's absolutely necessary.
|
||||
* <p>
|
||||
* Listener options must be specified in order for {@link NetworkMarker#getInputBuffer()} to function correctly.
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param connectionSide - the packet type the listener is looking for.
|
||||
* @param listenerPriority - the event priority.
|
||||
* @param gamePhase - which game phase this listener is active under.
|
||||
* @param options - which listener options to use.
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority, GamePhase gamePhase, ListenerOptions[] options, Integer... packets) {
|
||||
if (plugin == null)
|
||||
throw new IllegalArgumentException("plugin cannot be null");
|
||||
if (connectionSide == null)
|
||||
@ -127,12 +154,14 @@ public abstract class PacketAdapter implements PacketListener {
|
||||
throw new IllegalArgumentException("gamePhase cannot be NULL");
|
||||
if (packets == null)
|
||||
throw new IllegalArgumentException("packets cannot be null");
|
||||
if (options == null)
|
||||
throw new IllegalArgumentException("options cannot be null");
|
||||
|
||||
// Add whitelists
|
||||
if (connectionSide.isForServer())
|
||||
sendingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
|
||||
sendingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase, options);
|
||||
if (connectionSide.isForClient())
|
||||
receivingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase);
|
||||
receivingWhitelist = new ListeningWhitelist(listenerPriority, packets, gamePhase, options);
|
||||
|
||||
this.plugin = plugin;
|
||||
this.connectionSide = connectionSide;
|
||||
@ -180,7 +209,6 @@ public abstract class PacketAdapter implements PacketListener {
|
||||
* @return Name of the given plugin.
|
||||
*/
|
||||
public static String getPluginName(Plugin plugin) {
|
||||
|
||||
// Try to get the plugin name
|
||||
try {
|
||||
if (plugin == null)
|
||||
|
@ -52,6 +52,13 @@ public interface ListenerInvoker {
|
||||
*/
|
||||
public InterceptWritePacket getInterceptWritePacket();
|
||||
|
||||
/**
|
||||
* Determine if a given packet requires input buffering.
|
||||
* @param packetId - the packet to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean requireInputBuffer(int packetId);
|
||||
|
||||
/**
|
||||
* Associate a given class with the given packet ID. Internal method.
|
||||
* @param clazz - class to associate.
|
||||
|
@ -46,9 +46,11 @@ import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.async.AsyncMarker;
|
||||
import com.comphenix.protocol.concurrency.IntegerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
@ -138,6 +140,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
// Intercepting write packet methods
|
||||
private InterceptWritePacket interceptWritePacket;
|
||||
|
||||
// Whether or not a packet must be input buffered
|
||||
private volatile IntegerSet inputBufferedPackets = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
|
||||
|
||||
// The two listener containers
|
||||
private SortedPacketListenerList recievedListeners;
|
||||
private SortedPacketListenerList sendingListeners;
|
||||
@ -323,6 +328,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
if (hasSending || hasReceiving) {
|
||||
// Add listeners and hooks
|
||||
if (hasSending) {
|
||||
// This doesn't make any sense
|
||||
if (sending.getOptions().contains(ListenerOptions.INTERCEPT_INPUT_BUFFER)) {
|
||||
throw new IllegalArgumentException("Sending whitelist cannot require input bufferes to be intercepted.");
|
||||
}
|
||||
|
||||
verifyWhitelist(listener, sending);
|
||||
sendingListeners.addListener(listener, sending);
|
||||
enablePacketFilters(listener, ConnectionSide.SERVER_SIDE, sending.getWhitelist());
|
||||
@ -344,9 +354,30 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
|
||||
// Inform our injected hooks
|
||||
packetListeners.add(listener);
|
||||
updateRequireInputBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when we need to update the input buffer set.
|
||||
*/
|
||||
private void updateRequireInputBuffers() {
|
||||
IntegerSet updated = new IntegerSet(Packets.MAXIMUM_PACKET_ID + 1);
|
||||
|
||||
for (PacketListener listener : packetListeners) {
|
||||
ListeningWhitelist whitelist = listener.getReceivingWhitelist();
|
||||
|
||||
// We only check the recieving whitelist
|
||||
if (whitelist.getOptions().contains(ListenerOptions.INTERCEPT_INPUT_BUFFER)) {
|
||||
for (int id : whitelist.getWhitelist()) {
|
||||
updated.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Update it
|
||||
this.inputBufferedPackets = updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked to handle the different game phases of a added listener.
|
||||
* @param phase - listener's game game phase.
|
||||
@ -437,6 +468,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved);
|
||||
if (receivingRemoved != null && receivingRemoved.size() > 0)
|
||||
disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved);
|
||||
updateRequireInputBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -468,6 +500,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requireInputBuffer(int packetId) {
|
||||
return inputBufferedPackets.contains(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a packet sending or receiving event.
|
||||
* <p>
|
||||
@ -617,7 +654,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
packetInjector.undoCancel(packet.getID(), mcPacket);
|
||||
|
||||
if (filters) {
|
||||
PacketEvent event = packetInjector.packetRecieved(packet, sender);
|
||||
byte[] data = NetworkMarker.getByteBuffer(marker);
|
||||
PacketEvent event = packetInjector.packetRecieved(packet, sender, data);
|
||||
|
||||
if (!event.isCancelled())
|
||||
mcPacket = event.getPacket().getHandle();
|
||||
|
@ -0,0 +1,64 @@
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Represents an input stream that stores every read block of bytes in another output stream.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CaptureInputStream extends FilterInputStream {
|
||||
protected OutputStream out;
|
||||
|
||||
public CaptureInputStream(InputStream in, OutputStream out) {
|
||||
super(in);
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int value = super.read();
|
||||
|
||||
// Write the byte
|
||||
if (value >= 0)
|
||||
out.write(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
out.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
int count = super.read(b);
|
||||
|
||||
if (count > 0 ) {
|
||||
out.write(b, 0, count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int count = super.read(b, off, len);
|
||||
|
||||
if (count > 0 ) {
|
||||
out.write(b, off, count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the output stream that recieves all the read information.
|
||||
* @return The output stream everything is copied to.
|
||||
*/
|
||||
public OutputStream getOutputStream() {
|
||||
return out;
|
||||
}
|
||||
}
|
@ -51,9 +51,10 @@ public interface PacketInjector {
|
||||
* Let the packet listeners process the given packet.
|
||||
* @param packet - a packet to process.
|
||||
* @param client - the client that sent the packet.
|
||||
* @param buffered - a buffer containing the data that had to be read in order to construct the packet.
|
||||
* @return The resulting packet event.
|
||||
*/
|
||||
public abstract PacketEvent packetRecieved(PacketContainer packet, Player client);
|
||||
public abstract PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered);
|
||||
|
||||
/**
|
||||
* Perform any necessary cleanup before unloading ProtocolLib.
|
||||
|
@ -36,6 +36,8 @@ import com.comphenix.protocol.Packets;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
@ -298,6 +300,15 @@ class ProxyPacketInjector implements PacketInjector {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the data a packet read must be buffered.
|
||||
* @param packetId - the packet to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean requireInputBuffers(int packetId) {
|
||||
return manager.requireInputBuffer(packetId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPacketHandler(int packetID) {
|
||||
return PacketRegistry.getPreviousPackets().containsKey(packetID);
|
||||
@ -309,13 +320,13 @@ class ProxyPacketInjector implements PacketInjector {
|
||||
}
|
||||
|
||||
// Called from the ReadPacketModified monitor
|
||||
public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input) {
|
||||
public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input, byte[] buffered) {
|
||||
try {
|
||||
Player client = playerInjection.getPlayerByConnection(input);
|
||||
|
||||
// Never invoke a event if we don't know where it's from
|
||||
if (client != null) {
|
||||
return packetRecieved(packet, client);
|
||||
return packetRecieved(packet, client, buffered);
|
||||
} else {
|
||||
// Hack #2 - Caused by our server socket injector
|
||||
if (packet.getID() != Packets.Client.GET_INFO)
|
||||
@ -330,15 +341,10 @@ class ProxyPacketInjector implements PacketInjector {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Let the packet listeners process the given packet.
|
||||
* @param packet - a packet to process.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The resulting packet event.
|
||||
*/
|
||||
@Override
|
||||
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
|
||||
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, client);
|
||||
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
|
||||
NetworkMarker marker = buffered != null ? new NetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
|
||||
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, marker, client);
|
||||
|
||||
manager.invokePacketRecieving(event);
|
||||
return event;
|
||||
|
@ -17,7 +17,9 @@
|
||||
|
||||
package com.comphenix.protocol.injector.packet;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
@ -89,6 +91,18 @@ class ReadPacketModifier implements MethodInterceptor {
|
||||
Object overridenObject = override.get(thisObj);
|
||||
Object returnValue = null;
|
||||
|
||||
ByteArrayOutputStream bufferStream = null;
|
||||
|
||||
// See if we need to buffer the read data
|
||||
if (isReadPacketDataMethod && packetInjector.requireInputBuffers(packetID)) {
|
||||
CaptureInputStream captured = new CaptureInputStream(
|
||||
(InputStream) args[0],
|
||||
bufferStream = new ByteArrayOutputStream());
|
||||
|
||||
// Stash it back
|
||||
args[0] = new DataInputStream(captured);
|
||||
}
|
||||
|
||||
if (overridenObject != null) {
|
||||
// This packet has been cancelled
|
||||
if (overridenObject == CANCEL_MARKER) {
|
||||
@ -109,10 +123,11 @@ class ReadPacketModifier implements MethodInterceptor {
|
||||
try {
|
||||
// We need this in order to get the correct player
|
||||
DataInputStream input = (DataInputStream) args[0];
|
||||
byte[] buffer = bufferStream != null ? bufferStream.toByteArray() : null;
|
||||
|
||||
// Let the people know
|
||||
PacketContainer container = new PacketContainer(packetID, thisObj);
|
||||
PacketEvent event = packetInjector.packetRecieved(container, input);
|
||||
PacketEvent event = packetInjector.packetRecieved(container, input, buffer);
|
||||
|
||||
// Handle override
|
||||
if (event != null) {
|
||||
|
@ -51,7 +51,7 @@ class DummyPacketInjector implements PacketInjector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketEvent packetRecieved(PacketContainer packet, Player client) {
|
||||
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
|
||||
return injector.packetReceived(packet, client);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user