mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2025-02-26 01:12:29 +01:00
Attempt to fix memory leaks with the ChannelInjector
Addresses https://github.com/aadnk/ProtocolLib/issues/70
This commit is contained in:
parent
592874fd5e
commit
ca2bc3ecc5
@ -61,16 +61,16 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
|
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
|
||||||
public static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread.");
|
public static final ReportType REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD = new ReportType("Cannot execute code in channel thread.");
|
||||||
public static final ReportType REPORT_CANNOT_FIND_GET_VERSION = new ReportType("Cannot find getVersion() in NetworkMananger");
|
public static final ReportType REPORT_CANNOT_FIND_GET_VERSION = new ReportType("Cannot find getVersion() in NetworkMananger");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that a packet has bypassed packet listeners.
|
* Indicates that a packet has bypassed packet listeners.
|
||||||
*/
|
*/
|
||||||
private static final PacketEvent BYPASSED_PACKET = new PacketEvent(ChannelInjector.class);
|
private static final PacketEvent BYPASSED_PACKET = new PacketEvent(ChannelInjector.class);
|
||||||
|
|
||||||
// The login packet
|
// The login packet
|
||||||
private static Class<?> PACKET_LOGIN_CLIENT = null;
|
private static Class<?> PACKET_LOGIN_CLIENT = null;
|
||||||
private static FieldAccessor LOGIN_GAME_PROFILE = null;
|
private static FieldAccessor LOGIN_GAME_PROFILE = null;
|
||||||
|
|
||||||
// Saved accessors
|
// Saved accessors
|
||||||
private static MethodAccessor DECODE_BUFFER;
|
private static MethodAccessor DECODE_BUFFER;
|
||||||
private static MethodAccessor ENCODE_BUFFER;
|
private static MethodAccessor ENCODE_BUFFER;
|
||||||
@ -78,17 +78,17 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
|
|
||||||
// For retrieving the protocol
|
// For retrieving the protocol
|
||||||
private static FieldAccessor PROTOCOL_ACCESSOR;
|
private static FieldAccessor PROTOCOL_ACCESSOR;
|
||||||
|
|
||||||
// The factory that created this injector
|
// The factory that created this injector
|
||||||
private InjectionFactory factory;
|
private InjectionFactory factory;
|
||||||
|
|
||||||
// The player, or temporary player
|
// The player, or temporary player
|
||||||
private Player player;
|
private Player player;
|
||||||
private Player updated;
|
private Player updated;
|
||||||
|
|
||||||
// The player connection
|
// The player connection
|
||||||
private Object playerConnection;
|
private Object playerConnection;
|
||||||
|
|
||||||
// The current network manager and channel
|
// The current network manager and channel
|
||||||
private final Object networkManager;
|
private final Object networkManager;
|
||||||
private final Channel originalChannel;
|
private final Channel originalChannel;
|
||||||
@ -96,32 +96,33 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
|
|
||||||
// Known network markers
|
// Known network markers
|
||||||
private ConcurrentMap<Object, NetworkMarker> packetMarker = new MapMaker().weakKeys().makeMap();
|
private ConcurrentMap<Object, NetworkMarker> packetMarker = new MapMaker().weakKeys().makeMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicate that this packet has been processed by event listeners.
|
* Indicate that this packet has been processed by event listeners.
|
||||||
* <p>
|
* <p>
|
||||||
* This must never be set outside the channel pipeline's thread.
|
* This must never be set outside the channel pipeline's thread.
|
||||||
*/
|
*/
|
||||||
private PacketEvent currentEvent;
|
private PacketEvent currentEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A packet event that should be processed by the write method.
|
* A packet event that should be processed by the write method.
|
||||||
*/
|
*/
|
||||||
private PacketEvent finalEvent;
|
private PacketEvent finalEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flag set by the main thread to indiciate that a packet should not be processed.
|
* A flag set by the main thread to indiciate that a packet should not be processed.
|
||||||
*/
|
*/
|
||||||
private final ThreadLocal<Boolean> scheduleProcessPackets = new ThreadLocal<Boolean>() {
|
private final ThreadLocal<Boolean> scheduleProcessPackets = new ThreadLocal<Boolean>() {
|
||||||
|
@Override
|
||||||
protected Boolean initialValue() {
|
protected Boolean initialValue() {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Other handlers
|
// Other handlers
|
||||||
private ByteToMessageDecoder vanillaDecoder;
|
private ByteToMessageDecoder vanillaDecoder;
|
||||||
private MessageToByteEncoder<Object> vanillaEncoder;
|
private MessageToByteEncoder<Object> vanillaEncoder;
|
||||||
|
|
||||||
// Our extra handlers
|
// Our extra handlers
|
||||||
private MessageToByteEncoder<Object> protocolEncoder;
|
private MessageToByteEncoder<Object> protocolEncoder;
|
||||||
private ChannelInboundHandler finishHandler;
|
private ChannelInboundHandler finishHandler;
|
||||||
@ -129,14 +130,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
|
|
||||||
// The channel listener
|
// The channel listener
|
||||||
private ChannelListener channelListener;
|
private ChannelListener channelListener;
|
||||||
|
|
||||||
// Processing network markers
|
// Processing network markers
|
||||||
private NetworkProcessor processor;
|
private NetworkProcessor processor;
|
||||||
|
|
||||||
// Closed
|
// Closed
|
||||||
private boolean injected;
|
private boolean injected;
|
||||||
private boolean closed;
|
private boolean closed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new channel injector.
|
* Construct a new channel injector.
|
||||||
* @param player - the current player, or temporary player.
|
* @param player - the current player, or temporary player.
|
||||||
@ -152,14 +153,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
|
this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL");
|
||||||
this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL");
|
this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL");
|
||||||
this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter());
|
this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter());
|
||||||
|
|
||||||
// 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),
|
getFieldByType("channel", Channel.class),
|
||||||
networkManager, true);
|
networkManager, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the version of the current protocol.
|
* Get the version of the current protocol.
|
||||||
* @return The version.
|
* @return The version.
|
||||||
@ -168,7 +169,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
public int getProtocolVersion() {
|
public int getProtocolVersion() {
|
||||||
return MinecraftProtocolVersion.getCurrentVersion();
|
return MinecraftProtocolVersion.getCurrentVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public boolean inject() {
|
public boolean inject() {
|
||||||
@ -179,8 +180,8 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
return false;
|
return false;
|
||||||
if (!originalChannel.isActive())
|
if (!originalChannel.isActive())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Main thread? We should synchronize with the channel thread, otherwise we might see a
|
// Main thread? We should synchronize with the channel thread, otherwise we might see a
|
||||||
// pipeline with only some of the handlers removed
|
// pipeline with only some of the handlers removed
|
||||||
if (Bukkit.isPrimaryThread()) {
|
if (Bukkit.isPrimaryThread()) {
|
||||||
// Just like in the close() method, we'll avoid blocking the main thread
|
// Just like in the close() method, we'll avoid blocking the main thread
|
||||||
@ -192,43 +193,43 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
});
|
});
|
||||||
return false; // We don't know
|
return false; // We don't know
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't inject the same channel twice
|
// Don't inject the same channel twice
|
||||||
if (findChannelHandler(originalChannel, ChannelInjector.class) != null) {
|
if (findChannelHandler(originalChannel, ChannelInjector.class) != null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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");
|
||||||
|
|
||||||
if (vanillaDecoder == null)
|
if (vanillaDecoder == null)
|
||||||
throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline() );
|
throw new IllegalArgumentException("Unable to find vanilla decoder in " + originalChannel.pipeline() );
|
||||||
if (vanillaEncoder == null)
|
if (vanillaEncoder == null)
|
||||||
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline() );
|
throw new IllegalArgumentException("Unable to find vanilla encoder in " + originalChannel.pipeline() );
|
||||||
patchEncoder(vanillaEncoder);
|
patchEncoder(vanillaEncoder);
|
||||||
|
|
||||||
if (DECODE_BUFFER == null)
|
if (DECODE_BUFFER == null)
|
||||||
DECODE_BUFFER = Accessors.getMethodAccessor(vanillaDecoder.getClass(),
|
DECODE_BUFFER = Accessors.getMethodAccessor(vanillaDecoder.getClass(),
|
||||||
"decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
|
"decode", ChannelHandlerContext.class, ByteBuf.class, List.class);
|
||||||
if (ENCODE_BUFFER == null)
|
if (ENCODE_BUFFER == null)
|
||||||
ENCODE_BUFFER = Accessors.getMethodAccessor(vanillaEncoder.getClass(),
|
ENCODE_BUFFER = Accessors.getMethodAccessor(vanillaEncoder.getClass(),
|
||||||
"encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
|
"encode", ChannelHandlerContext.class, Object.class, ByteBuf.class);
|
||||||
|
|
||||||
// Intercept sent packets
|
// Intercept sent packets
|
||||||
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 {
|
||||||
ChannelInjector.this.encode(ctx, packet, output);
|
ChannelInjector.this.encode(ctx, packet, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
|
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
|
||||||
super.write(ctx, packet, promise);
|
super.write(ctx, packet, promise);
|
||||||
ChannelInjector.this.finalWrite(ctx, packet, promise);
|
ChannelInjector.this.finalWrite(ctx, packet, promise);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Intercept recieved packets
|
// Intercept recieved packets
|
||||||
finishHandler = new ChannelInboundHandlerAdapter() {
|
finishHandler = new ChannelInboundHandlerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
@ -238,27 +239,27 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
ChannelInjector.this.finishRead(ctx, msg);
|
ChannelInjector.this.finishRead(ctx, msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Insert our handlers - note that we effectively replace the vanilla encoder/decoder
|
// 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().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler);
|
originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler);
|
||||||
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(originalChannel, MinecraftReflection.getPacketClass()) {
|
channelField.setValue(new ChannelProxy(originalChannel, MinecraftReflection.getPacketClass()) {
|
||||||
@Override
|
@Override
|
||||||
protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) {
|
protected <T> Callable<T> onMessageScheduled(final Callable<T> callable, FieldAccessor packetAccessor) {
|
||||||
final PacketEvent event = handleScheduled(callable, packetAccessor);
|
final PacketEvent event = handleScheduled(callable, packetAccessor);
|
||||||
|
|
||||||
// Handle cancelled events
|
// Handle cancelled events
|
||||||
if (event != null && event.isCancelled())
|
if (event != null && event.isCancelled())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return new Callable<T>() {
|
return new Callable<T>() {
|
||||||
@Override
|
@Override
|
||||||
public T call() throws Exception {
|
public T call() throws Exception {
|
||||||
T result = null;
|
T result = null;
|
||||||
|
|
||||||
// This field must only be updated in the pipeline thread
|
// This field must only be updated in the pipeline thread
|
||||||
currentEvent = event;
|
currentEvent = event;
|
||||||
result = callable.call();
|
result = callable.call();
|
||||||
@ -267,11 +268,11 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) {
|
protected Runnable onMessageScheduled(final Runnable runnable, FieldAccessor packetAccessor) {
|
||||||
final PacketEvent event = handleScheduled(runnable, packetAccessor);
|
final PacketEvent event = handleScheduled(runnable, packetAccessor);
|
||||||
|
|
||||||
// Handle cancelled events
|
// Handle cancelled events
|
||||||
if (event != null && event.isCancelled())
|
if (event != null && event.isCancelled())
|
||||||
return null;
|
return null;
|
||||||
@ -285,15 +286,15 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
|
protected PacketEvent handleScheduled(Object instance, FieldAccessor accessor) {
|
||||||
// Let the filters handle this packet
|
// Let the filters handle this packet
|
||||||
Object original = accessor.get(instance);
|
Object original = accessor.get(instance);
|
||||||
|
|
||||||
// See if we've been instructed not to process packets
|
// See if we've been instructed not to process packets
|
||||||
if (!scheduleProcessPackets.get()) {
|
if (!scheduleProcessPackets.get()) {
|
||||||
NetworkMarker marker = getMarker(original);
|
NetworkMarker marker = getMarker(original);
|
||||||
|
|
||||||
if (marker != null) {
|
if (marker != null) {
|
||||||
PacketEvent result = new PacketEvent(ChannelInjector.class);
|
PacketEvent result = new PacketEvent(ChannelInjector.class);
|
||||||
result.setNetworkMarker(marker);
|
result.setNetworkMarker(marker);
|
||||||
@ -306,7 +307,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
|
|
||||||
if (event != null && !event.isCancelled()) {
|
if (event != null && !event.isCancelled()) {
|
||||||
Object changed = event.getPacket().getHandle();
|
Object changed = event.getPacket().getHandle();
|
||||||
|
|
||||||
// Change packet to be scheduled
|
// Change packet to be scheduled
|
||||||
if (original != changed)
|
if (original != changed)
|
||||||
accessor.set(instance, changed);
|
accessor.set(instance, changed);
|
||||||
@ -314,7 +315,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
return event != null ? event : BYPASSED_PACKET;
|
return event != null ? event : BYPASSED_PACKET;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
injected = true;
|
injected = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -328,7 +329,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
private PacketEvent processSending(Object message) {
|
private PacketEvent processSending(Object message) {
|
||||||
return channelListener.onPacketSending(ChannelInjector.this, message, getMarker(message));
|
return channelListener.onPacketSending(ChannelInjector.this, message, getMarker(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method patches the encoder so that it skips already created packets.
|
* This method patches the encoder so that it skips already created packets.
|
||||||
* @param encoder - the encoder to patch.
|
* @param encoder - the encoder to patch.
|
||||||
@ -339,17 +340,17 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
|
ENCODER_TYPE_MATCHER.set(encoder, TypeParameterMatcher.get(MinecraftReflection.getPacketClass()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||||
if (channelListener.isDebug())
|
if (channelListener.isDebug())
|
||||||
cause.printStackTrace();
|
cause.printStackTrace();
|
||||||
super.exceptionCaught(ctx, cause);
|
super.exceptionCaught(ctx, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a packet to a byte buffer, taking over for the standard Minecraft encoder.
|
* Encode a packet to a byte buffer, taking over for the standard Minecraft encoder.
|
||||||
* @param ctx - the current context.
|
* @param ctx - the current context.
|
||||||
* @param packet - the packet to encode to a byte array.
|
* @param packet - the packet to encode to a byte array.
|
||||||
* @param output - the output byte array.
|
* @param output - the output byte array.
|
||||||
* @throws Exception If anything went wrong.
|
* @throws Exception If anything went wrong.
|
||||||
@ -357,26 +358,26 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
|
protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception {
|
||||||
NetworkMarker marker = null;
|
NetworkMarker marker = null;
|
||||||
PacketEvent event = currentEvent;
|
PacketEvent event = currentEvent;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Skip every kind of non-filtered packet
|
// Skip every kind of non-filtered packet
|
||||||
if (!scheduleProcessPackets.get()) {
|
if (!scheduleProcessPackets.get()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This packet has not been seen by the main thread
|
// This packet has not been seen by the main thread
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
Class<?> clazz = packet.getClass();
|
Class<?> clazz = packet.getClass();
|
||||||
|
|
||||||
// Schedule the transmission on the main thread instead
|
// Schedule the transmission on the main thread instead
|
||||||
if (channelListener.hasMainThreadListener(clazz)) {
|
if (channelListener.hasMainThreadListener(clazz)) {
|
||||||
// Delay the packet
|
// Delay the packet
|
||||||
scheduleMainThread(packet);
|
scheduleMainThread(packet);
|
||||||
packet = null;
|
packet = null;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
event = processSending(packet);
|
event = processSending(packet);
|
||||||
|
|
||||||
// Handle the output
|
// Handle the output
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
packet = !event.isCancelled() ? event.getPacket().getHandle() : null;
|
packet = !event.isCancelled() ? event.getPacket().getHandle() : null;
|
||||||
@ -385,9 +386,9 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
// Retrieve marker without accidentally constructing it
|
// Retrieve marker without accidentally constructing it
|
||||||
marker = NetworkMarker.getNetworkMarker(event);
|
marker = NetworkMarker.getNetworkMarker(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process output handler
|
// Process output handler
|
||||||
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
|
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
|
||||||
ByteBuf packetBuffer = ctx.alloc().buffer();
|
ByteBuf packetBuffer = ctx.alloc().buffer();
|
||||||
@ -395,17 +396,17 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
|
|
||||||
// Let each handler prepare the actual output
|
// Let each handler prepare the actual output
|
||||||
byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer));
|
byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer));
|
||||||
|
|
||||||
// Write the result
|
// Write the result
|
||||||
output.writeBytes(data);
|
output.writeBytes(data);
|
||||||
packet = null;
|
packet = null;
|
||||||
|
|
||||||
// Sent listeners?
|
// Sent listeners?
|
||||||
finalEvent = event;
|
finalEvent = event;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
channelListener.getReporter().reportDetailed(this,
|
channelListener.getReporter().reportDetailed(this,
|
||||||
Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build());
|
Report.newBuilder(REPORT_CANNOT_INTERCEPT_SERVER_PACKET).callerParam(packet).error(e).build());
|
||||||
} finally {
|
} finally {
|
||||||
// Attempt to handle the packet nevertheless
|
// Attempt to handle the packet nevertheless
|
||||||
@ -424,12 +425,12 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
*/
|
*/
|
||||||
protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) {
|
protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) {
|
||||||
PacketEvent event = finalEvent;
|
PacketEvent event = finalEvent;
|
||||||
|
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
// Necessary to prevent infinite loops
|
// Necessary to prevent infinite loops
|
||||||
finalEvent = null;
|
finalEvent = null;
|
||||||
currentEvent = null;
|
currentEvent = null;
|
||||||
|
|
||||||
processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event));
|
processor.invokePostEvent(event, NetworkMarker.getNetworkMarker(event));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -443,30 +444,30 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@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();
|
byteBuffer.markReaderIndex();
|
||||||
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
|
DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Reset queue
|
// Reset queue
|
||||||
finishQueue.clear();
|
finishQueue.clear();
|
||||||
|
|
||||||
for (ListIterator<Object> it = packets.listIterator(); it.hasNext(); ) {
|
for (ListIterator<Object> it = packets.listIterator(); it.hasNext(); ) {
|
||||||
Object input = it.next();
|
Object input = it.next();
|
||||||
Class<?> packetClass = input.getClass();
|
Class<?> packetClass = input.getClass();
|
||||||
NetworkMarker marker = null;
|
NetworkMarker marker = null;
|
||||||
|
|
||||||
// Special case!
|
// Special case!
|
||||||
handleLogin(packetClass, input);
|
handleLogin(packetClass, input);
|
||||||
|
|
||||||
if (channelListener.includeBuffer(packetClass)) {
|
if (channelListener.includeBuffer(packetClass)) {
|
||||||
byteBuffer.resetReaderIndex();
|
byteBuffer.resetReaderIndex();
|
||||||
marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
|
marker = new NettyNetworkMarker(ConnectionSide.CLIENT_SIDE, getBytes(byteBuffer));
|
||||||
}
|
}
|
||||||
PacketEvent output = channelListener.onPacketReceiving(this, input, marker);
|
PacketEvent output = channelListener.onPacketReceiving(this, input, marker);
|
||||||
|
|
||||||
// Handle packet changes
|
// Handle packet changes
|
||||||
if (output != null) {
|
if (output != null) {
|
||||||
if (output.isCancelled()) {
|
if (output.isCancelled()) {
|
||||||
@ -475,16 +476,16 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
} else if (output.getPacket().getHandle() != input) {
|
} else if (output.getPacket().getHandle() != input) {
|
||||||
it.set(output.getPacket().getHandle());
|
it.set(output.getPacket().getHandle());
|
||||||
}
|
}
|
||||||
|
|
||||||
finishQueue.addLast(output);
|
finishQueue.addLast(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
channelListener.getReporter().reportDetailed(this,
|
channelListener.getReporter().reportDetailed(this,
|
||||||
Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
|
Report.newBuilder(REPORT_CANNOT_INTERCEPT_CLIENT_PACKET).callerParam(byteBuffer).error(e).build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked after our decoder.
|
* Invoked after our decoder.
|
||||||
* @param ctx - current context.
|
* @param ctx - current context.
|
||||||
@ -493,16 +494,16 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
protected void finishRead(ChannelHandlerContext ctx, Object msg) {
|
protected void finishRead(ChannelHandlerContext ctx, Object msg) {
|
||||||
// Assume same order
|
// Assume same order
|
||||||
PacketEvent event = finishQueue.pollFirst();
|
PacketEvent event = finishQueue.pollFirst();
|
||||||
|
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
|
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
|
||||||
|
|
||||||
if (marker != null) {
|
if (marker != null) {
|
||||||
processor.invokePostEvent(event, marker);
|
processor.invokePostEvent(event, marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked when we may need to handle the login packet.
|
* Invoked when we may need to handle the login packet.
|
||||||
* @param packetClass - the packet class.
|
* @param packetClass - the packet class.
|
||||||
@ -511,7 +512,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
protected void handleLogin(Class<?> packetClass, Object packet) {
|
protected void handleLogin(Class<?> packetClass, Object packet) {
|
||||||
Class<?> loginClass = PACKET_LOGIN_CLIENT;
|
Class<?> loginClass = PACKET_LOGIN_CLIENT;
|
||||||
FieldAccessor loginClient = LOGIN_GAME_PROFILE;
|
FieldAccessor loginClient = LOGIN_GAME_PROFILE;
|
||||||
|
|
||||||
// Initialize packet class and login
|
// Initialize packet class and login
|
||||||
if (loginClass == null) {
|
if (loginClass == null) {
|
||||||
loginClass = PacketType.Login.Client.START.getPacketClass();
|
loginClass = PacketType.Login.Client.START.getPacketClass();
|
||||||
@ -521,26 +522,26 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, GameProfile.class, true);
|
loginClient = Accessors.getFieldAccessor(PACKET_LOGIN_CLIENT, GameProfile.class, true);
|
||||||
LOGIN_GAME_PROFILE = loginClient;
|
LOGIN_GAME_PROFILE = loginClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if we are dealing with the login packet
|
// See if we are dealing with the login packet
|
||||||
if (loginClass.equals(packetClass)) {
|
if (loginClass.equals(packetClass)) {
|
||||||
GameProfile profile = (GameProfile) loginClient.get(packet);
|
GameProfile profile = (GameProfile) loginClient.get(packet);
|
||||||
|
|
||||||
// Save the channel injector
|
// Save the channel injector
|
||||||
factory.cacheInjector(profile.getName(), this);
|
factory.cacheInjector(profile.getName(), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
super.channelActive(ctx);
|
super.channelActive(ctx);
|
||||||
|
|
||||||
// See NetworkManager.channelActive(ChannelHandlerContext) for why
|
// See NetworkManager.channelActive(ChannelHandlerContext) for why
|
||||||
if (channelField != null) {
|
if (channelField != null) {
|
||||||
channelField.refreshValue();
|
channelField.refreshValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve every byte in the given byte buffer.
|
* Retrieve every byte in the given byte buffer.
|
||||||
* @param buffer - the buffer.
|
* @param buffer - the buffer.
|
||||||
@ -548,11 +549,11 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
*/
|
*/
|
||||||
private byte[] getBytes(ByteBuf buffer){
|
private byte[] getBytes(ByteBuf buffer){
|
||||||
byte[] data = new byte[buffer.readableBytes()];
|
byte[] data = new byte[buffer.readableBytes()];
|
||||||
|
|
||||||
buffer.readBytes(data);
|
buffer.readBytes(data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnect the current player.
|
* Disconnect the current player.
|
||||||
* @param message - the disconnect message, if possible.
|
* @param message - the disconnect message, if possible.
|
||||||
@ -575,7 +576,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
@Override
|
@Override
|
||||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||||
saveMarker(packet, marker);
|
saveMarker(packet, marker);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
scheduleProcessPackets.set(filtered);
|
scheduleProcessPackets.set(filtered);
|
||||||
invokeSendPacket(packet);
|
invokeSendPacket(packet);
|
||||||
@ -583,7 +584,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
scheduleProcessPackets.set(true);
|
scheduleProcessPackets.set(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke the sendPacket method in Minecraft.
|
* Invoke the sendPacket method in Minecraft.
|
||||||
* @param packet - the packet to send.
|
* @param packet - the packet to send.
|
||||||
@ -600,11 +601,11 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
throw new RuntimeException("Unable to send server packet " + packet, e);
|
throw new RuntimeException("Unable to send server packet " + packet, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recieveClientPacket(final Object packet) {
|
public void recieveClientPacket(final Object packet) {
|
||||||
// TODO: Ensure the packet listeners are executed in the channel thread.
|
// TODO: Ensure the packet listeners are executed in the channel thread.
|
||||||
|
|
||||||
// Execute this in the channel thread
|
// Execute this in the channel thread
|
||||||
Runnable action = new Runnable() {
|
Runnable action = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -617,7 +618,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Execute in the worker thread
|
// Execute in the worker thread
|
||||||
if (originalChannel.eventLoop().inEventLoop()) {
|
if (originalChannel.eventLoop().inEventLoop()) {
|
||||||
action.run();
|
action.run();
|
||||||
@ -625,7 +626,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
originalChannel.eventLoop().execute(action);
|
originalChannel.eventLoop().execute(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Protocol getCurrentProtocol() {
|
public Protocol getCurrentProtocol() {
|
||||||
if (PROTOCOL_ACCESSOR == null) {
|
if (PROTOCOL_ACCESSOR == null) {
|
||||||
@ -634,7 +635,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager));
|
return Protocol.fromVanilla((Enum<?>) PROTOCOL_ACCESSOR.get(networkManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the player connection of the current player.
|
* Retrieve the player connection of the current player.
|
||||||
* @return The player connection.
|
* @return The player connection.
|
||||||
@ -645,45 +646,47 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
return playerConnection;
|
return playerConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NetworkMarker getMarker(Object packet) {
|
public NetworkMarker getMarker(Object packet) {
|
||||||
return packetMarker.get(packet);
|
return packetMarker.get(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveMarker(Object packet, NetworkMarker marker) {
|
public void saveMarker(Object packet, NetworkMarker marker) {
|
||||||
if (marker != null) {
|
if (marker != null) {
|
||||||
packetMarker.put(packet, marker);
|
packetMarker.put(packet, marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Player getPlayer() {
|
public Player getPlayer() {
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the player instance.
|
* Set the player instance.
|
||||||
* @param player - current instance.
|
* @param player - current instance.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void setPlayer(Player player) {
|
public void setPlayer(Player player) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the updated player instance.
|
* Set the updated player instance.
|
||||||
* @param updated - updated instance.
|
* @param updated - updated instance.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void setUpdatedPlayer(Player updated) {
|
public void setUpdatedPlayer(Player updated) {
|
||||||
this.updated = updated;
|
this.updated = updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInjected() {
|
public boolean isInjected() {
|
||||||
return injected;
|
return injected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if this channel has been closed and cleaned up.
|
* Determine if this channel has been closed and cleaned up.
|
||||||
* @return TRUE if it has, FALSE otherwise.
|
* @return TRUE if it has, FALSE otherwise.
|
||||||
@ -692,29 +695,29 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
public boolean isClosed() {
|
public boolean isClosed() {
|
||||||
return closed;
|
return closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
closed = true;
|
closed = true;
|
||||||
|
|
||||||
if (injected) {
|
if (injected) {
|
||||||
channelField.revertValue();
|
channelField.revertValue();
|
||||||
|
|
||||||
// Calling remove() in the main thread will block the main thread, which may lead
|
// Calling remove() in the main thread will block the main thread, which may lead
|
||||||
// to a deadlock:
|
// to a deadlock:
|
||||||
// http://pastebin.com/L3SBVKzp
|
// http://pastebin.com/L3SBVKzp
|
||||||
//
|
//
|
||||||
// ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread,
|
// ProtocolLib executes this close() method through a PlayerQuitEvent in the main thread,
|
||||||
// which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)).
|
// which has implicitly aquired a lock on SimplePluginManager (see SimplePluginManager.callEvent(Event)).
|
||||||
// Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if
|
// Unfortunately, the remove() method will schedule the removal on one of the Netty worker threads if
|
||||||
// it's called from a different thread, blocking until the removal has been confirmed.
|
// it's called from a different thread, blocking until the removal has been confirmed.
|
||||||
//
|
//
|
||||||
// This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same
|
// This is bad enough (Rule #1: Don't block the main thread), but the real trouble starts if the same
|
||||||
// worker thread happens to be handling a server ping connection when this removal task is scheduled.
|
// worker thread happens to be handling a server ping connection when this removal task is scheduled.
|
||||||
// In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener)
|
// In that case, it may attempt to invoke an asynchronous ServerPingEvent (see PacketStatusListener)
|
||||||
// using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread,
|
// using SimplePluginManager.callEvent(). But, since this has already been locked by the main thread,
|
||||||
// we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and
|
// we end up with a deadlock. The main thread is waiting for the worker thread to process the task, and
|
||||||
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
|
// the worker thread is waiting for the main thread to finish executing PlayerQuitEvent.
|
||||||
//
|
//
|
||||||
// TLDR: Concurrenty is hard.
|
// TLDR: Concurrenty is hard.
|
||||||
@ -730,10 +733,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Clear cache
|
// Clear cache
|
||||||
factory.invalidate(player);
|
factory.invalidate(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dmulloy2 - attempt to fix memory leakage
|
||||||
|
this.player = null;
|
||||||
|
this.updated = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -750,13 +757,13 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
try {
|
try {
|
||||||
command.run();
|
command.run();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ProtocolLibrary.getErrorReporter().reportDetailed(ChannelInjector.this,
|
ProtocolLibrary.getErrorReporter().reportDetailed(ChannelInjector.this,
|
||||||
Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build());
|
Report.newBuilder(REPORT_CANNOT_EXECUTE_IN_CHANNEL_THREAD).error(e).build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the first channel handler that is assignable to a given type.
|
* Find the first channel handler that is assignable to a given type.
|
||||||
* @param channel - the channel.
|
* @param channel - the channel.
|
||||||
@ -771,14 +778,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a socket injector that foreards to the current channel injector.
|
* Represents a socket injector that foreards to the current channel injector.
|
||||||
* @author Kristian
|
* @author Kristian
|
||||||
*/
|
*/
|
||||||
static class ChannelSocketInjector implements SocketInjector {
|
static class ChannelSocketInjector implements SocketInjector {
|
||||||
private final ChannelInjector injector;
|
private final ChannelInjector injector;
|
||||||
|
|
||||||
public ChannelSocketInjector(ChannelInjector injector) {
|
public ChannelSocketInjector(ChannelInjector injector) {
|
||||||
this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL");
|
this.injector = Preconditions.checkNotNull(injector, "injector cannot be NULL");
|
||||||
}
|
}
|
||||||
@ -822,7 +829,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector {
|
|||||||
public void setUpdatedPlayer(Player updatedPlayer) {
|
public void setUpdatedPlayer(Player updatedPlayer) {
|
||||||
injector.player = updatedPlayer;
|
injector.player = updatedPlayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelInjector getChannelInjector() {
|
public ChannelInjector getChannelInjector() {
|
||||||
return injector;
|
return injector;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user