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