diff --git a/fml/client/cpw/mods/fml/client/FMLClientHandler.java b/fml/client/cpw/mods/fml/client/FMLClientHandler.java index 2e7a8795a9..2225d3ccb3 100644 --- a/fml/client/cpw/mods/fml/client/FMLClientHandler.java +++ b/fml/client/cpw/mods/fml/client/FMLClientHandler.java @@ -26,7 +26,9 @@ import net.minecraft.src.CrashReport; import net.minecraft.src.Entity; import net.minecraft.src.EntityLiving; import net.minecraft.src.EntityPlayer; +import net.minecraft.src.GuiConnecting; import net.minecraft.src.GuiScreen; +import net.minecraft.src.INetworkManager; import net.minecraft.src.NetClientHandler; import net.minecraft.src.NetHandler; import net.minecraft.src.Packet; @@ -39,6 +41,7 @@ import net.minecraft.src.WorldClient; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.MapDifference; import cpw.mods.fml.client.modloader.ModLoaderClientHelper; import cpw.mods.fml.client.registry.KeyBindingRegistry; @@ -61,8 +64,10 @@ import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket; import cpw.mods.fml.common.network.EntitySpawnPacket; import cpw.mods.fml.common.network.ModMissingPacket; import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; +import cpw.mods.fml.common.registry.GameRegistry; import cpw.mods.fml.common.registry.IEntityAdditionalSpawnData; import cpw.mods.fml.common.registry.IThrowableEntity; +import cpw.mods.fml.common.registry.ItemData; import cpw.mods.fml.common.registry.LanguageRegistry; @@ -113,6 +118,8 @@ public class FMLClientHandler implements IFMLSidedHandler private DuplicateModsFoundException dupesFound; + private boolean serverShouldBeKilledQuietly; + /** * Called to start the whole game off * @@ -404,6 +411,7 @@ public class FMLClientHandler implements IFMLSidedHandler @Override public void beginServerLoading(MinecraftServer server) { + serverShouldBeKilledQuietly = false; // NOOP } @@ -459,4 +467,49 @@ public class FMLClientHandler implements IFMLSidedHandler { return NetClientHandler.getConnectionCompatibilityLevel(); } + + public void warnIDMismatch(MapDifference idDifferences, boolean mayContinue) + { + GuiIdMismatchScreen mismatch = new GuiIdMismatchScreen(idDifferences, mayContinue); + client.func_71373_a(mismatch); + } + + public void callbackIdDifferenceResponse(boolean response) + { + if (response) + { + serverShouldBeKilledQuietly = false; + GameRegistry.releaseGate(true); + client.continueWorldLoading(); + } + else + { + serverShouldBeKilledQuietly = true; + GameRegistry.releaseGate(false); + // Reset and clear the client state + client.func_71403_a((WorldClient)null); + client.func_71373_a(null); + } + } + + @Override + public boolean shouldServerShouldBeKilledQuietly() + { + return serverShouldBeKilledQuietly; + } + + @Override + public void disconnectIDMismatch(MapDifference s, NetHandler toKill, INetworkManager mgr) + { + // Nuke the connection + ((NetClientHandler)toKill).func_72553_e(); + // Stop GuiConnecting + GuiConnecting.forceTermination((GuiConnecting)client.field_71462_r); + // pulse the network manager queue to clear cruft + mgr.func_74428_b(); + // Nuke the world client + client.func_71403_a((WorldClient)null); + // Show error screen + warnIDMismatch(s, false); + } } diff --git a/fml/client/cpw/mods/fml/client/GuiIdMismatchScreen.java b/fml/client/cpw/mods/fml/client/GuiIdMismatchScreen.java new file mode 100644 index 0000000000..7ecb8b90c3 --- /dev/null +++ b/fml/client/cpw/mods/fml/client/GuiIdMismatchScreen.java @@ -0,0 +1,87 @@ +package cpw.mods.fml.client; + +import java.util.List; +import java.util.Map.Entry; + +import com.google.common.collect.Lists; +import com.google.common.collect.MapDifference; +import com.google.common.collect.MapDifference.ValueDifference; + +import cpw.mods.fml.common.registry.ItemData; +import cpw.mods.fml.common.versioning.ArtifactVersion; +import net.minecraft.src.GuiButton; +import net.minecraft.src.GuiYesNo; +import net.minecraft.src.StringTranslate; + +public class GuiIdMismatchScreen extends GuiYesNo { + private List missingIds = Lists.newArrayList(); + private List mismatchedIds = Lists.newArrayList(); + private boolean allowContinue; + + public GuiIdMismatchScreen(MapDifference idDifferences, boolean allowContinue) + { + super(null,"ID mismatch", "Should I continue?", 1); + field_73942_a = this; + for (Entry entry : idDifferences.entriesOnlyOnLeft().entrySet()) + { + missingIds.add(String.format("ID %d (ModID: %s, type %s) is missing", entry.getValue().itemId, entry.getValue().modId, entry.getValue().itemType)); + } + for (Entry> entry : idDifferences.entriesDiffering().entrySet()) + { + ItemData world = entry.getValue().leftValue(); + ItemData game = entry.getValue().rightValue(); + mismatchedIds.add(String.format("ID %d is mismatched. World: (ModID: %s, type %s, ordinal %d) Game (ModID: %s, type %s, ordinal %d)", world.itemId, world.modId, world.itemType, world.ordinal, game.modId, game.itemType, game.ordinal)); + } + this.allowContinue = allowContinue; + } + + @Override + public void func_73878_a(boolean choice, int p_73878_2_) + { + FMLClientHandler.instance().callbackIdDifferenceResponse(choice); + } + + @Override + public void func_73863_a(int p_73863_1_, int p_73863_2_, float p_73863_3_) + { + this.func_73873_v_(); + if (!allowContinue && field_73887_h.size() == 2) + { + field_73887_h.remove(0); + } + int offset = Math.max(85 - missingIds.size() * 10 + mismatchedIds.size() * 30, 10); + this.func_73732_a(this.field_73886_k, "Forge Mod Loader has found world ID mismatches", this.field_73880_f / 2, offset, 0xFFFFFF); + offset += 10; + for (String s: missingIds) { + this.func_73732_a(this.field_73886_k, s, this.field_73880_f / 2, offset, 0xEEEEEE); + offset += 10; + } + for (String s: mismatchedIds) { + this.func_73732_a(this.field_73886_k, s, this.field_73880_f / 2, offset, 0xEEEEEE); + offset += 10; + } + offset += 10; + if (allowContinue) + { + this.func_73732_a(this.field_73886_k, "Do you wish to continue loading?", this.field_73880_f / 2, offset, 0xFFFFFF); + offset += 10; + } + else + { + this.func_73732_a(this.field_73886_k, "You cannot connect to this server", this.field_73880_f / 2, offset, 0xFFFFFF); + offset += 10; + } + // super.super. Grrr + for (int var4 = 0; var4 < this.field_73887_h.size(); ++var4) + { + GuiButton var5 = (GuiButton)this.field_73887_h.get(var4); + var5.field_73743_d = Math.min(offset + 10, this.field_73881_g - 20); + if (!allowContinue) + { + var5.field_73746_c = this.field_73880_f / 2 - 75; + var5.field_73744_e = StringTranslate.func_74808_a().func_74805_b("gui.done"); + } + var5.func_73737_a(this.field_73882_e, p_73863_1_, p_73863_2_); + } + } +} diff --git a/fml/common/cpw/mods/fml/common/FMLCommonHandler.java b/fml/common/cpw/mods/fml/common/FMLCommonHandler.java index 25818190d2..5bc591aac2 100644 --- a/fml/common/cpw/mods/fml/common/FMLCommonHandler.java +++ b/fml/common/cpw/mods/fml/common/FMLCommonHandler.java @@ -27,6 +27,7 @@ import net.minecraft.src.DedicatedServer; import net.minecraft.src.Entity; import net.minecraft.src.EntityPlayer; import net.minecraft.src.EntityPlayerMP; +import net.minecraft.src.INetworkManager; import net.minecraft.src.NBTBase; import net.minecraft.src.NBTTagCompound; import net.minecraft.src.NetHandler; @@ -42,6 +43,7 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.Lists; +import com.google.common.collect.MapDifference; import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -49,6 +51,7 @@ import com.google.common.collect.Sets; import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket; import cpw.mods.fml.common.network.EntitySpawnPacket; import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; +import cpw.mods.fml.common.registry.ItemData; import cpw.mods.fml.common.registry.TickRegistry; import cpw.mods.fml.server.FMLServerHandler; @@ -441,4 +444,14 @@ public class FMLCommonHandler } } } + + public boolean shouldServerBeKilledQuietly() + { + return sidedDelegate.shouldServerShouldBeKilledQuietly(); + } + + public void disconnectIDMismatch(MapDifference serverDifference, NetHandler toKill, INetworkManager network) + { + sidedDelegate.disconnectIDMismatch(serverDifference, toKill, network); + } } diff --git a/fml/common/cpw/mods/fml/common/FMLDummyContainer.java b/fml/common/cpw/mods/fml/common/FMLDummyContainer.java index 364f580466..0aed689c64 100644 --- a/fml/common/cpw/mods/fml/common/FMLDummyContainer.java +++ b/fml/common/cpw/mods/fml/common/FMLDummyContainer.java @@ -30,6 +30,9 @@ import com.google.common.collect.MapMaker; import com.google.common.collect.Sets; import com.google.common.eventbus.EventBus; +import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.common.registry.ItemData; + /** * @author cpw * @@ -75,6 +78,9 @@ public class FMLDummyContainer extends DummyModContainer implements WorldAccessC list.func_74742_a(mod); } fmlData.func_74782_a("ModList", list); + NBTTagList itemList = new NBTTagList(); + GameRegistry.writeItemData(itemList); + fmlData.func_74782_a("ModItemData", itemList); return fmlData; } @@ -101,5 +107,16 @@ public class FMLDummyContainer extends DummyModContainer implements WorldAccessC } } } + if (tag.func_74764_b("ModItemData")) + { + NBTTagList modList = tag.func_74761_m("ModItemData"); + Set worldSaveItems = GameRegistry.buildWorldItemData(modList); + GameRegistry.validateWorldSave(worldSaveItems); + } + else + { + GameRegistry.validateWorldSave(null); + } } + } diff --git a/fml/common/cpw/mods/fml/common/IDispenseHandler.java b/fml/common/cpw/mods/fml/common/IDispenseHandler.java index e51387a630..9b14bd911f 100644 --- a/fml/common/cpw/mods/fml/common/IDispenseHandler.java +++ b/fml/common/cpw/mods/fml/common/IDispenseHandler.java @@ -18,6 +18,13 @@ import net.minecraft.src.ItemStack; import net.minecraft.src.World; +/** + * Deprecated without replacement, use vanilla DispenserRegistry code + * + * @author cpw + * + */ +@Deprecated public interface IDispenseHandler { /** diff --git a/fml/common/cpw/mods/fml/common/IDispenserHandler.java b/fml/common/cpw/mods/fml/common/IDispenserHandler.java index f4b7fa7218..528f4ccb82 100644 --- a/fml/common/cpw/mods/fml/common/IDispenserHandler.java +++ b/fml/common/cpw/mods/fml/common/IDispenserHandler.java @@ -6,9 +6,13 @@ import net.minecraft.src.ItemStack; import net.minecraft.src.World; /** + * + * Deprecated without replacement. Use vanilla DispenserRegistry code. + * * @author cpw * */ +@Deprecated public interface IDispenserHandler { /** diff --git a/fml/common/cpw/mods/fml/common/IFMLSidedHandler.java b/fml/common/cpw/mods/fml/common/IFMLSidedHandler.java index d3db4165bd..e54d77e2fe 100644 --- a/fml/common/cpw/mods/fml/common/IFMLSidedHandler.java +++ b/fml/common/cpw/mods/fml/common/IFMLSidedHandler.java @@ -2,14 +2,18 @@ package cpw.mods.fml.common; import java.util.List; +import com.google.common.collect.MapDifference; + import net.minecraft.server.MinecraftServer; import net.minecraft.src.Entity; +import net.minecraft.src.INetworkManager; import net.minecraft.src.NetHandler; import net.minecraft.src.Packet; import net.minecraft.src.Packet131MapData; import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket; import cpw.mods.fml.common.network.EntitySpawnPacket; import cpw.mods.fml.common.network.ModMissingPacket; +import cpw.mods.fml.common.registry.ItemData; import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; public interface IFMLSidedHandler @@ -41,4 +45,8 @@ public interface IFMLSidedHandler void setClientCompatibilityLevel(byte compatibilityLevel); byte getClientCompatibilityLevel(); + + boolean shouldServerShouldBeKilledQuietly(); + + void disconnectIDMismatch(MapDifference s, NetHandler toKill, INetworkManager mgr); } diff --git a/fml/common/cpw/mods/fml/common/Loader.java b/fml/common/cpw/mods/fml/common/Loader.java index b710807e90..f0264d5d92 100644 --- a/fml/common/cpw/mods/fml/common/Loader.java +++ b/fml/common/cpw/mods/fml/common/Loader.java @@ -721,7 +721,7 @@ public class Loader public ModContainer activeModContainer() { - return modController.activeContainer(); + return modController != null ? modController.activeContainer() : null; } public boolean isInState(LoaderState state) @@ -735,7 +735,7 @@ public class Loader } public boolean hasReachedState(LoaderState state) { - return modController.hasReachedState(state); + return modController != null ? modController.hasReachedState(state) : false; } public String getMCPVersionString() { diff --git a/fml/common/cpw/mods/fml/common/network/FMLNetworkHandler.java b/fml/common/cpw/mods/fml/common/network/FMLNetworkHandler.java index 29c4e3e61e..0b0a00611a 100644 --- a/fml/common/cpw/mods/fml/common/network/FMLNetworkHandler.java +++ b/fml/common/cpw/mods/fml/common/network/FMLNetworkHandler.java @@ -47,7 +47,7 @@ import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; public class FMLNetworkHandler { private static final int FML_HASH = Hashing.murmur3_32().hashString("FML").asInt(); - private static final int PROTOCOL_VERSION = 0x1; + private static final int PROTOCOL_VERSION = 0x2; private static final FMLNetworkHandler INSTANCE = new FMLNetworkHandler(); // List of states for connections from clients to server @@ -86,7 +86,12 @@ public class FMLNetworkHandler private void handleFMLPacket(Packet250CustomPayload packet, INetworkManager network, NetHandler netHandler) { - FMLPacket pkt = FMLPacket.readPacket(packet.field_73629_c); + FMLPacket pkt = FMLPacket.readPacket(network, packet.field_73629_c); + // Part of an incomplete multipart packet + if (pkt == null) + { + return; + } String userName = ""; if (netHandler instanceof NetLoginHandler) { diff --git a/fml/common/cpw/mods/fml/common/network/FMLPacket.java b/fml/common/cpw/mods/fml/common/network/FMLPacket.java index 8063474521..851ec727cf 100644 --- a/fml/common/cpw/mods/fml/common/network/FMLPacket.java +++ b/fml/common/cpw/mods/fml/common/network/FMLPacket.java @@ -1,13 +1,16 @@ package cpw.mods.fml.common.network; import java.util.Arrays; +import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import net.minecraft.src.NetHandler; import net.minecraft.src.INetworkManager; import com.google.common.base.Throwables; +import com.google.common.collect.MapMaker; import com.google.common.primitives.Bytes; +import com.google.common.primitives.Ints; import com.google.common.primitives.UnsignedBytes; import cpw.mods.fml.common.FMLLog; @@ -19,38 +22,45 @@ public abstract class FMLPacket /** * Opening salutation from the server to the client -> request all mods from the client */ - MOD_LIST_REQUEST(ModListRequestPacket.class), + MOD_LIST_REQUEST(ModListRequestPacket.class, false), /** * The client responds with the list of mods and versions it has. This is verified by the server. */ - MOD_LIST_RESPONSE(ModListResponsePacket.class), + MOD_LIST_RESPONSE(ModListResponsePacket.class, false), /** * At which point the server tells the client the mod identifiers for this session. */ - MOD_IDENTIFIERS(ModIdentifiersPacket.class), + MOD_IDENTIFIERS(ModIdentifiersPacket.class, false), /** * Or, if there is missing stuff, the server tells the client what's missing and drops the connection. */ - MOD_MISSING(ModMissingPacket.class), + MOD_MISSING(ModMissingPacket.class, false), /** * Open a GUI on the client from the server */ - GUIOPEN(OpenGuiPacket.class), + GUIOPEN(OpenGuiPacket.class, false), /** * Spawn an entity on the client from the server */ - ENTITYSPAWN(EntitySpawnPacket.class), + ENTITYSPAWN(EntitySpawnPacket.class, false), /** * Fixes entity location data after spawning */ - ENTITYSPAWNADJUSTMENT(EntitySpawnAdjustmentPacket.class); - + ENTITYSPAWNADJUSTMENT(EntitySpawnAdjustmentPacket.class, false), + /** + * The ID map to send to the client + */ + MOD_IDMAP(ModIdMapPacket.class, true); + private Class packetType; + private boolean isMultipart; + private ConcurrentMap partTracker; - private Type(Class clazz) + private Type(Class clazz, boolean isMultipart) { this.packetType = clazz; + this.isMultipart = isMultipart; } FMLPacket make() @@ -66,20 +76,64 @@ public abstract class FMLPacket throw new FMLNetworkException(e); } } + + public boolean isMultipart() + { + return isMultipart; + } + + private FMLPacket findCurrentPart(INetworkManager network) + { + if (partTracker == null) + { + partTracker = new MapMaker().weakKeys().weakValues().makeMap(); + } + if (!partTracker.containsKey(network)) + { + partTracker.put(network, make()); + } + return partTracker.get(network); + } } private Type type; + public static byte[][] makePacketSet(Type type, Object... data) + { + if (!type.isMultipart()) + { + return new byte[0][]; + } + byte[] packetData = type.make().generatePacket(data); + + byte[][] chunks = new byte[packetData.length / 32000 + 1][]; + for (int i = 0; i < packetData.length / 32000 + 1; i++) + { + int len = Math.min(32000, packetData.length - i* 32000); + chunks[i] = Bytes.concat(new byte[] { UnsignedBytes.checkedCast(type.ordinal()), UnsignedBytes.checkedCast(i), UnsignedBytes.checkedCast(chunks.length)}, Ints.toByteArray(len), Arrays.copyOfRange(packetData, i * 32000, len)); + } + return chunks; + } public static byte[] makePacket(Type type, Object... data) { byte[] packetData = type.make().generatePacket(data); return Bytes.concat(new byte[] { UnsignedBytes.checkedCast(type.ordinal()) }, packetData ); } - public static FMLPacket readPacket(byte[] payload) + public static FMLPacket readPacket(INetworkManager network, byte[] payload) { int type = UnsignedBytes.toInt(payload[0]); - return Type.values()[type].make().consumePacket(Arrays.copyOfRange(payload, 1, payload.length)); + Type eType = Type.values()[type]; + FMLPacket pkt; + if (eType.isMultipart()) + { + pkt = eType.findCurrentPart(network); + } + else + { + pkt = eType.make(); + } + return pkt.consumePacket(Arrays.copyOfRange(payload, 1, payload.length)); } public FMLPacket(Type type) @@ -92,8 +146,4 @@ public abstract class FMLPacket public abstract FMLPacket consumePacket(byte[] data); public abstract void execute(INetworkManager network, FMLNetworkHandler handler, NetHandler netHandler, String userName); - { - // TODO Auto-generated method stub - - } } diff --git a/fml/common/cpw/mods/fml/common/network/ModIdMapPacket.java b/fml/common/cpw/mods/fml/common/network/ModIdMapPacket.java new file mode 100644 index 0000000000..566339723d --- /dev/null +++ b/fml/common/cpw/mods/fml/common/network/ModIdMapPacket.java @@ -0,0 +1,99 @@ +package cpw.mods.fml.common.network; + +import java.io.IOException; +import java.util.BitSet; +import java.util.Set; +import java.util.logging.Level; + +import com.google.common.collect.MapDifference; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; +import com.google.common.primitives.Bytes; +import com.google.common.primitives.Ints; +import com.google.common.primitives.UnsignedBytes; + +import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.common.registry.ItemData; +import net.minecraft.src.CompressedStreamTools; +import net.minecraft.src.INetworkManager; +import net.minecraft.src.NBTTagCompound; +import net.minecraft.src.NBTTagList; +import net.minecraft.src.NetHandler; +import net.minecraft.src.WorldClient; +import static cpw.mods.fml.common.network.FMLPacket.Type.MOD_IDMAP; + +public class ModIdMapPacket extends FMLPacket { + private byte[][] partials; + + public ModIdMapPacket() + { + super(MOD_IDMAP); + } + + @Override + public byte[] generatePacket(Object... data) + { + NBTTagList completeList = (NBTTagList) data[0]; + NBTTagCompound wrap = new NBTTagCompound(); + wrap.func_74782_a("List", completeList); + try + { + return CompressedStreamTools.func_74798_a(wrap); + } + catch (Exception e) + { + FMLLog.log(Level.SEVERE, e, "A critical error writing the id map"); + throw new FMLNetworkException(e); + } + } + + @Override + public FMLPacket consumePacket(byte[] data) + { + ByteArrayDataInput bdi = ByteStreams.newDataInput(data); + int chunkIdx = UnsignedBytes.toInt(bdi.readByte()); + int chunkTotal = UnsignedBytes.toInt(bdi.readByte()); + int chunkLength = bdi.readInt(); + if (partials == null) + { + partials = new byte[chunkTotal][]; + } + partials[chunkIdx] = new byte[chunkLength]; + bdi.readFully(partials[chunkIdx]); + for (int i = 0; i < partials.length; i++) + { + if (partials[i] == null) + { + return null; + } + } + return this; + } + + @Override + public void execute(INetworkManager network, FMLNetworkHandler handler, NetHandler netHandler, String userName) + { + byte[] allData = Bytes.concat(partials); + GameRegistry.initializeServerGate(1); + try + { + NBTTagCompound serverList = CompressedStreamTools.func_74792_a(allData); + NBTTagList list = serverList.func_74761_m("List"); + Set itemData = GameRegistry.buildWorldItemData(list); + GameRegistry.validateWorldSave(itemData); + MapDifference serverDifference = GameRegistry.gateWorldLoadingForValidation(); + if (serverDifference!=null) + { + FMLCommonHandler.instance().disconnectIDMismatch(serverDifference, netHandler, network); + + } + } + catch (IOException e) + { + } + } + +} diff --git a/fml/common/cpw/mods/fml/common/network/ModListResponsePacket.java b/fml/common/cpw/mods/fml/common/network/ModListResponsePacket.java index 0f51ca26bb..8c5813de90 100644 --- a/fml/common/cpw/mods/fml/common/network/ModListResponsePacket.java +++ b/fml/common/cpw/mods/fml/common/network/ModListResponsePacket.java @@ -3,12 +3,14 @@ package cpw.mods.fml.common.network; import static cpw.mods.fml.common.network.FMLPacket.Type.MOD_IDENTIFIERS; import static cpw.mods.fml.common.network.FMLPacket.Type.MOD_LIST_RESPONSE; import static cpw.mods.fml.common.network.FMLPacket.Type.MOD_MISSING; +import static cpw.mods.fml.common.network.FMLPacket.Type.MOD_IDMAP; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Logger; +import net.minecraft.src.NBTTagList; import net.minecraft.src.NetHandler; import net.minecraft.src.NetLoginHandler; import net.minecraft.src.INetworkManager; @@ -23,6 +25,7 @@ import com.google.common.io.ByteStreams; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.registry.GameRegistry; public class ModListResponsePacket extends FMLPacket { @@ -113,16 +116,25 @@ public class ModListResponsePacket extends FMLPacket FMLLog.info("User %s connection failed: missing %s, bad versions %s", userName, missingClientMods, versionIncorrectMods); // Mark this as bad FMLNetworkHandler.setHandlerState((NetLoginHandler) netHandler, FMLNetworkHandler.MISSING_MODS_OR_VERSIONS); + pkt.field_73628_b = pkt.field_73629_c.length; + network.func_74429_a(pkt); } else { pkt.field_73629_c = FMLPacket.makePacket(MOD_IDENTIFIERS, netHandler); Logger.getLogger("Minecraft").info(String.format("User %s connecting with mods %s", userName, modVersions.keySet())); FMLLog.info("User %s connecting with mods %s", userName, modVersions.keySet()); + pkt.field_73628_b = pkt.field_73629_c.length; + network.func_74429_a(pkt); + NBTTagList itemList = new NBTTagList(); + GameRegistry.writeItemData(itemList); + byte[][] registryPackets = FMLPacket.makePacketSet(MOD_IDMAP, itemList); + for (int i = 0; i < registryPackets.length; i++) + { + network.func_74429_a(PacketDispatcher.getPacket("FML", registryPackets[i])); + } } - pkt.field_73628_b = pkt.field_73629_c.length; - network.func_74429_a(pkt); // reset the continuation flag - we have completed extra negotiation and the login should complete now NetLoginHandler.func_72531_a((NetLoginHandler) netHandler, true); } diff --git a/fml/common/cpw/mods/fml/common/registry/GameRegistry.java b/fml/common/cpw/mods/fml/common/registry/GameRegistry.java index 5692680cbd..620e533b4a 100644 --- a/fml/common/cpw/mods/fml/common/registry/GameRegistry.java +++ b/fml/common/cpw/mods/fml/common/registry/GameRegistry.java @@ -1,10 +1,13 @@ package cpw.mods.fml.common.registry; import java.util.List; +import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.logging.Level; +import net.minecraft.server.MinecraftServer; import net.minecraft.src.BiomeGenBase; import net.minecraft.src.CraftingManager; import net.minecraft.src.EntityItem; @@ -13,16 +16,24 @@ import net.minecraft.src.FurnaceRecipes; import net.minecraft.src.IChunkProvider; import net.minecraft.src.IInventory; import net.minecraft.src.IRecipe; +import net.minecraft.src.Item; import net.minecraft.src.ItemBlock; import net.minecraft.src.ItemStack; +import net.minecraft.src.NBTTagCompound; +import net.minecraft.src.NBTTagList; import net.minecraft.src.TileEntity; import net.minecraft.src.World; import net.minecraft.src.WorldType; +import com.google.common.base.Function; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; +import com.google.common.collect.MapDifference; +import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; +import com.google.common.collect.Sets.SetView; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.ICraftingHandler; @@ -41,11 +52,10 @@ import cpw.mods.fml.common.ModContainer; public class GameRegistry { private static Multimap blockRegistry = ArrayListMultimap.create(); - private static Multimap itemRegistry = ArrayListMultimap.create(); + private static Set itemRegistry = Sets.newHashSet(); private static Set worldGenerators = Sets.newHashSet(); private static List fuelHandlers = Lists.newArrayList(); private static List craftingHandlers = Lists.newArrayList(); - private static List dispenserHandlers = Lists.newArrayList(); private static List pickupHandlers = Lists.newArrayList(); private static List playerTrackers = Lists.newArrayList(); @@ -83,33 +93,29 @@ public class GameRegistry } } + /** + * Deprecated without replacement. Use vanilla DispenserRegistry code + * + * @param handler + */ + @Deprecated public static void registerDispenserHandler(IDispenserHandler handler) { - dispenserHandlers.add(handler); } /** - * Register a handler for dispensers + * Deprecated without replacement. Use vanilla DispenserRegistry code * * @param handler */ @Deprecated public static void registerDispenserHandler(final IDispenseHandler handler) { - registerDispenserHandler(new IDispenserHandler() - { - - @Override - public int dispense(int x, int y, int z, int xVelocity, int zVelocity, World world, ItemStack item, Random random, double entX, double entY, double entZ) - { - return handler.dispense(x, y, z, xVelocity, zVelocity, world, item, random, entX, entY, entZ); - } - }); } /** - * Callback hook for dispenser activities - if you add a block and want mods to be able - * to extend their dispenser related activities to it call this + * + * Deprecated without replacement, use vanilla DispenserRegistry code * * @param world * @param x @@ -119,16 +125,9 @@ public class GameRegistry * @param zVelocity * @param item */ + @Deprecated public static int tryDispense(World world, int x, int y, int z, int xVelocity, int zVelocity, ItemStack item, Random random, double entX, double entY, double entZ) { - for (IDispenserHandler handler : dispenserHandlers) - { - int dispensed = handler.dispense(x, y, z, xVelocity, zVelocity, world, item, random, entX, entY, entZ); - if (dispensed>-1) - { - return dispensed; - } - } return -1; } /** @@ -302,4 +301,134 @@ public class GameRegistry for(IPlayerTracker tracker : playerTrackers) tracker.onPlayerRespawn(player); } + + public static void newItemAdded(Item item) + { + ModContainer mc = Loader.instance().activeModContainer(); + if (mc == null) + { + mc = Loader.instance().getMinecraftModContainer(); + if (Loader.instance().hasReachedState(LoaderState.AVAILABLE)) + { + FMLLog.severe("It appears something has tried to allocate an Item outside of the initialization phase of Minecraft, this could be very bad for your network connectivity."); + } + } + String itemType = item.getClass().getName(); + itemRegistry.add(new ItemData(item, mc)); + System.out.printf("Adding item %s(%d) owned by %s\n", item.getClass().getName(), item.field_77779_bT, mc); + + } + + public static void validateWorldSave(Set worldSaveItems) + { + isSaveValid = true; + shouldContinue = true; + // allow ourselves to continue if there's no saved data + if (worldSaveItems == null) + { + serverValidationLatch.countDown(); + try + { + clientValidationLatch.await(); + } + catch (InterruptedException e) + { + } + return; + } + + Function idMapFunction = new Function() { + public Integer apply(ItemData input) { + return input.itemId; + }; + }; + + Map worldMap = Maps.uniqueIndex(worldSaveItems,idMapFunction); + Map gameMap = Maps.uniqueIndex(itemRegistry, idMapFunction); + difference = Maps.difference(worldMap, gameMap); + if (!difference.entriesDiffering().isEmpty() || !difference.entriesOnlyOnLeft().isEmpty()) + { + isSaveValid = false; + serverValidationLatch.countDown(); + } + else + { + isSaveValid = true; + serverValidationLatch.countDown(); + } + try + { + clientValidationLatch.await(); + if (!shouldContinue) + { + throw new RuntimeException("This server instance is going to stop abnormally because of a fatal ID mismatch"); + } + } + catch (InterruptedException e) + { + } + } + + public static void writeItemData(NBTTagList itemList) + { + for (ItemData dat : itemRegistry) + { + itemList.func_74742_a(dat.toNBT()); + } + } + + /** + * Initialize the server gate + * @param gateCount the countdown amount. If it's 2 we're on the client and the client and server + * will wait at the latch. 1 is a server and the server will proceed + */ + public static void initializeServerGate(int gateCount) + { + serverValidationLatch = new CountDownLatch(gateCount - 1); + clientValidationLatch = new CountDownLatch(gateCount - 1); + } + + public static MapDifference gateWorldLoadingForValidation() + { + try + { + serverValidationLatch.await(); + if (!isSaveValid) + { + return difference; + } + } + catch (InterruptedException e) + { + } + difference = null; + return null; + } + + + public static void releaseGate(boolean carryOn) + { + shouldContinue = carryOn; + clientValidationLatch.countDown(); + } + + public static Set buildWorldItemData(NBTTagList modList) + { + Set worldSaveItems = Sets.newHashSet(); + for (int i = 0; i < modList.func_74745_c(); i++) + { + NBTTagCompound mod = (NBTTagCompound) modList.func_74743_b(i); + ItemData dat = new ItemData(mod); + worldSaveItems.add(dat); + } + return worldSaveItems; + } + + + private static CountDownLatch serverValidationLatch; + private static CountDownLatch clientValidationLatch; + private static MapDifference difference; + private static boolean shouldContinue = true; + private static boolean isSaveValid = true; + } diff --git a/fml/common/cpw/mods/fml/common/registry/ItemData.java b/fml/common/cpw/mods/fml/common/registry/ItemData.java new file mode 100644 index 0000000000..96eebd4c36 --- /dev/null +++ b/fml/common/cpw/mods/fml/common/registry/ItemData.java @@ -0,0 +1,72 @@ +package cpw.mods.fml.common.registry; + +import java.util.Map; + +import com.google.common.base.Objects; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Maps; +import com.google.common.collect.Multiset; + +import cpw.mods.fml.common.ModContainer; + +import net.minecraft.src.Item; +import net.minecraft.src.NBTTagCompound; + +public class ItemData { + + private static Map> modOrdinals = Maps.newHashMap(); + public final String modId; + public final String itemType; + public final int itemId; + public final int ordinal; + + public ItemData(Item item, ModContainer mc) + { + this.itemId = item.field_77779_bT; + this.itemType = item.getClass().getName(); + this.modId = mc.getModId(); + if (!modOrdinals.containsKey(mc.getModId())) + { + modOrdinals.put(mc.getModId(), HashMultiset.create()); + } + this.ordinal = modOrdinals.get(mc.getModId()).add(itemType, 1); + } + + public ItemData(NBTTagCompound tag) + { + this.modId = tag.func_74779_i("ModId"); + this.itemType = tag.func_74779_i("ItemType"); + this.itemId = tag.func_74762_e("ItemId"); + this.ordinal = tag.func_74762_e("ordinal"); + } + + public NBTTagCompound toNBT() + { + NBTTagCompound tag = new NBTTagCompound(); + tag.func_74778_a("ModId", modId); + tag.func_74778_a("ItemType", itemType); + tag.func_74768_a("ItemId", itemId); + tag.func_74768_a("ordinal", ordinal); + return tag; + } + + @Override + public int hashCode() + { + return Objects.hashCode(modId, itemType, itemId, ordinal); + } + + @Override + public boolean equals(Object obj) + { + try + { + ItemData other = (ItemData) obj; + return Objects.equal(modId, other.modId) && Objects.equal(itemType, other.itemType) && Objects.equal(itemId, other.itemId) && Objects.equal(ordinal, other.ordinal); + } + catch (ClassCastException cce) + { + return false; + } + } +} diff --git a/fml/common/cpw/mods/fml/server/FMLServerHandler.java b/fml/common/cpw/mods/fml/server/FMLServerHandler.java index f075b1b316..501e6ff3f5 100644 --- a/fml/common/cpw/mods/fml/server/FMLServerHandler.java +++ b/fml/common/cpw/mods/fml/server/FMLServerHandler.java @@ -15,9 +15,11 @@ package cpw.mods.fml.server; import java.util.List; import com.google.common.collect.ImmutableList; +import com.google.common.collect.MapDifference; import net.minecraft.server.MinecraftServer; import net.minecraft.src.Entity; +import net.minecraft.src.INetworkManager; import net.minecraft.src.NetClientHandler; import net.minecraft.src.NetHandler; import net.minecraft.src.Packet; @@ -32,6 +34,8 @@ import cpw.mods.fml.common.network.EntitySpawnAdjustmentPacket; import cpw.mods.fml.common.network.EntitySpawnPacket; import cpw.mods.fml.common.network.ModMissingPacket; import cpw.mods.fml.common.registry.EntityRegistry.EntityRegistration; +import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.common.registry.ItemData; import cpw.mods.fml.common.registry.LanguageRegistry; /** @@ -89,6 +93,7 @@ public class FMLServerHandler implements IFMLSidedHandler { Loader.instance().initializeMods(); LanguageRegistry.reloadLanguageTable(); + GameRegistry.initializeServerGate(1); } @Override @@ -174,4 +179,15 @@ public class FMLServerHandler implements IFMLSidedHandler { return 0; } + + @Override + public boolean shouldServerShouldBeKilledQuietly() + { + return false; + } + @Override + public void disconnectIDMismatch(MapDifference s, NetHandler handler, INetworkManager mgr) + { + + } } diff --git a/fml/common/fml_at.cfg b/fml/common/fml_at.cfg index 6d15b86a28..205034404e 100644 --- a/fml/common/fml_at.cfg +++ b/fml/common/fml_at.cfg @@ -69,3 +69,5 @@ public qu.bN #FD:EntityVillager/field_70958_bB public qu.bO #FD:EntityVillager/field_70960_bC # GuiButtonMerchant public avu #CL:GuiButtonMerchant + +protected asy.a #FD:GuiYesNo/field_73942_a #guiScreen diff --git a/fml/patches/common/net/minecraft/server/MinecraftServer.java.patch b/fml/patches/common/net/minecraft/server/MinecraftServer.java.patch index 0999dc466f..22136f8a1f 100644 --- a/fml/patches/common/net/minecraft/server/MinecraftServer.java.patch +++ b/fml/patches/common/net/minecraft/server/MinecraftServer.java.patch @@ -29,7 +29,29 @@ } else { -@@ -484,8 +491,10 @@ +@@ -429,6 +436,10 @@ + } + catch (Throwable var48) + { ++ if (FMLCommonHandler.instance().shouldServerBeKilledQuietly()) ++ { ++ return; ++ } + var48.printStackTrace(); + field_71306_a.log(Level.SEVERE, "Encountered an unexpected exception " + var48.getClass().getSimpleName(), var48); + CrashReport var2 = null; +@@ -459,6 +470,10 @@ + { + try + { ++ if (FMLCommonHandler.instance().shouldServerBeKilledQuietly()) ++ { ++ return; ++ } + this.func_71260_j(); + this.field_71316_v = true; + } +@@ -484,8 +499,10 @@ public void func_71217_p() { @@ -40,7 +62,7 @@ ++this.field_71315_w; if (this.field_71295_T) -@@ -531,6 +540,7 @@ +@@ -531,6 +548,7 @@ this.field_71304_b.func_76319_b(); this.field_71304_b.func_76319_b(); @@ -48,7 +70,7 @@ } public void func_71190_q() -@@ -558,6 +568,7 @@ +@@ -558,6 +576,7 @@ } this.field_71304_b.func_76320_a("tick"); @@ -56,7 +78,7 @@ CrashReport var6; try -@@ -582,6 +593,7 @@ +@@ -582,6 +601,7 @@ throw new ReportedException(var6); } @@ -64,7 +86,7 @@ this.field_71304_b.func_76319_b(); this.field_71304_b.func_76320_a("tracker"); var4.func_73039_n().func_72788_a(); -@@ -1150,6 +1162,12 @@ +@@ -1150,6 +1170,12 @@ @SideOnly(Side.SERVER) public static void main(String[] p_main_0_) { diff --git a/fml/patches/common/net/minecraft/src/Item.java.patch b/fml/patches/common/net/minecraft/src/Item.java.patch new file mode 100644 index 0000000000..b57a5b1d34 --- /dev/null +++ b/fml/patches/common/net/minecraft/src/Item.java.patch @@ -0,0 +1,19 @@ +--- ../src-base/common/net/minecraft/src/Item.java ++++ ../src-work/common/net/minecraft/src/Item.java +@@ -2,6 +2,8 @@ + + import cpw.mods.fml.common.Side; + import cpw.mods.fml.common.asm.SideOnly; ++import cpw.mods.fml.common.registry.GameRegistry; ++ + import java.util.List; + import java.util.Random; + +@@ -187,6 +189,7 @@ + } + + field_77698_e[256 + p_i3659_1_] = this; ++ GameRegistry.newItemAdded(this); + } + + public Item func_77665_c(int p_77665_1_) diff --git a/fml/patches/common/net/minecraft/src/SaveHandler.java.patch b/fml/patches/common/net/minecraft/src/SaveHandler.java.patch index 96e688fee2..db05103d38 100644 --- a/fml/patches/common/net/minecraft/src/SaveHandler.java.patch +++ b/fml/patches/common/net/minecraft/src/SaveHandler.java.patch @@ -9,7 +9,7 @@ public class SaveHandler implements ISaveHandler, IPlayerFileData { -@@ -97,14 +99,16 @@ +@@ -97,17 +99,23 @@ File var1 = new File(this.field_75770_b, "level.dat"); NBTTagCompound var2; NBTTagCompound var3; @@ -28,7 +28,14 @@ } catch (Exception var5) { -@@ -120,7 +124,9 @@ ++ if (FMLCommonHandler.instance().shouldServerBeKilledQuietly()) ++ { ++ throw (RuntimeException)var5; ++ } + var5.printStackTrace(); + } + } +@@ -120,7 +128,9 @@ { var2 = CompressedStreamTools.func_74796_a(new FileInputStream(var1)); var3 = var2.func_74775_l("Data"); @@ -39,7 +46,7 @@ } catch (Exception var4) { -@@ -136,7 +142,7 @@ +@@ -136,7 +146,7 @@ NBTTagCompound var3 = p_75755_1_.func_76082_a(p_75755_2_); NBTTagCompound var4 = new NBTTagCompound(); var4.func_74782_a("Data", var3); @@ -48,7 +55,7 @@ try { File var5 = new File(this.field_75770_b, "level.dat_new"); -@@ -174,7 +180,7 @@ +@@ -174,7 +184,7 @@ NBTTagCompound var2 = p_75761_1_.func_76066_a(); NBTTagCompound var3 = new NBTTagCompound(); var3.func_74782_a("Data", var2); diff --git a/fml/patches/minecraft/net/minecraft/client/Minecraft.java.patch b/fml/patches/minecraft/net/minecraft/client/Minecraft.java.patch index 457425f5eb..8a6b8c9aea 100644 --- a/fml/patches/minecraft/net/minecraft/client/Minecraft.java.patch +++ b/fml/patches/minecraft/net/minecraft/client/Minecraft.java.patch @@ -1,6 +1,16 @@ --- ../src-base/minecraft/net/minecraft/client/Minecraft.java +++ ../src-work/minecraft/net/minecraft/client/Minecraft.java -@@ -15,7 +15,9 @@ +@@ -2,6 +2,9 @@ + + import cpw.mods.fml.common.Side; + import cpw.mods.fml.common.asm.SideOnly; ++import cpw.mods.fml.common.registry.GameRegistry; ++import cpw.mods.fml.common.registry.ItemData; ++ + import java.awt.BorderLayout; + import java.awt.Canvas; + import java.awt.Color; +@@ -15,7 +18,9 @@ import java.text.DecimalFormat; import java.util.HashMap; import java.util.List; @@ -10,7 +20,7 @@ import net.minecraft.src.AchievementList; import net.minecraft.src.AnvilSaveConverter; import net.minecraft.src.AxisAlignedBB; -@@ -122,6 +124,7 @@ +@@ -122,6 +127,7 @@ import net.minecraft.src.WorldInfo; import net.minecraft.src.WorldRenderer; import net.minecraft.src.WorldSettings; @@ -18,11 +28,13 @@ import org.lwjgl.LWJGLException; import org.lwjgl.Sys; import org.lwjgl.input.Keyboard; -@@ -134,6 +137,12 @@ +@@ -134,6 +140,14 @@ import org.lwjgl.opengl.GLContext; import org.lwjgl.opengl.PixelFormat; import org.lwjgl.util.glu.GLU; + ++import com.google.common.collect.MapDifference; ++ +import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.Side; @@ -31,7 +43,7 @@ @SideOnly(Side.CLIENT) public abstract class Minecraft implements Runnable, IPlayerUsage -@@ -316,7 +325,7 @@ +@@ -316,7 +330,7 @@ this.func_71357_I(); this.field_71466_p = new FontRenderer(this.field_71474_y, "/font/default.png", this.field_71446_o, false); this.field_71464_q = new FontRenderer(this.field_71474_y, "/font/alternate.png", this.field_71446_o, false); @@ -40,7 +52,7 @@ if (this.field_71474_y.field_74363_ab != null) { StringTranslate.func_74808_a().func_74810_a(this.field_71474_y.field_74363_ab); -@@ -361,6 +370,8 @@ +@@ -361,6 +375,8 @@ GL11.glViewport(0, 0, this.field_71443_c, this.field_71440_d); this.field_71452_i = new EffectRenderer(this.field_71441_e, this.field_71446_o); @@ -49,7 +61,7 @@ try { this.field_71430_V = new ThreadDownloadResources(this.field_71412_D, this); -@@ -389,6 +400,7 @@ +@@ -389,6 +405,7 @@ { this.func_71352_k(); } @@ -57,7 +69,7 @@ } private void func_71357_I() throws LWJGLException -@@ -747,9 +759,11 @@ +@@ -747,9 +764,11 @@ if (!this.field_71454_w) { @@ -69,7 +81,7 @@ } GL11.glFlush(); -@@ -1279,11 +1293,13 @@ +@@ -1279,11 +1298,13 @@ public void func_71407_l() { @@ -83,7 +95,7 @@ this.field_71424_I.func_76320_a("stats"); this.field_71413_E.func_77449_e(); this.field_71424_I.func_76318_c("gui"); -@@ -1739,6 +1755,7 @@ +@@ -1739,6 +1760,7 @@ this.field_71453_ak.func_74428_b(); } @@ -91,7 +103,31 @@ this.field_71424_I.func_76319_b(); this.field_71423_H = func_71386_F(); } -@@ -1993,6 +2010,12 @@ +@@ -1777,8 +1799,23 @@ + } + + this.field_71413_E.func_77450_a(StatList.field_75936_f, 1); ++ GameRegistry.initializeServerGate(2); + this.field_71437_Z = new IntegratedServer(this, p_71371_1_, p_71371_2_, p_71371_3_); + this.field_71437_Z.func_71256_s(); ++ MapDifference idDifferences = GameRegistry.gateWorldLoadingForValidation(); ++ if (idDifferences!=null) ++ { ++ FMLClientHandler.instance().warnIDMismatch(idDifferences, true); ++ } ++ else ++ { ++ GameRegistry.releaseGate(true); ++ continueWorldLoading(); ++ } ++ ++ } ++ public void continueWorldLoading() ++ { + this.field_71455_al = true; + this.field_71461_s.func_73720_a(StatCollector.func_74838_a("menu.loadingLevel")); + +@@ -1993,6 +2030,12 @@ public static void main(String[] p_main_0_) { diff --git a/fml/patches/minecraft/net/minecraft/src/GuiConnecting.java.patch b/fml/patches/minecraft/net/minecraft/src/GuiConnecting.java.patch new file mode 100644 index 0000000000..25a3a72dae --- /dev/null +++ b/fml/patches/minecraft/net/minecraft/src/GuiConnecting.java.patch @@ -0,0 +1,13 @@ +--- ../src-base/minecraft/net/minecraft/src/GuiConnecting.java ++++ ../src-work/minecraft/net/minecraft/src/GuiConnecting.java +@@ -122,4 +122,10 @@ + { + return p_74251_0_.field_73882_e; + } ++ ++ public static void forceTermination(GuiConnecting gui) ++ { ++ gui.field_74258_b = true; ++ gui.field_74259_a = null; ++ } + }