001package cpw.mods.fml.common.network;
002
003import static cpw.mods.fml.common.network.FMLPacket.Type.MOD_LIST_REQUEST;
004
005import java.io.IOException;
006import java.net.InetAddress;
007import java.net.NetworkInterface;
008import java.net.SocketAddress;
009import java.util.Collections;
010import java.util.List;
011import java.util.Map;
012import java.util.Set;
013
014import net.minecraft.entity.Entity;
015import net.minecraft.entity.player.EntityPlayer;
016import net.minecraft.entity.player.EntityPlayerMP;
017import net.minecraft.item.Item;
018import net.minecraft.network.*;
019import net.minecraft.network.packet.*;
020import net.minecraft.server.MinecraftServer;
021import net.minecraft.server.management.ServerConfigurationManager;
022import net.minecraft.world.EnumGameType;
023import net.minecraft.world.World;
024import net.minecraft.world.WorldType;
025
026import com.google.common.collect.Lists;
027import com.google.common.collect.Maps;
028import com.google.common.hash.Hashing;
029
030import cpw.mods.fml.common.FMLCommonHandler;
031import cpw.mods.fml.common.FMLLog;
032import cpw.mods.fml.common.Loader;
033import cpw.mods.fml.common.ModContainer;
034import cpw.mods.fml.common.discovery.ASMDataTable;
035import cpw.mods.fml.common.network.FMLPacket.Type;
036import cpw.mods.fml.common.registry.EntityRegistry;
037import cpw.mods.fml.common.registry.GameRegistry;
038import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration;
039
040public 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}