Attempting to add backwards compatibility for NetworkMarker.

This commit is contained in:
Kristian S. Stangeland 2013-12-06 18:02:26 +01:00
parent 92c8f21d5e
commit 6159507bb4
12 changed files with 359 additions and 21 deletions

View File

@ -2,6 +2,7 @@ package com.comphenix.protocol.events;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -10,6 +11,9 @@ import java.util.PriorityQueue;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.utility.ByteBufferInputStream;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.StreamSerializer; import com.comphenix.protocol.utility.StreamSerializer;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
@ -20,13 +24,35 @@ import com.google.common.primitives.Ints;
* *
* @author Kristian * @author Kristian
*/ */
public class NetworkMarker { public abstract class NetworkMarker {
public static class EmptyBufferMarker extends NetworkMarker {
public EmptyBufferMarker(@Nonnull ConnectionSide side) {
super(side, (ByteBuffer)null, null);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
@Override
protected DataInputStream addHeader(DataInputStream input, PacketType type) {
throw new IllegalStateException("Buffer is empty.");
}
}
// Custom network handler // Custom network handler
private PriorityQueue<PacketOutputHandler> outputHandlers; private PriorityQueue<PacketOutputHandler> outputHandlers;
// The input buffer // The input buffer
private ByteBuffer inputBuffer; private ByteBuffer inputBuffer;
private final ConnectionSide side; private final ConnectionSide side;
private final PacketType type;
// Cache serializer too // Cache serializer too
private StreamSerializer serializer; private StreamSerializer serializer;
@ -36,9 +62,10 @@ public class NetworkMarker {
* @param side - whether or not this marker belongs to a client or server packet. * @param side - whether or not this marker belongs to a client or server packet.
* @param inputBuffer - the read serialized packet data. * @param inputBuffer - the read serialized packet data.
*/ */
public NetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer) { public NetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL."); this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.inputBuffer = Preconditions.checkNotNull(inputBuffer, "inputBuffer cannot be NULL."); this.inputBuffer = Preconditions.checkNotNull(inputBuffer, "inputBuffer cannot be NULL.");
this.type = Preconditions.checkNotNull(type, "type cannot be NULL.");
} }
/** /**
@ -47,10 +74,12 @@ public class NetworkMarker {
* The input buffer is only non-null for client-side packets. * The input buffer is only non-null for client-side packets.
* @param side - whether or not this marker belongs to a client or server packet. * @param side - whether or not this marker belongs to a client or server packet.
* @param inputBuffer - the read serialized packet data. * @param inputBuffer - the read serialized packet data.
* @param handler - handle skipping headers.
*/ */
public NetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer) { public NetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
this.side = Preconditions.checkNotNull(side, "side cannot be NULL."); this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
this.type = Preconditions.checkNotNull(type, "type cannot be NULL.");
if (inputBuffer != null) { if (inputBuffer != null) {
this.inputBuffer = ByteBuffer.wrap(inputBuffer); this.inputBuffer = ByteBuffer.wrap(inputBuffer);
} }
@ -75,7 +104,7 @@ public class NetworkMarker {
} }
/** /**
* Retrieve the serialized packet data (excluding the header) from the network input stream. * Retrieve the serialized packet data (excluding the header by default) from the network input stream.
* <p> * <p>
* The returned buffer is read-only. If the parent event is a server side packet this * The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}. * method throws {@link IllegalStateException}.
@ -84,9 +113,49 @@ public class NetworkMarker {
* @return A byte buffer containing the raw packet data read from the network. * @return A byte buffer containing the raw packet data read from the network.
*/ */
public ByteBuffer getInputBuffer() { public ByteBuffer getInputBuffer() {
return getInputBuffer(true);
}
/**
* Retrieve the serialized packet data from the network input stream.
* <p>
* The returned buffer is read-only. If the parent event is a server side packet this
* method throws {@link IllegalStateException}.
* <p>
* It returns NULL if the packet was transmitted by a plugin locally.
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return A byte buffer containing the raw packet data read from the network.
*/
public ByteBuffer getInputBuffer(boolean excludeHeader) {
if (side.isForServer()) if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer."); throw new IllegalStateException("Server-side packets have no input buffer.");
return inputBuffer != null ? inputBuffer.asReadOnlyBuffer() : null;
if (inputBuffer != null) {
ByteBuffer result = inputBuffer.asReadOnlyBuffer();
try {
if (excludeHeader)
result = skipHeader(result);
else
result = addHeader(result, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return result;
}
return null;
}
/**
* Retrieve the serialized packet data (excluding the header by default) as an input stream.
* <p>
* The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer()
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/
public DataInputStream getInputStream() {
return getInputStream(true);
} }
/** /**
@ -94,17 +163,37 @@ public class NetworkMarker {
* <p> * <p>
* The data is exactly the same as in {@link #getInputBuffer()}. * The data is exactly the same as in {@link #getInputBuffer()}.
* @see #getInputBuffer() * @see #getInputBuffer()
* @param excludeHeader - whether or not to exclude the packet ID header.
* @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally. * @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally.
*/ */
public DataInputStream getInputStream() { @SuppressWarnings("resource")
public DataInputStream getInputStream(boolean excludeHeader) {
if (side.isForServer()) if (side.isForServer())
throw new IllegalStateException("Server-side packets have no input buffer."); throw new IllegalStateException("Server-side packets have no input buffer.");
if (inputBuffer == null) if (inputBuffer == null)
return null; return null;
return new DataInputStream( DataInputStream input = new DataInputStream(
new ByteArrayInputStream(inputBuffer.array()) new ByteArrayInputStream(inputBuffer.array())
); );
try {
if (excludeHeader)
input = skipHeader(input);
else
input = addHeader(input, type);
} catch (IOException e) {
throw new RuntimeException("Cannot skip packet header.", e);
}
return input;
}
/**
* Whether or not the output handlers have to write a packet header.
* @return TRUE if they do, FALSE otherwise.
*/
public boolean requireOutputHeader() {
return MinecraftReflection.isUsingNetty();
} }
/** /**
@ -172,6 +261,40 @@ public class NetworkMarker {
} }
} }
/**
* Return a byte buffer without the header in the current packet.
* <p>
* It's safe to modify the position of the buffer.
* @param buffer - a read-only byte source.
*/
protected ByteBuffer skipHeader(ByteBuffer buffer) throws IOException {
skipHeader(new DataInputStream(new ByteBufferInputStream(buffer)));
return buffer;
}
/**
* Return an input stream without the header in the current packet.
* <p>
* It's safe to modify the input stream.
*/
protected abstract DataInputStream skipHeader(DataInputStream input) throws IOException;
/**
* Return the byte buffer prepended with the packet header.
* @param buffer - the read-only byte buffer.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract ByteBuffer addHeader(ByteBuffer buffer, PacketType type);
/**
* Return the input stream prepended with the packet header.
* @param input - the input stream.
* @param type - the current packet.
* @return The byte buffer.
*/
protected abstract DataInputStream addHeader(DataInputStream input, PacketType type);
/** /**
* Determine if the given marker has any output handlers. * Determine if the given marker has any output handlers.
* @param marker - the marker to check. * @param marker - the marker to check.

View File

@ -207,8 +207,8 @@ public class PacketEvent extends EventObject implements Cancellable {
public NetworkMarker getNetworkMarker() { public NetworkMarker getNetworkMarker() {
if (networkMarker == null) { if (networkMarker == null) {
if (isServerPacket()) { if (isServerPacket()) {
networkMarker = new NetworkMarker( networkMarker = new NetworkMarker.EmptyBufferMarker(
serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, (byte[]) null); serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE);
} else { } else {
throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener."); throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener.");
} }

View File

@ -25,8 +25,10 @@ public interface PacketOutputHandler {
/** /**
* Invoked when a given packet is to be written to the output stream. * Invoked when a given packet is to be written to the output stream.
* <p> * <p>
* Note that the buffer is initially filled with the output from the default write method. This excludes * Note that the buffer is initially filled with the output from the default write method.
* the packet ID header. * <p>
* In Minecraft 1.6.4, the header is always excluded, whereas it MUST be included in Minecraft 1.7.2. Call
* {@link NetworkMarker#requireOutputHeader()} to determine this.
* @param event - the packet that will be outputted. * @param event - the packet that will be outputted.
* @param buffer - the data that is currently scheduled to be outputted. * @param buffer - the data that is currently scheduled to be outputted.
* @return The modified byte array to write. NULL is not permitted. * @return The modified byte array to write. NULL is not permitted.

View File

@ -244,10 +244,6 @@ class ChannelInjector extends ByteToMessageDecoder {
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
ChannelInjector.this.encode(ctx, packet, output); ChannelInjector.this.encode(ctx, packet, output);
} }
public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {
throwable.printStackTrace();
}
}; };
// Insert our handlers - note that we effectively replace the vanilla encoder/decoder // Insert our handlers - note that we effectively replace the vanilla encoder/decoder
@ -369,7 +365,7 @@ class ChannelInjector extends ByteToMessageDecoder {
if (channelListener.includeBuffer(packetClass)) { if (channelListener.includeBuffer(packetClass)) {
byteBuffer.resetReaderIndex(); byteBuffer.resetReaderIndex();
marker = new NetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer)); marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
} }
Object output = channelListener.onPacketReceiving(this, input, marker); Object output = channelListener.onPacketReceiving(this, input, marker);

View File

@ -0,0 +1,40 @@
package com.comphenix.protocol.injector.netty;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
class NettyNetworkMarker extends NetworkMarker {
public NettyNetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer) {
super(side, inputBuffer, null);
}
public NettyNetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer) {
super(side, inputBuffer, null);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
// Skip the variable int containing the packet ID
getSerializer().deserializeVarInt(input);
return input;
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
// We don't have to add anything - it's already there
return buffer;
}
@Override
protected DataInputStream addHeader(DataInputStream input, PacketType type) {
// As above
return input;
}
}

View File

@ -307,7 +307,7 @@ public class NettyProtocolInjector implements ChannelListener {
return new AbstractPacketInjector(reveivedFilters) { return new AbstractPacketInjector(reveivedFilters) {
@Override @Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) { public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new NetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null; NetworkMarker marker = buffered != null ? new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
ChannelInjector.fromPlayer(client, NettyProtocolInjector.this). ChannelInjector.fromPlayer(client, NettyProtocolInjector.this).
saveMarker(packet.getHandle(), marker); saveMarker(packet.getHandle(), marker);
return packetReceived(packet, client, marker); return packetReceived(packet, client, marker);

View File

@ -0,0 +1,66 @@
package com.comphenix.protocol.injector.packet;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import javax.annotation.Nonnull;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.google.common.io.ByteStreams;
import com.google.common.io.InputSupplier;
import com.google.common.primitives.Bytes;
/**
* Represents a network marker for 1.6.4 and earlier.
* @author Kristian
*/
public class LegacyNetworkMarker extends NetworkMarker {
public LegacyNetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer, PacketType type) {
super(side, inputBuffer, type);
}
public LegacyNetworkMarker(@Nonnull ConnectionSide side, ByteBuffer inputBuffer, PacketType type) {
super(side, inputBuffer, type);
}
@Override
protected DataInputStream skipHeader(DataInputStream input) throws IOException {
// This has already been done
return input;
}
@Override
protected ByteBuffer addHeader(ByteBuffer buffer, PacketType type) {
return ByteBuffer.wrap(Bytes.concat(new byte[] { (byte) type.getLegacyId() }, buffer.array()));
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
protected DataInputStream addHeader(final DataInputStream input, final PacketType type) {
InputSupplier<InputStream> header = new InputSupplier<InputStream>() {
@Override
public InputStream getInput() throws IOException {
byte[] data = new byte[] { (byte) type.getLegacyId() };
return new ByteArrayInputStream(data);
}
};
InputSupplier<InputStream> data = new InputSupplier<InputStream>() {
@Override
public InputStream getInput() throws IOException {
return input;
}
};
// Combine them into a single stream
try {
return new DataInputStream(ByteStreams.join((InputSupplier) header, (InputSupplier) data).getInput());
} catch (IOException e) {
throw new RuntimeException("Cannot add header.", e);
}
}
}

View File

@ -349,7 +349,7 @@ class ProxyPacketInjector implements PacketInjector {
@Override @Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) { public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new NetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null; NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
PacketEvent event = PacketEvent.fromClient((Object) manager, packet, marker, client); PacketEvent event = PacketEvent.fromClient((Object) manager, packet, marker, client);
manager.invokePacketRecieving(event); manager.invokePacketRecieving(event);

View File

@ -35,6 +35,7 @@ import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.packet.LegacyNetworkMarker;
import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketInjector;
import com.comphenix.protocol.injector.player.NetworkObjectInjector; import com.comphenix.protocol.injector.player.NetworkObjectInjector;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
@ -486,7 +487,7 @@ public class SpigotPacketInjector implements SpigotPacketListener {
* @return The packet event that was used. * @return The packet event that was used.
*/ */
PacketEvent packetReceived(PacketContainer packet, Player sender, byte[] buffered) { PacketEvent packetReceived(PacketContainer packet, Player sender, byte[] buffered) {
NetworkMarker marker = buffered != null ? new NetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null; NetworkMarker marker = buffered != null ? new LegacyNetworkMarker(ConnectionSide.CLIENT_SIDE, buffered, packet.getType()) : null;
PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender); PacketEvent event = PacketEvent.fromClient(this, packet, marker, sender);
invoker.invokePacketRecieving(event); invoker.invokePacketRecieving(event);

View File

@ -0,0 +1,35 @@
package com.comphenix.protocol.utility;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* Represents an input stream that delegates to a byte buffer.
* @author Kristian
*/
public class ByteBufferInputStream extends InputStream {
private ByteBuffer buf;
public ByteBufferInputStream(ByteBuffer buf) {
this.buf = buf;
}
public int read() throws IOException {
if (!buf.hasRemaining()) {
return -1;
}
return buf.get() & 0xFF;
}
public int read(byte[] bytes, int off, int len)
throws IOException {
if (!buf.hasRemaining()) {
return -1;
}
len = Math.min(len, buf.remaining());
buf.get(bytes, off, len);
return len;
}
}

View File

@ -0,0 +1,26 @@
package com.comphenix.protocol.utility;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Represents an output stream that is backed by a ByteBuffer.
* @author Kristian
*/
public class ByteBufferOutputStream extends OutputStream {
ByteBuffer buf;
public ByteBufferOutputStream(ByteBuffer buf) {
this.buf = buf;
}
public void write(int b) throws IOException {
buf.put((byte) b);
}
public void write(byte[] bytes, int off, int len)
throws IOException {
buf.put(bytes, off, len);
}
}

View File

@ -17,6 +17,7 @@ import com.comphenix.protocol.reflect.FuzzyReflection.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.base.Preconditions;
/** /**
* Utility methods for reading and writing Minecraft objects to streams. * Utility methods for reading and writing Minecraft objects to streams.
@ -24,6 +25,8 @@ import com.comphenix.protocol.wrappers.nbt.NbtFactory;
* @author Kristian * @author Kristian
*/ */
public class StreamSerializer { public class StreamSerializer {
private static final StreamSerializer DEFAULT = new StreamSerializer();
// Cached methods // Cached methods
private static MethodAccessor READ_ITEM_METHOD; private static MethodAccessor READ_ITEM_METHOD;
private static MethodAccessor WRITE_ITEM_METHOD; private static MethodAccessor WRITE_ITEM_METHOD;
@ -34,6 +37,52 @@ public class StreamSerializer {
private static MethodAccessor READ_STRING_METHOD; private static MethodAccessor READ_STRING_METHOD;
private static MethodAccessor WRITE_STRING_METHOD; private static MethodAccessor WRITE_STRING_METHOD;
/**
* Retrieve a default stream serializer.
* @return A serializer.
*/
public static StreamSerializer getDefault() {
return DEFAULT;
}
/**
* Read a variable integer from an input stream.
* @param source - the source.
* @return The integer.
* @throws IOException The source stream threw an exception.
*/
public int deserializeVarInt(@Nonnull DataInputStream source) throws IOException {
Preconditions.checkNotNull(source, "source cannot be NULL");
int result = 0;
int length = 0;
byte currentByte;
do {
currentByte = source.readByte();
result |= (currentByte & 0x7F) << length++ * 7;
if (length > 5)
throw new RuntimeException("VarInt too big");
} while ((currentByte & 0x80) == 0x80);
return result;
}
/**
* Write a variable integer to an output stream.
* @param destination - the destination.
* @param value - the value to write.
* @throws IOException The destination stream threw an exception.
*/
public void serializeVarInt(@Nonnull DataOutputStream destination, int value) throws IOException {
Preconditions.checkNotNull(destination, "source cannot be NULL");
while ((value & 0xFFFFFF80) != 0) {
destination.writeByte(value & 0x7F | 0x80);
value >>>= 7;
}
destination.writeByte(value);
}
/** /**
* Read or deserialize an item stack from an underlying input stream. * Read or deserialize an item stack from an underlying input stream.
* <p> * <p>