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 }