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. * Construct a packet using the special builtin Minecraft constructors.
* <p>
* Deprecated: Use {@link #createPacketConstructor(PacketType, Object...)} instead.
* @param id - the packet ID. * @param id - the packet ID.
* @param arguments - arguments that will be passed to the constructor. * @param arguments - arguments that will be passed to the constructor.
* @return The packet constructor. * @return The packet constructor.
*/ */
@Deprecated
public PacketConstructor createPacketConstructor(int id, Object... arguments); 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. * Completely resend an entity to a list of clients.
* <p> * <p>

View File

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

View File

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

View File

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

View File

@ -6,9 +6,11 @@ import java.net.SocketAddress;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentMap; 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.buffer.ByteBuf;
import net.minecraft.util.io.netty.channel.Channel; import net.minecraft.util.io.netty.channel.Channel;
import net.minecraft.util.io.netty.channel.ChannelHandler; 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.ByteToMessageDecoder;
import net.minecraft.util.io.netty.handler.codec.MessageToByteEncoder; 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.concurrent.GenericFutureListener;
import net.minecraft.util.io.netty.util.internal.TypeParameterMatcher;
import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.Factory;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType.Protocol; 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.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
@ -45,6 +51,9 @@ import com.google.common.collect.MapMaker;
* @author Kristian * @author Kristian
*/ */
class ChannelInjector extends ByteToMessageDecoder { 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. * Represents a listener for received or sent packets.
* @author Kristian * @author Kristian
@ -78,18 +87,29 @@ class ChannelInjector extends ByteToMessageDecoder {
* @return TRUE if we do, FALSE otherwise. * @return TRUE if we do, FALSE otherwise.
*/ */
public boolean includeBuffer(int packetId); 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(); 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 // The player, or temporary player
private Player player; private Player player;
// The player connection // The player connection
private Object playerConnection; private Object playerConnection;
// For retrieving the protocol
private FieldAccessor protocolAccessor;
// The current network manager and channel // The current network manager and channel
private final Object networkManager; private final Object networkManager;
@ -106,8 +126,6 @@ class ChannelInjector extends ByteToMessageDecoder {
// Other handlers // Other handlers
private ByteToMessageDecoder vanillaDecoder; private ByteToMessageDecoder vanillaDecoder;
private MessageToByteEncoder<Object> vanillaEncoder; private MessageToByteEncoder<Object> vanillaEncoder;
private MethodAccessor decodeBuffer;
private MethodAccessor encodeBuffer;
// Our extra handler // Our extra handler
private MessageToByteEncoder<Object> protocolEncoder; private MessageToByteEncoder<Object> protocolEncoder;
@ -130,16 +148,18 @@ class ChannelInjector extends ByteToMessageDecoder {
this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL"); this.networkManager = Preconditions.checkNotNull(networkManager, "networkMananger cannot be NULL");
this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL"); this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL");
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL"); this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
// Get the channel field // Get the channel field
this.channelField = new VolatileField( this.channelField = new VolatileField(
FuzzyReflection.fromObject(networkManager, true). 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. * Construct or retrieve a channel injector from an existing Bukkit player.
* @param player - the 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. * @return A new injector, or an existing injector associated with this player.
*/ */
public static ChannelInjector fromPlayer(Player player, ChannelListener listener) { 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. * Construct a new channel injector for the given channel.
* @param channel - the channel. * @param channel - the channel.
* @param playerFactory - a temporary player creator. * @param playerFactory - a temporary player creator.
* @param channelListener - the listener.
* @param loader - the current (plugin) class loader.
* @return The channel injector. * @return The channel injector.
*/ */
public static ChannelInjector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) { 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 // Get the vanilla decoder, so we don't have to replicate the work
vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder"); vanillaDecoder = (ByteToMessageDecoder) originalChannel.pipeline().get("decoder");
vanillaEncoder = (MessageToByteEncoder<Object>) originalChannel.pipeline().get("encoder"); 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); "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); "encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
protocolEncoder = new MessageToByteEncoder<Object>() { protocolEncoder = new MessageToByteEncoder<Object>() {
@Override @Override
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
NetworkMarker marker = getMarker(output); try {
PacketEvent event = markerEvent.remove(marker); 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);
for (PacketOutputHandler handler : marker.getOutputHandlers()) { if (event != null && NetworkMarker.hasOutputHandlers(marker)) {
handler.handle(event, data); 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 } catch (Exception e) {
output.writeBytes(data); 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().addBefore("decoder", "protocol_lib_decoder", this);
originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder); originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder);
// Intercept all write methods // Intercept all write methods
channelField.setValue(new ChannelProxy() { channelField.setValue(new ChannelProxy(originalChannel) {
@Override @Override
protected Object onMessageWritten(Object message) { protected Object onMessageWritten(Object message) {
return channelListener.onPacketSending(ChannelInjector.this, message, packetMarker.get(message)); return channelListener.onPacketSending(ChannelInjector.this, message, packetMarker.get(message));
} }
}.asChannel(originalChannel)); });
injected = true; injected = true;
return 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. * Close the current injector.
*/ */
@ -251,34 +306,54 @@ class ChannelInjector extends ByteToMessageDecoder {
closed = true; closed = true;
if (injected) { if (injected) {
originalChannel.pipeline().remove(this);
originalChannel.pipeline().remove(protocolEncoder);
channelField.revertValue(); channelField.revertValue();
try {
originalChannel.pipeline().remove(this);
originalChannel.pipeline().remove(protocolEncoder);
} catch (NoSuchElementException e) {
// Ignore it - the player has logged out
}
} }
} }
} }
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List<Object> packets) throws Exception {
byteBuffer.markReaderIndex(); try {
decodeBuffer.invoke(vanillaDecoder, ctx, byteBuffer, packets); byteBuffer.markReaderIndex();
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
if (packets.size() > 0) {
Object input = packets.get(0);
int id = PacketRegistry.getPacketID(input.getClass());
NetworkMarker marker = null;
if (channelListener.includeBuffer(id)) { if (packets.size() > 0) {
byteBuffer.resetReaderIndex(); Object input = packets.get(0);
marker = new NetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer)); 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); } catch (Exception e) {
channelListener.getReporter().reportDetailed(this,
// Handle packet changes Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
if (output == null) }
packets.clear(); }
else if (output != input)
packets.set(0, output); @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. * @return The current protocol.
*/ */
public Protocol getCurrentProtocol() { public Protocol getCurrentProtocol() {
if (protocolAccessor == null) { if (PROTOCOL_ACCESSOR == null) {
protocolAccessor = FuzzyReflection.getFieldAccessor( PROTOCOL_ACCESSOR = FuzzyReflection.getFieldAccessor(
networkManager.getClass(), MinecraftReflection.getEnumProtocolClass(), true); 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; package com.comphenix.protocol.injector.netty;
import java.lang.reflect.Method; import java.lang.reflect.Constructor;
import java.util.List; import java.net.SocketAddress;
import java.util.Set;
import com.comphenix.protocol.reflect.FuzzyReflection; import net.minecraft.util.io.netty.buffer.ByteBufAllocator;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import net.minecraft.util.com.google.common.collect.Sets;
import net.minecraft.util.io.netty.channel.Channel; import net.minecraft.util.io.netty.channel.Channel;
import net.sf.cglib.proxy.Enhancer; import net.minecraft.util.io.netty.channel.ChannelConfig;
import net.sf.cglib.proxy.MethodInterceptor; import net.minecraft.util.io.netty.channel.ChannelFuture;
import net.sf.cglib.proxy.MethodProxy; 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 { abstract class ChannelProxy implements Channel {
private static Set<Method> WRITE_METHODS; private static Constructor<? extends ChannelFuture> FUTURE_CONSTRUCTOR;
/** // The underlying channel
* Retrieve the channel proxy object. private Channel delegate;
* @param proxyInstance - the proxy instance object.
* @return The channel proxy. public ChannelProxy(Channel delegate) {
*/ this.delegate = delegate;
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();
} }
/** /**
* Invoked when a packet is being transmitted. * Invoked when a packet is being transmitted.
* @param message - the packet to transmit. * @param message - the packet to transmit.
* @return The object to transmit. * @return The object to transmit.
*/ */
protected abstract Object onMessageWritten(Object message); 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.Packets;
import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.concurrency.IntegerSet;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer; 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.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Lists;
public class NettyProtocolInjector implements ChannelListener { public class NettyProtocolInjector implements ChannelListener {
private volatile boolean injected; private volatile boolean injected;
@ -41,7 +43,7 @@ public class NettyProtocolInjector implements ChannelListener {
// The temporary player factory // The temporary player factory
private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory(); private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory();
private VolatileField bootstrapField; private List<VolatileField> bootstrapFields = Lists.newArrayList();
// Different sending filters // Different sending filters
private IntegerSet queuedFilters = new IntegerSet(Packets.PACKET_COUNT); private IntegerSet queuedFilters = new IntegerSet(Packets.PACKET_COUNT);
@ -49,11 +51,14 @@ public class NettyProtocolInjector implements ChannelListener {
// Which packets are buffered // Which packets are buffered
private Set<Integer> bufferedPackets; private Set<Integer> bufferedPackets;
private ListenerInvoker invoker; private ListenerInvoker invoker;
public NettyProtocolInjector(ListenerInvoker invoker) { // Handle errors
private ErrorReporter reporter;
public NettyProtocolInjector(ListenerInvoker invoker, ErrorReporter reporter) {
this.invoker = invoker; this.invoker = invoker;
this.reporter = reporter;
} }
/** /**
@ -89,7 +94,7 @@ public class NettyProtocolInjector implements ChannelListener {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg; Channel channel = (Channel) msg;
// Execute the other handlers before adding our own // Execute the other handlers before adding our own
ctx.fireChannelRead(msg); ctx.fireChannelRead(msg);
channel.pipeline().addLast(initProtocol); channel.pipeline().addLast(initProtocol);
@ -97,11 +102,14 @@ public class NettyProtocolInjector implements ChannelListener {
}; };
// Insert ProtocolLib's connection interceptor // Insert ProtocolLib's connection interceptor
bootstrapField = getBootstrapField(serverConnection); bootstrapFields = getBootstrapFields(serverConnection);
bootstrapField.setValue(new BootstrapList(
(List<ChannelFuture>) bootstrapField.getValue(), connectionHandler
));
for (VolatileField field : bootstrapFields) {
field.setValue(new BootstrapList(
(List<Object>) field.getValue(), connectionHandler
));
}
injected = true; injected = true;
} catch (Exception e) { } 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. * Inject our packet handling into a specific player.
* @param player * @param player
@ -117,23 +130,20 @@ public class NettyProtocolInjector implements ChannelListener {
ChannelInjector.fromPlayer(player, this).inject(); ChannelInjector.fromPlayer(player, this).inject();
} }
private VolatileField getBootstrapField(Object serverConnection) { private List<VolatileField> getBootstrapFields(Object serverConnection) {
VolatileField firstVolatile = null; List<VolatileField> result = Lists.newArrayList();
// Find and (possibly) proxy every list
for (Field field : FuzzyReflection.fromObject(serverConnection, true).getFieldListByType(List.class)) { 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") @SuppressWarnings("unchecked")
List<Object> list = (List<Object>) currentVolatile.getValue(); List<Object> list = (List<Object>) volatileField.getValue();
// Also save the first list if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
if (firstVolatile == null) { result.add(volatileField);
firstVolatile = currentVolatile;
}
if (list.size() > 0 && list.get(0) instanceof ChannelFuture) {
return currentVolatile;
} }
} }
return firstVolatile; return result;
} }
/** /**
@ -142,22 +152,21 @@ public class NettyProtocolInjector implements ChannelListener {
public synchronized void close() { public synchronized void close() {
if (!closed) { if (!closed) {
closed = true; closed = true;
@SuppressWarnings("unchecked") for (VolatileField field : bootstrapFields) {
List<Object> bootstraps = (List<Object>) bootstrapField.getValue(); Object value = field.getValue();
// Remember to close all the bootstraps // Undo the processed channels, if any
for (Object value : bootstraps) {
if (value instanceof BootstrapList) { if (value instanceof BootstrapList) {
((BootstrapList) value).close(); ((BootstrapList) value).close();
} }
field.revertValue();
} }
// Uninject all the players // Uninject all the players
for (Player player : Bukkit.getServer().getOnlinePlayers()) { for (Player player : Bukkit.getServer().getOnlinePlayers()) {
ChannelInjector.fromPlayer(player, this).close(); ChannelInjector.fromPlayer(player, this).close();
} }
bootstrapField.revertValue();
} }
} }
@ -261,12 +270,14 @@ public class NettyProtocolInjector implements ChannelListener {
@Override @Override
public void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException { 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 @Override
public void recieveClientPacket(Player player, Object mcPacket) throws IllegalAccessException, InvocationTargetException { 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 @Override
@ -296,7 +307,8 @@ public class NettyProtocolInjector implements ChannelListener {
@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 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); return packetReceived(packet, client, marker);
} }

View File

@ -30,23 +30,17 @@ import net.sf.cglib.proxy.NoOp;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; 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.reflect.FieldAccessException;
import com.comphenix.protocol.utility.ChatExtensions;
/** /**
* Create fake player instances that represents pre-authenticated clients. * Create fake player instances that represents pre-authenticated clients.
*/ */
public class TemporaryPlayerFactory { public class TemporaryPlayerFactory {
// Helpful constructors
private final PacketConstructor chatPacket;
// Prevent too many class creations // Prevent too many class creations
private static CallbackFilter callbackFilter; 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. * Retrieve the injector from a given player if it contains one.
* @param player - the player that may contain a reference to a player injector. * @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. * @throws FieldAccessException If we were unable to construct the message packet.
*/ */
private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { 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; return null;
} }
} }

View File

@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -29,9 +30,11 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import net.minecraft.util.com.google.common.base.Joiner;
import net.minecraft.util.com.google.common.collect.Sets; import net.minecraft.util.com.google.common.collect.Sets;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; 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.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -135,10 +138,22 @@ public class FuzzyReflection {
*/ */
public static FieldAccessor getFieldAccessor(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) { public static FieldAccessor getFieldAccessor(Class<?> instanceClass, Class<?> fieldClass, boolean forceAccess) {
// Get a field accessor // 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); 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. * Retrieve a field accessor from a given field that uses unchecked exceptions.
* @param field - the field. * @param field - the field.
@ -186,9 +201,30 @@ public class FuzzyReflection {
* @return The method accessor. * @return The method accessor.
*/ */
public static MethodAccessor getMethodAccessor(Class<?> instanceClass, String name, Class<?>... parameters) { public static MethodAccessor getMethodAccessor(Class<?> instanceClass, String name, Class<?>... parameters) {
Method method = MethodUtils.getAccessibleMethod(instanceClass, name, parameters); return getMethodAccessor(instanceClass, instanceClass, name, parameters);
method.setAccessible(true); }
return getMethodAccessor(method);
// 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; Field field = null;
try { try {
method = getMethodByParameters("getInstance", source.getClass(), new Class<?>[0]); method = getMethod(
FuzzyMethodContract.newBuilder().
parameterCount(0).
returnDerivedOf(source).
requireModifier(Modifier.STATIC).
build()
);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// Try getting the field instead // Try getting the field instead
// Note that this will throw an exception if not found // Note that this will throw an exception if not found

View File

@ -122,7 +122,6 @@ public class VolatileField {
* @param newValue - new field value. * @param newValue - new field value.
*/ */
public void setValue(Object newValue) { public void setValue(Object newValue) {
// Remember to safe the previous value // Remember to safe the previous value
ensureLoaded(); ensureLoaded();
@ -132,7 +131,20 @@ public class VolatileField {
currentSet = true; currentSet = true;
} catch (IllegalAccessException e) { } 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.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.List; import java.util.List;
@ -27,12 +26,14 @@ import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.PacketConstructor; import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FuzzyReflection; 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.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.google.common.base.Strings; import com.google.common.base.Strings;
@ -45,12 +46,14 @@ import com.google.common.collect.Iterables;
*/ */
public class ChatExtensions { public class ChatExtensions {
// Used to sent chat messages // Used to sent chat messages
private PacketConstructor chatConstructor;
private ProtocolManager manager; 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 // Whether or not we have to use the post-1.6.1 chat format
private static Constructor<?> jsonConstructor = getJsonFormatConstructor(); private static volatile Constructor<?> jsonConstructor = getJsonFormatConstructor();
private static Method messageFactory; private static volatile MethodAccessor messageFactory;
public ChatExtensions(ProtocolManager manager) { public ChatExtensions(ProtocolManager manager) {
this.manager = manager; this.manager = manager;
@ -83,63 +86,64 @@ public class ChatExtensions {
* @throws InvocationTargetException If we were unable to send the message. * @throws InvocationTargetException If we were unable to send the message.
*/ */
private void sendMessageSilently(Player player, String message) throws InvocationTargetException { private void sendMessageSilently(Player player, String message) throws InvocationTargetException {
if (jsonConstructor != null) { try {
sendMessageAsJson(player, message); for (PacketContainer packet : createChatPackets(message)) {
} else { manager.sendServerPacket(player, packet, false);
sendMessageAsString(player, message); }
} catch (FieldAccessException e) {
throw new InvocationTargetException(e);
} }
} }
/** /**
* Send a message using the new JSON format in 1.6.1. * Construct chat packet to send in order to display a given message.
* @param player - the player to send it to.
* @param message - the message to send. * @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 { public static PacketContainer[] createChatPackets(String message) {
Object messageObject = null; 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) { } else {
Class<?> messageClass = jsonConstructor.getParameterTypes()[0]; if (chatConstructor == null) {
chatConstructor = manager.createPacketConstructor(Packets.Server.CHAT, messageClass); chatConstructor = PacketConstructor.DEFAULT.withPacket(PacketType.Play.Server.CHAT, new Object[] { message });
}
// Try one of the string constructors // Minecraft 1.6.0 and earlier
messageFactory = FuzzyReflection.fromClass(messageClass).getMethod( return new PacketContainer[] { chatConstructor.createPacket(message) };
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);
} }
} }
@ -210,7 +214,7 @@ public class ChatExtensions {
* @return A constructor for JSON-based packets. * @return A constructor for JSON-based packets.
*/ */
static Constructor<?> getJsonFormatConstructor() { 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( List<Constructor<?>> list = FuzzyReflection.fromClass(chatPacket).getConstructorList(
FuzzyMethodContract.newBuilder(). FuzzyMethodContract.newBuilder().
parameterCount(1). parameterCount(1).

View File

@ -1,14 +1,10 @@
package com.comphenix.protocol.utility; package com.comphenix.protocol.utility;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List; import java.util.List;
import java.util.Map; 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.ByteBuf;
import net.minecraft.util.io.netty.buffer.UnpooledByteBufAllocator; import net.minecraft.util.io.netty.buffer.UnpooledByteBufAllocator;
import net.minecraft.util.io.netty.channel.ChannelHandlerContext; import net.minecraft.util.io.netty.channel.ChannelHandlerContext;

View File

@ -1366,6 +1366,14 @@ public class MinecraftReflection {
public static Class<?> getCraftEntityClass() { public static Class<?> getCraftEntityClass() {
return getCraftBukkitClass("entity.CraftEntity"); 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. * Retrieve a CraftItemStack from a given ItemStack.

View File

@ -7,7 +7,6 @@ import java.io.DataInputStream;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import org.bukkit.inventory.ItemStack; 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.Block;
import net.minecraft.server.v1_7_R1.Item; import net.minecraft.server.v1_7_R1.Item;
import net.minecraft.server.v1_7_R1.RegistryMaterials;
import net.minecraft.server.v1_7_R1.StatisticList; import net.minecraft.server.v1_7_R1.StatisticList;
// Will have to be updated for every version though // Will have to be updated for every version though