Skip to content

Commit

Permalink
Added the ability to intercept serialized input packets on CraftBukkit.
Browse files Browse the repository at this point in the history
Spigot will be added later.
  • Loading branch information
aadnk committed Jul 17, 2013
1 parent 8f30199 commit 6527c0a
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePh
this.priority = priority;
this.whitelist = Sets.newHashSet(whitelist);
this.gamePhase = gamePhase;
this.options.addAll(Arrays.asList(options));

if (options != null) {
this.options.addAll(Arrays.asList(options));
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,23 @@ private void checkServerSide() {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
* @author Kristian
*/
public abstract class PacketAdapter implements PacketListener {

protected Plugin plugin;
protected ConnectionSide connectionSide;
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
Expand Down Expand Up @@ -95,6 +94,17 @@ public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPrior
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.
Expand All @@ -117,6 +127,23 @@ public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gam
* @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)
Expand All @@ -127,12 +154,14 @@ public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, ListenerPrior
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;
Expand Down Expand Up @@ -180,7 +209,6 @@ public static String getPluginName(PacketListener listener) {
* @return Name of the given plugin.
*/
public static String getPluginName(Plugin plugin) {

// Try to get the plugin name
try {
if (plugin == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@
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;
Expand Down Expand Up @@ -138,6 +140,9 @@ public enum PlayerInjectHooks {
// 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;
Expand Down Expand Up @@ -323,6 +328,11 @@ public void addPacketListener(PacketListener listener) {
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());
Expand All @@ -344,9 +354,30 @@ public void addPacketListener(PacketListener listener) {

// 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.
Expand Down Expand Up @@ -437,6 +468,7 @@ public void removePacketListener(PacketListener listener) {
disablePacketFilters(ConnectionSide.SERVER_SIDE, sendingRemoved);
if (receivingRemoved != null && receivingRemoved.size() > 0)
disablePacketFilters(ConnectionSide.CLIENT_SIDE, receivingRemoved);
updateRequireInputBuffers();
}

@Override
Expand Down Expand Up @@ -467,6 +499,11 @@ public void invokePacketSending(PacketEvent event) {
handlePacket(sendingListeners, event, true);
}
}

@Override
public boolean requireInputBuffer(int packetId) {
return inputBufferedPackets.contains(packetId);
}

/**
* Handle a packet sending or receiving event.
Expand Down Expand Up @@ -617,7 +654,8 @@ public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMa
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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
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;
Expand Down Expand Up @@ -298,6 +300,15 @@ public boolean removePacketHandler(int packetID) {
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);
Expand All @@ -309,13 +320,13 @@ public Set<Integer> getPacketHandlers() {
}

// 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)
Expand All @@ -330,15 +341,10 @@ public PacketEvent packetRecieved(PacketContainer packet, DataInputStream input)
}
}

/**
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -89,6 +91,18 @@ public Object intercept(Object thisObj, Method method, Object[] args, MethodProx
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) {
Expand All @@ -109,10 +123,11 @@ public Object intercept(Object thisObj, Method method, Object[] args, MethodProx
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) {
Expand Down
Loading

0 comments on commit 6527c0a

Please sign in to comment.