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