package de.take_weiland.mods.commons.internal;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.common.network.IPacketHandler;
import cpw.mods.fml.common.network.Player;
import cpw.mods.fml.relauncher.Side;
import de.take_weiland.mods.commons.internal.transformers.ModPacketCstrAdder;
import de.take_weiland.mods.commons.net.*;
import de.take_weiland.mods.commons.util.SCReflector;
import de.take_weiland.mods.commons.util.Sides;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.INetworkManager;
import net.minecraft.network.packet.Packet;
import net.minecraft.network.packet.Packet250CustomPayload;
import org.apache.commons.lang3.ArrayUtils;
import sun.reflect.ConstructorAccessor;
import sun.reflect.ReflectionFactory;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.lang.invoke.MethodHandles.publicLookup;
import static java.lang.invoke.MethodType.methodType;
public final class FMLPacketHandlerImpl implements IPacketHandler, PacketHandler {
public static final int STREAMS_INITIAL_CAP = 64;
private final String channel;
private final Logger logger;
private static ImmutableMap<Class<? extends ModPacket>, ModPacketInfo> packetInfo = ImmutableMap.of();
private final Map<Integer, MethodHandle> packetCstrs;
private static final InstantiationStrategy strategy;
static {
// transformer checks if sun.reflect.ReflectionFactory is available
// if that is the case (most of the time, really), we don't need to transform the packet class
if (ModPacketCstrAdder.isNeeded) {
strategy = new TransformerStrategy();
} else {
try {
strategy = (InstantiationStrategy) Class.forName("de.take_weiland.mods.commons.internal.FMLPacketHandlerImpl$ReflectionFactStrategy").newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
throw new RuntimeException(e); // impossible
}
}
}
public FMLPacketHandlerImpl(String channel, Map<Class<? extends ModPacket>, Integer> packets) {
this.channel = channel;
this.packetCstrs = buildConstructors(packets);
updatePacketInfo(packets);
String logChannel = "SCNet|" + channel;
FMLLog.makeLog(logChannel);
logger = Logger.getLogger(logChannel);
}
private Map<Integer, MethodHandle> buildConstructors(Map<Class<? extends ModPacket>, Integer> packets) {
ImmutableMap.Builder<Integer, MethodHandle> builder = ImmutableMap.builder();
for (Map.Entry<Class<? extends ModPacket>, Integer> entry : packets.entrySet()) {
builder.put(entry.getValue(), strategy.makeNoArgCstr(entry.getKey()));
}
return builder.build();
}
private void updatePacketInfo(Map<Class<? extends ModPacket>, Integer> newPackets) {
synchronized (FMLPacketHandlerImpl.class) {
ImmutableMap.Builder<Class<? extends ModPacket>, ModPacketInfo> builder = ImmutableMap.builder();
builder.putAll(packetInfo);
for (Map.Entry<Class<? extends ModPacket>, Integer> entry : newPackets.entrySet()) {
Class<? extends ModPacket> clazz = entry.getKey();
Integer id = entry.getValue();
builder.put(clazz, new ModPacketInfo(clazz, this, id));
}
packetInfo = builder.build();
}
}
@Override
public void onPacketData(INetworkManager manager, Packet250CustomPayload packet, Player fmlPlayer) {
MCDataInput in = Network.newDataInput(packet.data, 0, packet.length);
EntityPlayer player = (EntityPlayer) fmlPlayer;
int id = in.readVarInt();
ModPacket modPacket = newPacket(id);
handlePacket(in, player, modPacket, packetInfo.get(modPacket.getClass()));
}
final void handlePacket(MCDataInput in, EntityPlayer player) {
ModPacket packet = newPacket(in.readVarInt());
handlePacket(in, player, packet, packetInfo.get(packet.getClass()));
}
static void handlePacket(MCDataInput in, EntityPlayer player, ModPacket modPacket, ModPacketInfo info) {
Side side = Sides.logical(player);
try {
if (!info.isValidTarget(side)) {
throw new ProtocolException("Packet received on wrong Side!");
}
modPacket.read(in, player, side);
modPacket.execute(player, side);
} catch (ProtocolException pe) {
String kickMsg = pe.getKickMessage();
if (kickMsg != null && side.isServer()) {
((EntityPlayerMP) player).playerNetServerHandler.kickPlayerFromServer(kickMsg);
}
info.handler.logException(modPacket, pe, player);
} catch (IOException e) {
info.handler.logException(modPacket, e, player);
}
}
public static Packet makePacketForModPacket(ModPacket packet) {
ModPacketInfo info = packetInfo.get(packet.getClass());
return info.handler.buildPacket(packet, info);
}
private Packet buildPacket(ModPacket mp, ModPacketInfo info) {
MCDataOutput out = Network.newDataOutput(mp.expectedSize() + 1); // packetID should rarely take more than one byte (more than 127)
out.writeVarInt(info.packetID);
mp.write(out);
out.lock();
return new Packet250Fake(mp, info, channel, out.backingArray(), out.length());
}
@Override
public MCDataOutput createStream(int packetId) {
return createStream(packetId, STREAMS_INITIAL_CAP);
}
@Override
public MCDataOutput createStream(int packetId, int initialCapacity) {
MCDataOutput stream = Network.newDataOutput(initialCapacity + 1);
stream.writeVarInt(packetId);
return stream;
}
@Override
public SimplePacket makePacket(MCDataOutput stream) {
stream.lock();
return new Packet250FakeNoMP(this, channel, stream.backingArray(), stream.length());
}
private void logException(ModPacket packet, Exception e, EntityPlayer player) {
logger.log(Level.WARNING, String.format("Unhandled %s during Packet read of Packet %s for player %s", e.getClass().getSimpleName(), packet.getClass().getSimpleName(), player.username), e);
}
private ModPacket newPacket(int id) {
try {
return (ModPacket) packetCstrs.get(id).invokeExact();
} catch (Throwable e) {
throw Throwables.propagate(e);
}
}
static {
Map<Class<? extends Packet>, Integer> classToIdMap = SCReflector.instance.getClassToIdMap();
classToIdMap.put(Packet250Fake.class, 250);
classToIdMap.put(Packet250FakeNoMP.class, 250);
}
private static abstract class InstantiationStrategy {
abstract MethodHandle makeNoArgCstr(Class<? extends ModPacket> clazz);
}
static final class ReflectionFactStrategy extends InstantiationStrategy {
private static final MethodHandle cstrAccNewInstance;
private static final Constructor<?> modPacketCstr;
static {
try {
MethodHandle rawNewInst = publicLookup().findVirtual(ConstructorAccessor.class, "newInstance", methodType(Object.class, Object[].class));
cstrAccNewInstance = MethodHandles.insertArguments(rawNewInst, 1, (Object) ArrayUtils.EMPTY_OBJECT_ARRAY);
modPacketCstr = ModPacket.class.getConstructor();
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
MethodHandle makeNoArgCstr(Class<? extends ModPacket> clazz) {
MethodHandle result;
// go up the hierarchy until we find a no-arg constructor
// then generate a fake constructor that invokes that no-arg constructor
// but creates an instance of the desired class
Class<?> test = clazz;
while (true) {
try {
Constructor<?> cstr = test.getDeclaredConstructor();
cstr.setAccessible(true);
if (test == clazz) {
result = publicLookup().unreflectConstructor(cstr);
} else {
ReflectionFactory factory = ReflectionFactory.getReflectionFactory();
ConstructorAccessor ca = factory.getConstructorAccessor(factory.newConstructorForSerialization(clazz, cstr));
result = cstrAccNewInstance.bindTo(ca);
}
break;
} catch (NoSuchMethodException e) {
if (test == ModPacket.class) {
throw new AssertionError("missing constructor in ModPacket.class");
}
test = test.getSuperclass();
} catch (IllegalAccessException e) {
throw new AssertionError(e); // impossible
}
}
return result.asType(methodType(ModPacket.class));
}
}
static final class TransformerStrategy extends InstantiationStrategy {
@Override
MethodHandle makeNoArgCstr(Class<? extends ModPacket> clazz) {
try {
return publicLookup().findConstructor(clazz, methodType(void.class))
.asType(methodType(ModPacket.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException("Packet transformer failed on " + clazz.getName());
}
}
}
static final class ModPacketInfo {
private final PacketDirection.Dir direction;
final int packetID;
final FMLPacketHandlerImpl handler;
ModPacketInfo(Class<?> clazz, FMLPacketHandlerImpl handler, int packetID) {
PacketDirection dir = clazz.getAnnotation(PacketDirection.class);
direction = dir == null ? PacketDirection.Dir.BOTH_WAYS : dir.value();
this.packetID = packetID;
this.handler = handler;
}
boolean isValidTarget(Side side) {
Side allowed = direction.validTarget;
return allowed == null || side == allowed;
}
}
}