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