mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-28 05:35:28 +01:00
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:
parent
7b813fa4e6
commit
6842166b94
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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).
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user