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