    001package cpw.mods.fml.common.network;
    002
    003import static cpw.mods.fml.common.network.FMLPacket.Type.MOD_LIST_REQUEST;
    004
    005import java.io.IOException;
    006import java.net.InetAddress;
    007import java.net.NetworkInterface;
    008import java.net.SocketAddress;
    009import java.util.Collections;
    010import java.util.List;
    011import java.util.Map;
    012import java.util.Set;
    013
    014import net.minecraft.entity.Entity;
    015import net.minecraft.entity.player.EntityPlayer;
    016import net.minecraft.entity.player.EntityPlayerMP;
    017import net.minecraft.item.Item;
    018import net.minecraft.network.*;
    019import net.minecraft.network.packet.*;
    020import net.minecraft.server.MinecraftServer;
    021import net.minecraft.server.management.ServerConfigurationManager;
    022import net.minecraft.world.EnumGameType;
    023import net.minecraft.world.World;
    024import net.minecraft.world.WorldType;
    025
    026import com.google.common.collect.Lists;
    027import com.google.common.collect.Maps;
    028import com.google.common.hash.Hashing;
    029
    030import cpw.mods.fml.common.FMLCommonHandler;
    031import cpw.mods.fml.common.FMLLog;
    032import cpw.mods.fml.common.Loader;
    033import cpw.mods.fml.common.ModContainer;
    034import cpw.mods.fml.common.discovery.ASMDataTable;
    035import cpw.mods.fml.common.network.FMLPacket.Type;
    036import cpw.mods.fml.common.registry.EntityRegistry;
    037import cpw.mods.fml.common.registry.GameRegistry;
    038import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration;
    039
    040public class FMLNetworkHandler
    041{
    042    private static final int FML_HASH = Hashing.murmur3_32().hashString("FML").asInt();
    043    private static final int PROTOCOL_VERSION = 0x2;
    044    private static final FMLNetworkHandler INSTANCE = new FMLNetworkHandler();
    045
    046    // List of states for connections from clients to server
    047    static final int LOGIN_RECEIVED = 1;
    048    static final int CONNECTION_VALID = 2;
    049    static final int FML_OUT_OF_DATE = -1;
    050    static final int MISSING_MODS_OR_VERSIONS = -2;
    051
    052    private Map<NetLoginHandler, Integer> loginStates = Maps.newHashMap();
    053    private Map<ModContainer, NetworkModHandler> networkModHandlers = Maps.newHashMap();
    054
    055    private Map<Integer, NetworkModHandler> networkIdLookup = Maps.newHashMap();
    056
    057    public static void handlePacket250Packet(Packet250CustomPayload packet, INetworkManager network, NetHandler handler)
    058    {
    059        String target = packet.channel;
    060
    061        if (target.startsWith("MC|"))
    062        {
    063            handler.handleVanilla250Packet(packet);
    064        }
    065        if (target.equals("FML"))
    066        {
    067            instance().handleFMLPacket(packet, network, handler);
    068        }
    069        else
    070        {
    071            NetworkRegistry.instance().handleCustomPacket(packet, network, handler);
    072        }
    073    }
    074
    075    public static void onConnectionEstablishedToServer(NetHandler clientHandler, INetworkManager manager, Packet1Login login)
    076    {
    077        NetworkRegistry.instance().clientLoggedIn(clientHandler, manager, login);
    078    }
    079
    080    private void handleFMLPacket(Packet250CustomPayload packet, INetworkManager network, NetHandler netHandler)
    081    {
    082        FMLPacket pkt = FMLPacket.readPacket(network, packet.data);
    083        // Part of an incomplete multipart packet
    084        if (pkt == null)
    085        {
    086            return;
    087        }
    088        String userName = "";
    089        if (netHandler instanceof NetLoginHandler)
    090        {
    091            userName = ((NetLoginHandler) netHandler).clientUsername;
    092        }
    093        else
    094        {
    095            EntityPlayer pl = netHandler.getPlayer();
    096            if (pl != null)
    097            {
    098                userName = pl.getCommandSenderName();
    099            }
    100        }
    101
    102        pkt.execute(network, this, netHandler, userName);
    103    }
    104
    105    public static void onConnectionReceivedFromClient(NetLoginHandler netLoginHandler, MinecraftServer server, SocketAddress address, String userName)
    106    {
    107        instance().handleClientConnection(netLoginHandler, server, address, userName);
    108    }
    109
    110    private void handleClientConnection(NetLoginHandler netLoginHandler, MinecraftServer server, SocketAddress address, String userName)
    111    {
    112        if (!loginStates.containsKey(netLoginHandler))
    113        {
    114            if (handleVanillaLoginKick(netLoginHandler, server, address, userName))
    115            {
    116                // No FML on the client
    117                FMLLog.fine("Connection from %s rejected - no FML packet received from client", userName);
    118                netLoginHandler.completeConnection("You don't have FML installed, you cannot connect to this server");
    119                return;
    120            }
    121            else
    122            {
    123                // Vanilla kicked us for some reason - bye now!
    124                FMLLog.fine("Connection from %s was closed by vanilla minecraft", userName);
    125                return;
    126            }
    127
    128        }
    129        switch (loginStates.get(netLoginHandler))
    130        {
    131        case LOGIN_RECEIVED:
    132            // mods can try and kick undesireables here
    133            String modKick = NetworkRegistry.instance().connectionReceived(netLoginHandler, netLoginHandler.myTCPConnection);
    134            if (modKick != null)
    135            {
    136                netLoginHandler.completeConnection(modKick);
    137                loginStates.remove(netLoginHandler);
    138                return;
    139            }
    140            // The vanilla side wanted to kick
    141            if (!handleVanillaLoginKick(netLoginHandler, server, address, userName))
    142            {
    143                loginStates.remove(netLoginHandler);
    144                return;
    145            }
    146            // Reset the "connection completed" flag so processing can continue
    147            NetLoginHandler.func_72531_a(netLoginHandler, false);
    148            // Send the mod list request packet to the client from the server
    149            netLoginHandler.myTCPConnection.addToSendQueue(getModListRequestPacket());
    150            loginStates.put(netLoginHandler, CONNECTION_VALID);
    151            break;
    152        case CONNECTION_VALID:
    153            netLoginHandler.completeConnection(null);
    154            loginStates.remove(netLoginHandler);
    155            break;
    156        case MISSING_MODS_OR_VERSIONS:
    157            netLoginHandler.completeConnection("The server requires mods that are absent or out of date on your client");
    158            loginStates.remove(netLoginHandler);
    159            break;
    160        case FML_OUT_OF_DATE:
    161            netLoginHandler.completeConnection("Your client is not running a new enough version of FML to connect to this server");
    162            loginStates.remove(netLoginHandler);
    163            break;
    164        default:
    165            netLoginHandler.completeConnection("There was a problem during FML negotiation");
    166            loginStates.remove(netLoginHandler);
    167            break;
    168        }
    169    }
    170
    171    /**
    172     * @param netLoginHandler
    173     * @param server
    174     * @param address
    175     * @param userName
    176     * @return if the user can carry on
    177     */
    178    private boolean handleVanillaLoginKick(NetLoginHandler netLoginHandler, MinecraftServer server, SocketAddress address, String userName)
    179    {
    180        // Vanilla reasons first
    181        ServerConfigurationManager playerList = server.getConfigurationManager();
    182        String kickReason = playerList.allowUserToConnect(address, userName);
    183
    184        if (kickReason != null)
    185        {
    186            netLoginHandler.completeConnection(kickReason);
    187        }
    188        return kickReason == null;
    189    }
    190
    191    public static void handleLoginPacketOnServer(NetLoginHandler handler, Packet1Login login)
    192    {
    193        if (login.clientEntityId == FML_HASH)
    194        {
    195            if (login.dimension == PROTOCOL_VERSION)
    196            {
    197                FMLLog.finest("Received valid FML login packet from %s", handler.myTCPConnection.getSocketAddress());
    198                instance().loginStates.put(handler, LOGIN_RECEIVED);
    199            }
    200            else if (login.dimension != PROTOCOL_VERSION)
    201            {
    202                FMLLog.finest("Received incorrect FML (%x) login packet from %s", login.dimension, handler.myTCPConnection.getSocketAddress());
    203                instance().loginStates.put(handler, FML_OUT_OF_DATE);
    204            }
    205        }
    206        else
    207        {
    208            FMLLog.fine("Received invalid login packet (%x, %x) from %s", login.clientEntityId, login.dimension,
    209                    handler.myTCPConnection.getSocketAddress());
    210        }
    211    }
    212
    213    static void setHandlerState(NetLoginHandler handler, int state)
    214    {
    215        instance().loginStates.put(handler, state);
    216    }
    217
    218    public static FMLNetworkHandler instance()
    219    {
    220        return INSTANCE;
    221    }
    222
    223    public static Packet1Login getFMLFakeLoginPacket()
    224    {
    225        // Always reset compat to zero before sending our fake packet
    226        FMLCommonHandler.instance().getSidedDelegate().setClientCompatibilityLevel((byte) 0);
    227        Packet1Login fake = new Packet1Login();
    228        // Hash FML using a simple function
    229        fake.clientEntityId = FML_HASH;
    230        // The FML protocol version
    231        fake.dimension = PROTOCOL_VERSION;
    232        fake.gameType = EnumGameType.NOT_SET;
    233        fake.terrainType = WorldType.worldTypes[0];
    234        return fake;
    235    }
    236
    237    public Packet250CustomPayload getModListRequestPacket()
    238    {
    239        return PacketDispatcher.getPacket("FML", FMLPacket.makePacket(MOD_LIST_REQUEST));
    240    }
    241
    242    public void registerNetworkMod(NetworkModHandler handler)
    243    {
    244        networkModHandlers.put(handler.getContainer(), handler);
    245        networkIdLookup.put(handler.getNetworkId(), handler);
    246    }
    247    public boolean registerNetworkMod(ModContainer container, Class<?> networkModClass, ASMDataTable asmData)
    248    {
    249        NetworkModHandler handler = new NetworkModHandler(container, networkModClass, asmData);
    250        if (handler.isNetworkMod())
    251        {
    252            registerNetworkMod(handler);
    253        }
    254
    255        return handler.isNetworkMod();
    256    }
    257
    258    public NetworkModHandler findNetworkModHandler(Object mc)
    259    {
    260        if (mc instanceof ModContainer)
    261        {
    262            return networkModHandlers.get(mc);
    263        }
    264        else if (mc instanceof Integer)
    265        {
    266            return networkIdLookup.get(mc);
    267        }
    268        else
    269        {
    270            return networkModHandlers.get(FMLCommonHandler.instance().findContainerFor(mc));
    271        }
    272    }
    273
    274    public Set<ModContainer> getNetworkModList()
    275    {
    276        return networkModHandlers.keySet();
    277    }
    278
    279    public static void handlePlayerLogin(EntityPlayerMP player, NetServerHandler netHandler, INetworkManager manager)
    280    {
    281        NetworkRegistry.instance().playerLoggedIn(player, netHandler, manager);
    282        GameRegistry.onPlayerLogin(player);
    283    }
    284
    285    public Map<Integer, NetworkModHandler> getNetworkIdMap()
    286    {
    287        return networkIdLookup;
    288    }
    289
    290    public void bindNetworkId(String key, Integer value)
    291    {
    292        Map<String, ModContainer> mods = Loader.instance().getIndexedModList();
    293        NetworkModHandler handler = findNetworkModHandler(mods.get(key));
    294        if (handler != null)
    295        {
    296            handler.setNetworkId(value);
    297            networkIdLookup.put(value, handler);
    298        }
    299    }
    300
    301    public static void onClientConnectionToRemoteServer(NetHandler netClientHandler, String server, int port, INetworkManager networkManager)
    302    {
    303        NetworkRegistry.instance().connectionOpened(netClientHandler, server, port, networkManager);
    304    }
    305
    306    public static void onClientConnectionToIntegratedServer(NetHandler netClientHandler, MinecraftServer server, INetworkManager networkManager)
    307    {
    308        NetworkRegistry.instance().connectionOpened(netClientHandler, server, networkManager);
    309    }
    310
    311    public static void onConnectionClosed(INetworkManager manager, EntityPlayer player)
    312    {
    313        NetworkRegistry.instance().connectionClosed(manager, player);
    314    }
    315
    316
    317    public static void openGui(EntityPlayer player, Object mod, int modGuiId, World world, int x, int y, int z)
    318    {
    319        ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod);
    320        if (mc == null)
    321        {
    322            NetworkModHandler nmh = instance().findNetworkModHandler(mod);
    323            if (nmh != null)
    324            {
    325                mc = nmh.getContainer();
    326            }
    327            else
    328            {
    329                FMLLog.warning("A mod tried to open a gui on the server without being a NetworkMod");
    330                return;
    331            }
    332        }
    333        if (player instanceof EntityPlayerMP)
    334        {
    335            NetworkRegistry.instance().openRemoteGui(mc, (EntityPlayerMP) player, modGuiId, world, x, y, z);
    336        }
    337        else
    338        {
    339            NetworkRegistry.instance().openLocalGui(mc, player, modGuiId, world, x, y, z);
    340        }
    341    }
    342
    343    public static Packet getEntitySpawningPacket(Entity entity)
    344    {
    345        EntityRegistration er = EntityRegistry.instance().lookupModSpawn(entity.getClass(), false);
    346        if (er == null)
    347        {
    348            return null;
    349        }
    350        if (er.usesVanillaSpawning())
    351        {
    352            return null;
    353        }
    354        return PacketDispatcher.getPacket("FML", FMLPacket.makePacket(Type.ENTITYSPAWN, er, entity, instance().findNetworkModHandler(er.getContainer())));
    355    }
    356
    357    public static void makeEntitySpawnAdjustment(int entityId, EntityPlayerMP player, int serverX, int serverY, int serverZ)
    358    {
    359        Packet250CustomPayload pkt = PacketDispatcher.getPacket("FML", FMLPacket.makePacket(Type.ENTITYSPAWNADJUSTMENT, entityId, serverX, serverY, serverZ));
    360        player.playerNetServerHandler.sendPacketToPlayer(pkt);
    361    }
    362
    363    public static InetAddress computeLocalHost() throws IOException
    364    {
    365        InetAddress add = null;
    366        List<InetAddress> addresses = Lists.newArrayList();
    367        InetAddress localHost = InetAddress.getLocalHost();
    368        for (NetworkInterface ni : Collections.list(NetworkInterface.getNetworkInterfaces()))
    369        {
    370            if (!ni.isLoopback() && ni.isUp())
    371            {
    372                addresses.addAll(Collections.list(ni.getInetAddresses()));
    373                if (addresses.contains(localHost))
    374                {
    375                    add = localHost;
    376                    break;
    377                }
    378            }
    379        }
    380        if (add == null && !addresses.isEmpty())
    381        {
    382            for (InetAddress addr: addresses)
    383            {
    384                if (addr.getAddress().length == 4)
    385                {
    386                    add = addr;
    387                    break;
    388                }
    389            }
    390        }
    391        if (add == null)
    392        {
    393            add = localHost;
    394        }
    395        return add;
    396    }
    397
    398    public static Packet3Chat handleChatMessage(NetHandler handler, Packet3Chat chat)
    399    {
    400        return NetworkRegistry.instance().handleChat(handler, chat);
    401    }
    402
    403    public static void handlePacket131Packet(NetHandler handler, Packet131MapData mapData)
    404    {
    405        if (handler instanceof NetServerHandler || mapData.itemID != Item.map.itemID)
    406        {
    407            // Server side and not "map" packets are always handled by us
    408            NetworkRegistry.instance().handleTinyPacket(handler, mapData);
    409        }
    410        else
    411        {
    412            // Fallback to the net client handler implementation
    413            FMLCommonHandler.instance().handleTinyPacket(handler, mapData);
    414        }
    415    }
    416
    417    public static int getCompatibilityLevel()
    418    {
    419        return PROTOCOL_VERSION;
    420    }
    421
    422    public static boolean vanillaLoginPacketCompatibility()
    423    {
    424        return FMLCommonHandler.instance().getSidedDelegate().getClientCompatibilityLevel() == 0;
    425    }
    426}