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 }