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}