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