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