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            // Always reset compat to zero before sending our fake packet
222            FMLCommonHandler.instance().getSidedDelegate().setClientCompatibilityLevel((byte) 0);
223            Packet1Login fake = new Packet1Login();
224            // Hash FML using a simple function
225            fake.clientEntityId = FML_HASH;
226            // The FML protocol version
227            fake.dimension = PROTOCOL_VERSION;
228            fake.gameType = EnumGameType.NOT_SET;
229            fake.terrainType = WorldType.worldTypes[0];
230            return fake;
231        }
232    
233        public Packet250CustomPayload getModListRequestPacket()
234        {
235            return PacketDispatcher.getPacket("FML", FMLPacket.makePacket(MOD_LIST_REQUEST));
236        }
237    
238        public void registerNetworkMod(NetworkModHandler handler)
239        {
240            networkModHandlers.put(handler.getContainer(), handler);
241            networkIdLookup.put(handler.getNetworkId(), handler);
242        }
243        public boolean registerNetworkMod(ModContainer container, Class<?> networkModClass, ASMDataTable asmData)
244        {
245            NetworkModHandler handler = new NetworkModHandler(container, networkModClass, asmData);
246            if (handler.isNetworkMod())
247            {
248                registerNetworkMod(handler);
249            }
250    
251            return handler.isNetworkMod();
252        }
253    
254        public NetworkModHandler findNetworkModHandler(Object mc)
255        {
256            if (mc instanceof ModContainer)
257            {
258                return networkModHandlers.get(mc);
259            }
260            else if (mc instanceof Integer)
261            {
262                return networkIdLookup.get(mc);
263            }
264            else
265            {
266                return networkModHandlers.get(FMLCommonHandler.instance().findContainerFor(mc));
267            }
268        }
269    
270        public Set<ModContainer> getNetworkModList()
271        {
272            return networkModHandlers.keySet();
273        }
274    
275        public static void handlePlayerLogin(EntityPlayerMP player, NetServerHandler netHandler, NetworkManager manager)
276        {
277            NetworkRegistry.instance().playerLoggedIn(player, netHandler, manager);
278            GameRegistry.onPlayerLogin(player);
279        }
280    
281        public Map<Integer, NetworkModHandler> getNetworkIdMap()
282        {
283            return networkIdLookup;
284        }
285    
286        public void bindNetworkId(String key, Integer value)
287        {
288            Map<String, ModContainer> mods = Loader.instance().getIndexedModList();
289            NetworkModHandler handler = findNetworkModHandler(mods.get(key));
290            if (handler != null)
291            {
292                handler.setNetworkId(value);
293                networkIdLookup.put(value, handler);
294            }
295        }
296    
297        public static void onClientConnectionToRemoteServer(NetHandler netClientHandler, String server, int port, NetworkManager networkManager)
298        {
299            NetworkRegistry.instance().connectionOpened(netClientHandler, server, port, networkManager);
300        }
301    
302        public static void onClientConnectionToIntegratedServer(NetHandler netClientHandler, MinecraftServer server, NetworkManager networkManager)
303        {
304            NetworkRegistry.instance().connectionOpened(netClientHandler, server, networkManager);
305        }
306    
307        public static void onConnectionClosed(NetworkManager manager, EntityPlayer player)
308        {
309            NetworkRegistry.instance().connectionClosed(manager, player);
310        }
311    
312    
313        public static void openGui(EntityPlayer player, Object mod, int modGuiId, World world, int x, int y, int z)
314        {
315            ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod);
316            if (mc == null)
317            {
318                NetworkModHandler nmh = instance().findNetworkModHandler(mod);
319                if (nmh != null)
320                {
321                    mc = nmh.getContainer();
322                }
323                else
324                {
325                    FMLLog.warning("A mod tried to open a gui on the server without being a NetworkMod");
326                    return;
327                }
328            }
329            if (player instanceof EntityPlayerMP)
330            {
331                NetworkRegistry.instance().openRemoteGui(mc, (EntityPlayerMP) player, modGuiId, world, x, y, z);
332            }
333            else
334            {
335                NetworkRegistry.instance().openLocalGui(mc, player, modGuiId, world, x, y, z);
336            }
337        }
338    
339        public static Packet getEntitySpawningPacket(Entity entity)
340        {
341            EntityRegistration er = EntityRegistry.instance().lookupModSpawn(entity.getClass(), false);
342            if (er == null)
343            {
344                return null;
345            }
346            if (er.usesVanillaSpawning())
347            {
348                return null;
349            }
350            return PacketDispatcher.getPacket("FML", FMLPacket.makePacket(Type.ENTITYSPAWN, er, entity, instance().findNetworkModHandler(er.getContainer())));
351        }
352    
353        public static void makeEntitySpawnAdjustment(int entityId, EntityPlayerMP player, int serverX, int serverY, int serverZ)
354        {
355            Packet250CustomPayload pkt = PacketDispatcher.getPacket("FML", FMLPacket.makePacket(Type.ENTITYSPAWNADJUSTMENT, entityId, serverX, serverY, serverZ));
356            player.playerNetServerHandler.sendPacketToPlayer(pkt);
357        }
358    
359        public static InetAddress computeLocalHost() throws IOException
360        {
361            InetAddress add = null;
362            List<InetAddress> addresses = Lists.newArrayList();
363            InetAddress localHost = InetAddress.getLocalHost();
364            for (NetworkInterface ni : Collections.list(NetworkInterface.getNetworkInterfaces()))
365            {
366                if (!ni.isLoopback() && ni.isUp())
367                {
368                    addresses.addAll(Collections.list(ni.getInetAddresses()));
369                    if (addresses.contains(localHost))
370                    {
371                        add = localHost;
372                        break;
373                    }
374                }
375            }
376            if (add == null && !addresses.isEmpty())
377            {
378                for (InetAddress addr: addresses)
379                {
380                    if (addr.getAddress().length == 4)
381                    {
382                        add = addr;
383                        break;
384                    }
385                }
386            }
387            if (add == null)
388            {
389                add = localHost;
390            }
391            return add;
392        }
393    
394        public static Packet3Chat handleChatMessage(NetHandler handler, Packet3Chat chat)
395        {
396            return NetworkRegistry.instance().handleChat(handler, chat);
397        }
398    
399        public static void handlePacket131Packet(NetHandler handler, Packet131MapData mapData)
400        {
401            if (handler instanceof NetServerHandler || mapData.itemID != Item.map.shiftedIndex)
402            {
403                // Server side and not "map" packets are always handled by us
404                NetworkRegistry.instance().handleTinyPacket(handler, mapData);
405            }
406            else
407            {
408                // Fallback to the net client handler implementation
409                FMLCommonHandler.instance().handleTinyPacket(handler, mapData);
410            }
411        }
412    
413        public static int getCompatibilityLevel()
414        {
415            return PROTOCOL_VERSION;
416        }
417    
418        public static boolean vanillaLoginPacketCompatibility()
419        {
420            return FMLCommonHandler.instance().getSidedDelegate().getClientCompatibilityLevel() == 0;
421        }
422    }