package com.alecgorge.minecraft.jsonapi.packets.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
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 abstract class NettyInjector {
// The temporary player factory
private List<VolatileField> bootstrapFields = Lists.newArrayList();
// List of network managers
private volatile List<Object> networkManagers;
private boolean injected;
private boolean closed;
/**
* Inject into the spigot connection class.
*/
@SuppressWarnings("unchecked")
public synchronized void inject() {
if (injected)
throw new IllegalStateException("Cannot inject twice.");
try {
FuzzyReflection fuzzyServer = FuzzyReflection.fromClass(MinecraftReflection.getMinecraftServerClass());
Method serverConnectionMethod = fuzzyServer.getMethodByParameters("getServerConnection", MinecraftReflection.getServerConnectionClass(), new Class[] {});
// Get the server connection
Object server = fuzzyServer.getSingleton();
Object serverConnection = serverConnectionMethod.invoke(server);
// Handle connected channels
final ChannelInboundHandler endInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
try {
// This can take a while, so we need to stop the main thread from interfering
synchronized (networkManagers) {
injectChannel(channel);
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
// This is executed before Minecraft's channel handler
final ChannelInboundHandler beginInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
// Our only job is to add init protocol
channel.pipeline().addLast(endInitProtocol);
}
};
// Add our handler to newly created channels
final ChannelHandler connectionHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel channel = (Channel) msg;
// Prepare to initialize ths channel
channel.pipeline().addFirst(beginInitProtocol);
ctx.fireChannelRead(msg);
}
};
// Get the current NetworkMananger list
networkManagers = (List<Object>) FuzzyReflection.fromObject(serverConnection, true).
invokeMethod(null, "getNetworkManagers", List.class, serverConnection);
// Insert ProtocolLib's connection interceptor
bootstrapFields = getBootstrapFields(serverConnection);
for (VolatileField field : bootstrapFields) {
final List<Object> list = (List<Object>) field.getValue();
// We don't have to override this list
if (list == networkManagers) {
continue;
}
// Synchronize with each list before we attempt to replace them.
field.setValue(new BootstrapList(list, connectionHandler));
}
injected = true;
} catch (Exception e) {
throw new RuntimeException("Unable to inject channel futures.", e);
}
}
/**
* Invoked when a channel is ready to be injected.
* @param channel - the channel to inject.
*/
protected abstract void injectChannel(Channel channel);
/**
* Retrieve a list of every field with a list of channel futures.
* @param serverConnection - the connection.
* @return List of fields.
*/
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 volatileField = new VolatileField(field, serverConnection, true).toSynchronized();
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) volatileField.getValue();
if (list.size() == 0 || list.get(0) instanceof ChannelFuture) {
result.add(volatileField);
}
}
return result;
}
/**
* Clean up any remaning injections.
*/
public synchronized void close() {
if (!closed) {
closed = true;
for (VolatileField field : bootstrapFields) {
Object value = field.getValue();
// Undo the processed channels, if any
if (value instanceof BootstrapList) {
((BootstrapList) value).close();
}
field.revertValue();
}
}
}
}