Finalize the Netty injection system.

We still have some more debugging and refactoring left, however. In 
particular, I have to switch to PacketType in wherever possible.
This commit is contained in:
Kristian S. Stangeland 2013-12-05 03:54:17 +01:00
parent 7b813fa4e6
commit 6842166b94
15 changed files with 599 additions and 220 deletions

View File

@ -153,12 +153,23 @@ public interface ProtocolManager extends PacketStream {
/**
* Construct a packet using the special builtin Minecraft constructors.
* <p>
* Deprecated: Use {@link #createPacketConstructor(PacketType, Object...)} instead.
* @param id - the packet ID.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
@Deprecated
public PacketConstructor createPacketConstructor(int id, Object... arguments);
/**
* Construct a packet using the special builtin Minecraft constructors.
* @param id - the packet type.
* @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor.
*/
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
/**
* Completely resend an entity to a list of clients.
* <p>

View File

@ -15,6 +15,7 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
@ -318,6 +319,7 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager {
}
}
@SuppressWarnings("deprecation")
@Override
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
if (delegate != null)
@ -325,6 +327,14 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager {
else
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) {
if (delegate != null)
return delegate.createPacketConstructor(type, arguments);
else
return PacketConstructor.DEFAULT.withPacket(type, arguments);
}
@Override
public Set<Integer> getSendingFilters() {
@ -416,4 +426,6 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager {
delegate.close();
closed = true;
}
}

View File

@ -48,6 +48,7 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.async.AsyncFilterManager;
@ -236,7 +237,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// Use the correct injection type
if (MinecraftReflection.isUsingNetty()) {
this.nettyInjector = new NettyProtocolInjector(this);
this.nettyInjector = new NettyProtocolInjector(this, reporter);
this.playerInjection = nettyInjector.getPlayerInjector();
this.packetInjector = nettyInjector.getPacketInjector();
@ -834,10 +835,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
}
@Override
@Deprecated
public PacketConstructor createPacketConstructor(int id, Object... arguments) {
return PacketConstructor.DEFAULT.withPacket(id, arguments);
}
@Override
public PacketConstructor createPacketConstructor(PacketType type, Object... arguments) {
return PacketConstructor.DEFAULT.withPacket(type, arguments);
}
@Override
public Set<Integer> getSendingFilters() {
return playerInjection.getSendingFilters();
@ -1145,6 +1152,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
packetInjector.cleanupAll();
if (spigotInjector != null)
spigotInjector.cleanupAll();
if (nettyInjector != null)
nettyInjector.close();
// Remove server handler
playerInjection.close();

View File

@ -2,6 +2,7 @@ package com.comphenix.protocol.injector.netty;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import com.google.common.collect.ForwardingList;
import com.google.common.collect.Lists;
@ -10,8 +11,8 @@ import com.google.common.collect.Lists;
import net.minecraft.util.io.netty.channel.ChannelFuture;
import net.minecraft.util.io.netty.channel.ChannelHandler;
class BootstrapList extends ForwardingList<ChannelFuture> {
private List<ChannelFuture> delegate;
class BootstrapList extends ForwardingList<Object> {
private List<Object> delegate;
private ChannelHandler handler;
/**
@ -19,59 +20,79 @@ class BootstrapList extends ForwardingList<ChannelFuture> {
* @param delegate - the delegate.
* @param handler - the channel handler to add.
*/
public BootstrapList(List<ChannelFuture> delegate, ChannelHandler handler) {
public BootstrapList(List<Object> delegate, ChannelHandler handler) {
this.delegate = delegate;
this.handler = handler;
// Process all existing bootstraps
for (ChannelFuture future : this)
processBootstrap(future);
for (Object item : this) {
if (item instanceof ChannelFuture) {
processBootstrap((ChannelFuture) item);
}
}
}
@Override
protected List<ChannelFuture> delegate() {
protected List<Object> delegate() {
return delegate;
}
@Override
public boolean add(ChannelFuture element) {
processBootstrap(element);
public boolean add(Object element) {
processElement(element);
return super.add(element);
}
@Override
public boolean addAll(Collection<? extends ChannelFuture> collection) {
List<? extends ChannelFuture> copy = Lists.newArrayList(collection);
public boolean addAll(Collection<? extends Object> collection) {
List<Object> copy = Lists.newArrayList(collection);
// Process the collection before we pass it on
for (ChannelFuture future : copy) {
processBootstrap(future);
for (Object element : copy) {
processElement(element);
}
return super.addAll(copy);
}
@Override
public ChannelFuture set(int index, ChannelFuture element) {
ChannelFuture old = super.set(index, element);
public Object set(int index, Object element) {
Object old = super.set(index, element);
// Handle the old future, and the newly inserted future
if (old != element) {
if (old != null) {
unprocessBootstrap(old);
}
if (element != null) {
processBootstrap(element);
}
unprocessElement(old);
processElement(element);
}
return old;
}
/**
* Process a single element.
* @param element - the element.
*/
protected void processElement(Object element) {
if (element instanceof ChannelFuture) {
processBootstrap((ChannelFuture) element);
}
}
/**
* Unprocess a single element.
* @param element - the element to unprocess.
*/
protected void unprocessElement(Object element) {
if (element instanceof ChannelFuture) {
unprocessBootstrap((ChannelFuture) element);
}
}
/**
* Process a single channel future.
* @param future - the future.
*/
protected void processBootstrap(ChannelFuture future) {
future.channel().pipeline().addLast(handler);
// Important: Must be addFirst()
future.channel().pipeline().addFirst(handler);
}
/**
@ -79,14 +100,18 @@ class BootstrapList extends ForwardingList<ChannelFuture> {
* @param future - the future.
*/
protected void unprocessBootstrap(ChannelFuture future) {
future.channel().pipeline().remove(handler);
try {
future.channel().pipeline().remove(handler);
} catch (NoSuchElementException e) {
// Whatever
}
}
/**
* Close and revert all changes.
*/
public void close() {
for (ChannelFuture future : this)
unprocessBootstrap(future);
for (Object element : this)
unprocessElement(element);
}
}

View File

@ -6,9 +6,11 @@ import java.net.SocketAddress;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import net.minecraft.util.com.google.common.base.Joiner;
import net.minecraft.util.io.netty.buffer.ByteBuf;
import net.minecraft.util.io.netty.channel.Channel;
import net.minecraft.util.io.netty.channel.ChannelHandler;
@ -17,12 +19,16 @@ import net.minecraft.util.io.netty.channel.socket.SocketChannel;
import net.minecraft.util.io.netty.handler.codec.ByteToMessageDecoder;
import net.minecraft.util.io.netty.handler.codec.MessageToByteEncoder;
import net.minecraft.util.io.netty.util.concurrent.GenericFutureListener;
import net.minecraft.util.io.netty.util.internal.TypeParameterMatcher;
import net.sf.cglib.proxy.Factory;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType.Protocol;
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.PacketEvent;
@ -45,6 +51,9 @@ import com.google.common.collect.MapMaker;
* @author Kristian
*/
class ChannelInjector extends ByteToMessageDecoder {
public static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet.");
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
/**
* Represents a listener for received or sent packets.
* @author Kristian
@ -78,18 +87,29 @@ class ChannelInjector extends ByteToMessageDecoder {
* @return TRUE if we do, FALSE otherwise.
*/
public boolean includeBuffer(int packetId);
/**
* Retrieve the current error reporter.
* @return The error reporter.
*/
public ErrorReporter getReporter();
}
private static final ConcurrentMap<Player, ChannelInjector> cachedInjector = new MapMaker().weakKeys().makeMap();
// Saved accessors
private static MethodAccessor DECODE_BUFFER;
private static MethodAccessor ENCODE_BUFFER;
private static FieldAccessor ENCODER_TYPE_MATCHER;
// For retrieving the protocol
private static FieldAccessor PROTOCOL_ACCESSOR;
// The player, or temporary player
private Player player;
// The player connection
private Object playerConnection;
// For retrieving the protocol
private FieldAccessor protocolAccessor;
// The current network manager and channel
private final Object networkManager;
@ -106,8 +126,6 @@ class ChannelInjector extends ByteToMessageDecoder {
// Other handlers
private ByteToMessageDecoder vanillaDecoder;
private MessageToByteEncoder<Object> vanillaEncoder;
private MethodAccessor decodeBuffer;
private MethodAccessor encodeBuffer;
// Our extra handler
private MessageToByteEncoder<Object> protocolEncoder;
@ -130,16 +148,18 @@ class ChannelInjector extends ByteToMessageDecoder {
this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL");
this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL");
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
// Get the channel field
this.channelField = new VolatileField(
FuzzyReflection.fromObject(networkManager, true).
getFieldByType("channel", Channel.class), networkManager);
getFieldByType("channel", Channel.class),
networkManager, true);
}
/**
* Construct or retrieve a channel injector from an existing Bukkit player.
* @param player - the existing Bukkit player.
* @param channelListener - the listener.
* @return A new injector, or an existing injector associated with this player.
*/
public static ChannelInjector fromPlayer(Player player, ChannelListener listener) {
@ -169,6 +189,8 @@ class ChannelInjector extends ByteToMessageDecoder {
* Construct a new channel injector for the given channel.
* @param channel - the channel.
* @param playerFactory - a temporary player creator.
* @param channelListener - the listener.
* @param loader - the current (plugin) class loader.
* @return The channel injector.
*/
public static ChannelInjector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
@ -201,48 +223,81 @@ class ChannelInjector extends ByteToMessageDecoder {
// Get the vanilla decoder, so we don't have to replicate the work
vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder");
vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder");
decodeBuffer = FuzzyReflection.getMethodAccessor(vanillaDecoder.getClass(),
patchEncoder(vanillaEncoder);
if (vanillaDecoder == null)
throw new IllegalArgumentException("Unable to find vanilla decoder.in " + originalChannel.pipeline());
if (vanillaEncoder == null)
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline());
if (DECODE_BUFFER == null)
DECODE_BUFFER = FuzzyReflection.getMethodAccessor(vanillaDecoder.getClass(),
"decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
encodeBuffer = FuzzyReflection.getMethodAccessor(vanillaEncoder.getClass(),
if (ENCODE_BUFFER == null)
ENCODE_BUFFER = FuzzyReflection.getMethodAccessor(vanillaEncoder.getClass(),
"encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
protocolEncoder = new MessageToByteEncoder<Object>() {
@Override
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
NetworkMarker marker = getMarker(output);
PacketEvent event = markerEvent.remove(marker);
if (event != null && NetworkMarker.hasOutputHandlers(marker)) {
ByteBuf packetBuffer = ctx.alloc().buffer();
encodeBuffer.invoke(vanillaEncoder, ctx, packet, packetBuffer);
byte[] data = getBytes(packetBuffer);
try {
NetworkMarker marker = getMarker(output);
PacketEvent event = markerEvent.remove(marker);
for (PacketOutputHandler handler : marker.getOutputHandlers()) {
handler.handle(event, data);
if (event != null && NetworkMarker.hasOutputHandlers(marker)) {
ByteBuf packetBuffer = ctx.alloc().buffer();
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, packetBuffer);
byte[] data = getBytes(packetBuffer);
for (PacketOutputHandler handler : marker.getOutputHandlers()) {
handler.handle(event, data);
}
// Write the result
output.writeBytes(data);
return;
}
// Write the result
output.writeBytes(data);
} catch (Exception e) {
channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build());
} finally {
// Attempt to handle the packet nevertheless
ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, output);
}
}
public void exceptionCaught(ChannelHandlerContext channelhandlercontext, Throwable throwable) {
throwable.printStackTrace();
}
};
// Insert our handler - note that we replace the decoder with our own
// Insert our handlers - note that we effectively replace the vanilla encoder/decoder
originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this);
originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder);
// Intercept all write methods
channelField.setValue(new ChannelProxy() {
channelField.setValue(new ChannelProxy(originalChannel) {
@Override
protected Object onMessageWritten(Object message) {
return channelListener.onPacketSending(ChannelInjector.this, message, packetMarker.get(message));
}
}.asChannel(originalChannel));
});
injected = true;
return true;
}
}
/**
* This method patches the encoder so that it skips already created packets.
* @param encoder - the encoder to patch.
*/
private void patchEncoder(MessageToByteEncoder<Object> encoder) {
if (ENCODER_TYPE_MATCHER == null) {
ENCODER_TYPE_MATCHER = FuzzyReflection.getFieldAccessor(encoder.getClass(), "matcher", true);
}
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
}
/**
* Close the current injector.
*/
@ -251,34 +306,54 @@ class ChannelInjector extends ByteToMessageDecoder {
closed = true;
if (injected) {
originalChannel.pipeline().remove(this);
originalChannel.pipeline().remove(protocolEncoder);
channelField.revertValue();
try {
originalChannel.pipeline().remove(this);
originalChannel.pipeline().remove(protocolEncoder);
} catch (NoSuchElementException e) {
// Ignore it - the player has logged out
}
}
}
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception {
byteBuffer.markReaderIndex();
decodeBuffer.invoke(vanillaDecoder, ctx, byteBuffer, packets);
if (packets.size() > 0) {
Object input = packets.get(0);
int id = PacketRegistry.getPacketID(input.getClass());
NetworkMarker marker = null;
try {
byteBuffer.markReaderIndex();
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
if (channelListener.includeBuffer(id)) {
byteBuffer.resetReaderIndex();
marker = new NetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
if (packets.size() > 0) {
Object input = packets.get(0);
int id = PacketRegistry.getPacketID(input.getClass());
NetworkMarker marker = null;
if (channelListener.includeBuffer(id)) {
byteBuffer.resetReaderIndex();
marker = new NetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
}
Object output = channelListener.onPacketReceiving(this, input, marker);
// Handle packet changes
if (output == null)
packets.clear();
else if (output != input)
packets.set(0, output);
}
Object output = channelListener.onPacketReceiving(this, input, marker);
// Handle packet changes
if (output == null)
packets.clear();
else if (output != input)
packets.set(0, output);
} catch (Exception e) {
channelListener.getReporter().reportDetailed(this,
Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
// See NetworkManager.channelActive(ChannelHandlerContext) for why
if (channelField != null) {
channelField.refreshValue();
}
}
@ -364,11 +439,11 @@ class ChannelInjector extends ByteToMessageDecoder {
* @return The current protocol.
*/
public Protocol getCurrentProtocol() {
if (protocolAccessor == null) {
protocolAccessor = FuzzyReflection.getFieldAccessor(
if (PROTOCOL_ACCESSOR == null) {
PROTOCOL_ACCESSOR = FuzzyReflection.getFieldAccessor(
networkManager.getClass(), MinecraftReflection.getEnumProtocolClass(), true);
}
return Protocol.fromVanilla((Enum<?>) protocolAccessor.get(networkManager));
return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager));
}
/**

View File

@ -1,57 +1,236 @@
package com.comphenix.protocol.injector.netty;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import java.lang.reflect.Constructor;
import java.net.SocketAddress;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import net.minecraft.util.com.google.common.collect.Sets;
import net.minecraft.util.io.netty.buffer.ByteBufAllocator;
import net.minecraft.util.io.netty.channel.Channel;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.minecraft.util.io.netty.channel.ChannelConfig;
import net.minecraft.util.io.netty.channel.ChannelFuture;
import net.minecraft.util.io.netty.channel.ChannelMetadata;
import net.minecraft.util.io.netty.channel.ChannelPipeline;
import net.minecraft.util.io.netty.channel.ChannelProgressivePromise;
import net.minecraft.util.io.netty.channel.ChannelPromise;
import net.minecraft.util.io.netty.channel.EventLoop;
import net.minecraft.util.io.netty.util.Attribute;
import net.minecraft.util.io.netty.util.AttributeKey;
import net.minecraft.util.io.netty.util.concurrent.EventExecutor;
abstract class ChannelProxy {
private static Set<Method> WRITE_METHODS;
abstract class ChannelProxy implements Channel {
private static Constructor<? extends ChannelFuture> FUTURE_CONSTRUCTOR;
/**
* Retrieve the channel proxy object.
* @param proxyInstance - the proxy instance object.
* @return The channel proxy.
*/
public Channel asChannel(final Channel proxyInstance) {
// Simple way to match all the write methods
if (WRITE_METHODS == null) {
List<Method> writers = FuzzyReflection.fromClass(Channel.class).
getMethodList(FuzzyMethodContract.newBuilder().nameRegex("write.*").build());
WRITE_METHODS = Sets.newHashSet(writers);
}
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Channel.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (WRITE_METHODS.contains(method)) {
args[0] = onMessageWritten(args[0]);
// If we should skip this object
if (args[0] == null)
return null;
}
// Forward to proxy
return proxy.invoke(proxyInstance, args);
}
});
return (Channel) enhancer.create();
// The underlying channel
private Channel delegate;
public ChannelProxy(Channel delegate) {
this.delegate = delegate;
}
/**
* Invoked when a packet is being transmitted.
* @param message - the packet to transmit.
* @return The object to transmit.
*/
protected abstract Object onMessageWritten(Object message);
/**
* The future we return when packets are being cancelled.
* @return A succeeded future.
*/
protected ChannelFuture getSucceededFuture() {
try {
if (FUTURE_CONSTRUCTOR == null) {
@SuppressWarnings("unchecked")
Class<? extends ChannelFuture> succededFuture =
(Class<? extends ChannelFuture>) ChannelProxy.class.getClassLoader().
loadClass("net.minecraft.util.io.netty.channel.SucceededChannelFuture");
FUTURE_CONSTRUCTOR = succededFuture.getConstructor(Channel.class, EventExecutor.class);
}
return FUTURE_CONSTRUCTOR.newInstance(this, null);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot get succeeded future.");
} catch (Exception e) {
throw new RuntimeException("Cannot construct completed future.");
}
}
public <T> Attribute<T> attr(AttributeKey<T> paramAttributeKey) {
return delegate.attr(paramAttributeKey);
}
public ChannelFuture bind(SocketAddress paramSocketAddress) {
return delegate.bind(paramSocketAddress);
}
public ChannelPipeline pipeline() {
return delegate.pipeline();
}
public ChannelFuture connect(SocketAddress paramSocketAddress) {
return delegate.connect(paramSocketAddress);
}
public ByteBufAllocator alloc() {
return delegate.alloc();
}
public ChannelPromise newPromise() {
return delegate.newPromise();
}
public EventLoop eventLoop() {
return delegate.eventLoop();
}
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2);
}
public ChannelProgressivePromise newProgressivePromise() {
return delegate.newProgressivePromise();
}
public Channel parent() {
return delegate.parent();
}
public ChannelConfig config() {
return delegate.config();
}
public ChannelFuture newSucceededFuture() {
return delegate.newSucceededFuture();
}
public boolean isOpen() {
return delegate.isOpen();
}
public ChannelFuture disconnect() {
return delegate.disconnect();
}
public boolean isRegistered() {
return delegate.isRegistered();
}
public ChannelFuture newFailedFuture(Throwable paramThrowable) {
return delegate.newFailedFuture(paramThrowable);
}
public ChannelFuture close() {
return delegate.close();
}
public boolean isActive() {
return delegate.isActive();
}
@Deprecated
public ChannelFuture deregister() {
return delegate.deregister();
}
public ChannelPromise voidPromise() {
return delegate.voidPromise();
}
public ChannelMetadata metadata() {
return delegate.metadata();
}
public ChannelFuture bind(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.bind(paramSocketAddress, paramChannelPromise);
}
public SocketAddress localAddress() {
return delegate.localAddress();
}
public SocketAddress remoteAddress() {
return delegate.remoteAddress();
}
public ChannelFuture connect(SocketAddress paramSocketAddress,
ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress, paramChannelPromise);
}
public ChannelFuture closeFuture() {
return delegate.closeFuture();
}
public boolean isWritable() {
return delegate.isWritable();
}
public Channel flush() {
return delegate.flush();
}
public ChannelFuture connect(SocketAddress paramSocketAddress1,
SocketAddress paramSocketAddress2, ChannelPromise paramChannelPromise) {
return delegate.connect(paramSocketAddress1, paramSocketAddress2, paramChannelPromise);
}
public Channel read() {
return delegate.read();
}
public Unsafe unsafe() {
return delegate.unsafe();
}
public ChannelFuture disconnect(ChannelPromise paramChannelPromise) {
return delegate.disconnect(paramChannelPromise);
}
public ChannelFuture close(ChannelPromise paramChannelPromise) {
return delegate.close(paramChannelPromise);
}
@Deprecated
public ChannelFuture deregister(ChannelPromise paramChannelPromise) {
return delegate.deregister(paramChannelPromise);
}
public ChannelFuture write(Object message) {
Object result = onMessageWritten(message);
if (result != null)
return delegate.write(result);
return getSucceededFuture();
}
public ChannelFuture write(Object message, ChannelPromise paramChannelPromise) {
Object result = onMessageWritten(message);
if (result != null)
return delegate.write(message, paramChannelPromise);
return getSucceededFuture();
}
public ChannelFuture writeAndFlush(Object message, ChannelPromise paramChannelPromise) {
Object result = onMessageWritten(message);
if (result != null)
return delegate.writeAndFlush(message, paramChannelPromise);
return getSucceededFuture();
}
public ChannelFuture writeAndFlush(Object message) {
Object result = onMessageWritten(message);
if (result != null)
return delegate.writeAndFlush(message);
return getSucceededFuture();
}
public int compareTo(Channel o) {
return delegate.compareTo(o);
}
}

View File

@ -20,6 +20,7 @@ import net.minecraft.util.io.netty.channel.ChannelInitializer;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
@ -34,6 +35,7 @@ import com.comphenix.protocol.injector.spigot.AbstractPlayerHandler;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Lists;
public class NettyProtocolInjector implements ChannelListener {
private volatile boolean injected;
@ -41,7 +43,7 @@ public class NettyProtocolInjector implements ChannelListener {
// The temporary player factory
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
private VolatileField bootstrapField;
private List<VolatileField> bootstrapFields = Lists.newArrayList();
// Different sending filters
private IntegerSet queuedFilters = new IntegerSet(Packets.PACKET_COUNT);
@ -49,11 +51,14 @@ public class NettyProtocolInjector implements ChannelListener {
// Which packets are buffered
private Set<Integer> bufferedPackets;
private ListenerInvoker invoker;
public NettyProtocolInjector(ListenerInvoker invoker) {
// Handle errors
private ErrorReporter reporter;
public NettyProtocolInjector(ListenerInvoker invoker, ErrorReporter reporter) {
this.invoker = invoker;
this.reporter = reporter;
}
/**
@ -89,7 +94,7 @@ public class NettyProtocolInjector implements ChannelListener {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg;
// Execute the other handlers before adding our own
ctx.fireChannelRead(msg);
channel.pipeline().addLast(initProtocol);
@ -97,11 +102,14 @@ public class NettyProtocolInjector implements ChannelListener {
};
// Insert ProtocolLib's connection interceptor
bootstrapField = getBootstrapField(serverConnection);
bootstrapField.setValue(new BootstrapList(
(List<ChannelFuture>) bootstrapField.getValue(), connectionHandler
));
bootstrapFields = getBootstrapFields(serverConnection);
for (VolatileField field : bootstrapFields) {
field.setValue(new BootstrapList(
(List<Object>) field.getValue(), connectionHandler
));
}
injected = true;
} catch (Exception e) {
@ -109,6 +117,11 @@ public class NettyProtocolInjector implements ChannelListener {
}
}
@Override
public ErrorReporter getReporter() {
return reporter;
}
/**
* Inject our packet handling into a specific player.
* @param player
@ -117,23 +130,20 @@ public class NettyProtocolInjector implements ChannelListener {
ChannelInjector.fromPlayer(player, this).inject();
}
private VolatileField getBootstrapField(Object serverConnection) {
VolatileField firstVolatile = null;
private List<VolatileField> getBootstrapFields(Object serverConnection) {
List<VolatileField> result = Lists.newArrayList();
// Find and (possibly) proxy every list
for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) {
VolatileField currentVolatile = new VolatileField(field, serverConnection, true);
VolatileField volatileField = new VolatileField(field, serverConnection, true);
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) currentVolatile.getValue();
List<Object> list = (List<Object>) volatileField.getValue();
// Also save the first list
if (firstVolatile == null) {
firstVolatile = currentVolatile;
}
if (list.size() > 0 && list.get(0) instanceof ChannelFuture) {
return currentVolatile;
if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
result.add(volatileField);
}
}
return firstVolatile;
return result;
}
/**
@ -142,22 +152,21 @@ public class NettyProtocolInjector implements ChannelListener {
public synchronized void close() {
if (!closed) {
closed = true;
@SuppressWarnings("unchecked")
List<Object> bootstraps = (List<Object>) bootstrapField.getValue();
// Remember to close all the bootstraps
for (Object value : bootstraps) {
for (VolatileField field : bootstrapFields) {
Object value = field.getValue();
// Undo the processed channels, if any
if (value instanceof BootstrapList) {
((BootstrapList) value).close();
}
field.revertValue();
}
// Uninject all the players
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
ChannelInjector.fromPlayer(player, this).close();
}
bootstrapField.revertValue();
}
}
@ -261,12 +270,14 @@ public class NettyProtocolInjector implements ChannelListener {
@Override
public void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException {
ChannelInjector.fromPlayer(reciever, listener).sendServerPacket(packet.getHandle(), marker, filters);
ChannelInjector.fromPlayer(reciever, listener).
sendServerPacket(packet.getHandle(), marker, filters);
}
@Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException {
ChannelInjector.fromPlayer(player, listener).recieveClientPacket(mcPacket, null, true);
ChannelInjector.fromPlayer(player, listener).
recieveClientPacket(mcPacket, null, true);
}
@Override
@ -296,7 +307,8 @@ public class NettyProtocolInjector implements ChannelListener {
@Override
public PacketEvent packetRecieved(PacketContainer packet, Player client, byte[] buffered) {
NetworkMarker marker = buffered != null ? new NetworkMarker(ConnectionSide.CLIENT_SIDE, buffered) : null;
ChannelInjector.fromPlayer(client, NettyProtocolInjector.this).saveMarker(packet.getHandle(), marker);
ChannelInjector.fromPlayer(client, NettyProtocolInjector.this).
saveMarker(packet.getHandle(), marker);
return packetReceived(packet, client, marker);
}

View File

@ -30,23 +30,17 @@ import net.sf.cglib.proxy.NoOp;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.utility.ChatExtensions;
/**
* Create fake player instances that represents pre-authenticated clients.
*/
public class TemporaryPlayerFactory {
// Helpful constructors
private final PacketConstructor chatPacket;
// Prevent too many class creations
private static CallbackFilter callbackFilter;
public TemporaryPlayerFactory() {
chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" });
}
/**
* Retrieve the injector from a given player if it contains one.
* @param player - the player that may contain a reference to a player injector.
@ -190,7 +184,9 @@ public class TemporaryPlayerFactory {
* @throws FieldAccessException If we were unable to construct the message packet.
*/
private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), null, false);
for (PacketContainer packet : ChatExtensions.createChatPackets(message)) {
injector.sendServerPacket(packet.getHandle(), null, false);
}
return null;
}
}

View File

@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
@ -29,9 +30,11 @@ import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import net.minecraft.util.com.google.common.base.Joiner;
import net.minecraft.util.com.google.common.collect.Sets;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@ -135,10 +138,22 @@ public class FuzzyReflection {
*/
public static FieldAccessor getFieldAccessor(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) {
// Get a field accessor
Field field = FuzzyReflection.fromObject(instanceClass, forceAccess).getFieldByType(null, fieldClass);
Field field = FuzzyReflection.fromClass(instanceClass, forceAccess).getFieldByType(null, fieldClass);
return getFieldAccessor(field);
}
/**
* Retrieve an accessor for the first field of the given type.
* @param instanceClass - the type of the instance to retrieve.
* @param fieldClass - type of the field to retrieve.
* @param forceAccess - whether or not to look for private and protected fields.
* @return The value of that field.
* @throws IllegalArgumentException If the field cannot be found.
*/
public static FieldAccessor getFieldAccessor(Class<?> instanceClass, String fieldName, boolean forceAccess) {
return getFieldAccessor(FieldUtils.getField(instanceClass, fieldName, forceAccess));
}
/**
* Retrieve a field accessor from a given field that uses unchecked exceptions.
* @param field - the field.
@ -186,9 +201,30 @@ public class FuzzyReflection {
* @return The method accessor.
*/
public static MethodAccessor getMethodAccessor(Class<?> instanceClass, String name, Class<?>... parameters) {
Method method = MethodUtils.getAccessibleMethod(instanceClass, name, parameters);
method.setAccessible(true);
return getMethodAccessor(method);
return getMethodAccessor(instanceClass, instanceClass, name, parameters);
}
// Helper method
private static MethodAccessor getMethodAccessor(
Class<?> initialClass, Class<?> instanceClass, String name, Class<?>... parameters) {
try {
Method method = instanceClass.getDeclaredMethod(name, parameters);
method.setAccessible(true);
return getMethodAccessor(method);
} catch (NoSuchMethodException e) {
// Search for a private method in the superclass
if (initialClass.getSuperclass() != null)
return getMethodAccessor(initialClass, instanceClass.getSuperclass(), name, parameters);
// Unable to find it
throw new IllegalArgumentException("Unable to find method " + name +
"(" + Joiner.on(", ").join(parameters) +") in " + initialClass);
} catch (Exception e) {
throw new RuntimeException("Unable to retrieve methods.", e);
}
}
/**
@ -244,7 +280,13 @@ public class FuzzyReflection {
Field field = null;
try {
method = getMethodByParameters("getInstance", source.getClass(), new Class<?>[0]);
method = getMethod(
FuzzyMethodContract.newBuilder().
parameterCount(0).
returnDerivedOf(source).
requireModifier(Modifier.STATIC).
build()
);
} catch (IllegalArgumentException e) {
// Try getting the field instead
// Note that this will throw an exception if not found

View File

@ -122,7 +122,6 @@ public class VolatileField {
* @param newValue - new field value.
*/
public void setValue(Object newValue) {
// Remember to safe the previous value
ensureLoaded();
@ -132,7 +131,20 @@ public class VolatileField {
currentSet = true;
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read field " + field.getName(), e);
throw new RuntimeException("Unable to write field " + field.getName(), e);
}
}
/**
* Ensure the previously set value is set.
*/
public void refreshValue() {
if (currentSet) {
try {
FieldUtils.writeField(field, container, current, forceAccess);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read field " + field.getName(), e);
}
}
}

View File

@ -19,7 +19,6 @@ package com.comphenix.protocol.utility;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
@ -27,12 +26,14 @@ import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.FuzzyReflection.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.google.common.base.Strings;
@ -45,12 +46,14 @@ import com.google.common.collect.Iterables;
*/
public class ChatExtensions {
// Used to sent chat messages
private PacketConstructor chatConstructor;
private ProtocolManager manager;
// The chat packet constructor
private static volatile PacketConstructor chatConstructor;
// Whether or not we have to use the post-1.6.1 chat format
private static Constructor<?> jsonConstructor = getJsonFormatConstructor();
private static Method messageFactory;
private static volatile Constructor<?> jsonConstructor = getJsonFormatConstructor();
private static volatile MethodAccessor messageFactory;
public ChatExtensions(ProtocolManager manager) {
this.manager = manager;
@ -83,63 +86,64 @@ public class ChatExtensions {
* @throws InvocationTargetException If we were unable to send the message.
*/
private void sendMessageSilently(Player player, String message) throws InvocationTargetException {
if (jsonConstructor != null) {
sendMessageAsJson(player, message);
} else {
sendMessageAsString(player, message);
try {
for (PacketContainer packet : createChatPackets(message)) {
manager.sendServerPacket(player, packet, false);
}
} catch (FieldAccessException e) {
throw new InvocationTargetException(e);
}
}
/**
* Send a message using the new JSON format in 1.6.1.
* @param player - the player to send it to.
* Construct chat packet to send in order to display a given message.
* @param message - the message to send.
* @throws InvocationTargetException InvocationTargetException If we were unable to send the message.
* @return The packets.
*/
private void sendMessageAsJson(Player player, String message) throws InvocationTargetException {
Object messageObject = null;
public static PacketContainer[] createChatPackets(String message) {
if (jsonConstructor != null) {
if (chatConstructor == null) {
Class<?> messageClass = jsonConstructor.getParameterTypes()[0];
chatConstructor = PacketConstructor.DEFAULT.withPacket(PacketType.Play.Server.CHAT, new Object[] { messageClass });
// Try one of the string constructors
if (MinecraftReflection.isUsingNetty()) {
messageFactory = FuzzyReflection.getMethodAccessor(
MinecraftReflection.getCraftMessageClass(), "fromString", String.class);
} else {
messageFactory = FuzzyReflection.getMethodAccessor(
FuzzyReflection.fromClass(messageClass).getMethod(
FuzzyMethodContract.newBuilder().
requireModifier(Modifier.STATIC).
parameterCount(1).
parameterExactType(String.class).
returnTypeMatches(FuzzyMatchers.matchParent()).
build())
);
}
}
// Minecraft 1.7.2 and later
if (MinecraftReflection.isUsingNetty()) {
Object[] components = (Object[]) messageFactory.invoke(null, message);
PacketContainer[] packets = new PacketContainer[components.length];
for (int i = 0; i < components.length; i++) {
packets[i] = chatConstructor.createPacket(components[i]);
}
return packets;
// Minecraft 1.6.1 - 1.6.4
} else {
return new PacketContainer[] { chatConstructor.createPacket(messageFactory.invoke(null, message)) };
}
if (chatConstructor == null) {
Class<?> messageClass = jsonConstructor.getParameterTypes()[0];
chatConstructor = manager.createPacketConstructor(Packets.Server.CHAT, messageClass);
// Try one of the string constructors
messageFactory = FuzzyReflection.fromClass(messageClass).getMethod(
FuzzyMethodContract.newBuilder().
requireModifier(Modifier.STATIC).
parameterCount(1).
parameterExactType(String.class).
returnTypeMatches(FuzzyMatchers.matchParent()).
build());
}
try {
messageObject = messageFactory.invoke(null, message);
} catch (Exception e) {
throw new InvocationTargetException(e);
}
try {
manager.sendServerPacket(player, chatConstructor.createPacket(messageObject), false);
} catch (FieldAccessException e) {
throw new InvocationTargetException(e);
}
}
/**
* Send a message as a pure string.
* @param player - the player.
* @param message - the message to send.
* @throws InvocationTargetException If anything went wrong.
*/
private void sendMessageAsString(Player player, String message) throws InvocationTargetException {
if (chatConstructor == null)
chatConstructor = manager.createPacketConstructor(Packets.Server.CHAT, message);
try {
manager.sendServerPacket(player, chatConstructor.createPacket(message), false);
} catch (FieldAccessException e) {
throw new InvocationTargetException(e);
} else {
if (chatConstructor == null) {
chatConstructor = PacketConstructor.DEFAULT.withPacket(PacketType.Play.Server.CHAT, new Object[] { message });
}
// Minecraft 1.6.0 and earlier
return new PacketContainer[] { chatConstructor.createPacket(message) };
}
}
@ -210,7 +214,7 @@ public class ChatExtensions {
* @return A constructor for JSON-based packets.
*/
static Constructor<?> getJsonFormatConstructor() {
Class<?> chatPacket = PacketRegistry.getPacketClassFromID(3, true);
Class<?> chatPacket = PacketRegistry.getPacketClassFromType(PacketType.Play.Server.CHAT, true);
List<Constructor<?>> list = FuzzyReflection.fromClass(chatPacket).getConstructorList(
FuzzyMethodContract.newBuilder().
parameterCount(1).

View File

@ -1,14 +1,10 @@
package com.comphenix.protocol.utility;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import org.bukkit.command.defaults.EnchantCommand;
import net.minecraft.util.io.netty.buffer.ByteBuf;
import net.minecraft.util.io.netty.buffer.UnpooledByteBufAllocator;
import net.minecraft.util.io.netty.channel.ChannelHandlerContext;

View File

@ -1366,6 +1366,14 @@ public class MinecraftReflection {
public static Class<?> getCraftEntityClass() {
return getCraftBukkitClass("entity.CraftEntity");
}
/**
* Retrieve the CraftChatMessage introduced in 1.7.2
* @return The CraftChatMessage class.
*/
public static Class<?> getCraftMessageClass() {
return getCraftBukkitClass("util.CraftChatMessage");
}
/**
* Retrieve a CraftItemStack from a given ItemStack.

View File

@ -7,7 +7,6 @@ import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import javax.annotation.Nonnull;
import org.bukkit.inventory.ItemStack;

View File

@ -6,7 +6,6 @@ import static org.mockito.Mockito.when;
import net.minecraft.server.v1_7_R1.Block;
import net.minecraft.server.v1_7_R1.Item;
import net.minecraft.server.v1_7_R1.RegistryMaterials;
import net.minecraft.server.v1_7_R1.StatisticList;
// Will have to be updated for every version though