001    package cpw.mods.fml.common.registry;
002    
003    import java.io.File;
004    import java.io.FileInputStream;
005    import java.io.FileNotFoundException;
006    import java.io.IOException;
007    import java.util.Map;
008    import java.util.Properties;
009    import java.util.Set;
010    import java.util.concurrent.CountDownLatch;
011    import java.util.logging.Level;
012    
013    import net.minecraft.item.Item;
014    import net.minecraft.nbt.NBTTagCompound;
015    import net.minecraft.nbt.NBTTagList;
016    
017    import com.google.common.base.Function;
018    import com.google.common.base.Throwables;
019    import com.google.common.collect.ImmutableMap;
020    import com.google.common.collect.MapDifference;
021    import com.google.common.collect.MapDifference.ValueDifference;
022    import com.google.common.collect.Maps;
023    import com.google.common.collect.Sets;
024    
025    import cpw.mods.fml.common.FMLLog;
026    import cpw.mods.fml.common.Loader;
027    import cpw.mods.fml.common.LoaderState;
028    import cpw.mods.fml.common.ModContainer;
029    
030    public class GameData {
031        private static Map<Integer, ItemData> idMap = Maps.newHashMap();
032        private static CountDownLatch serverValidationLatch;
033        private static CountDownLatch clientValidationLatch;
034        private static MapDifference<Integer, ItemData> difference;
035        private static boolean shouldContinue = true;
036        private static boolean isSaveValid = true;
037        private static Map<String,String> ignoredMods;
038    
039        private static boolean isModIgnoredForIdValidation(String modId)
040        {
041            if (ignoredMods == null)
042            {
043                File f = new File(Loader.instance().getConfigDir(),"fmlIDChecking.properties");
044                if (f.exists())
045                {
046                    Properties p = new Properties();
047                    try
048                    {
049                        p.load(new FileInputStream(f));
050                        ignoredMods = Maps.fromProperties(p);
051                        if (ignoredMods.size()>0)
052                        {
053                            FMLLog.warning("Using non-empty ignored mods configuration file %s", ignoredMods.keySet());
054                        }
055                    }
056                    catch (Exception e)
057                    {
058                        Throwables.propagateIfPossible(e);
059                        FMLLog.log(Level.SEVERE, e, "Failed to read ignored ID checker mods properties file");
060                        ignoredMods = ImmutableMap.<String, String>of();
061                    }
062                }
063                else
064                {
065                    ignoredMods = ImmutableMap.<String, String>of();
066                }
067            }
068            return ignoredMods.containsKey(modId);
069        }
070    
071        public static void newItemAdded(Item item)
072        {
073            ModContainer mc = Loader.instance().activeModContainer();
074            if (mc == null)
075            {
076                mc = Loader.instance().getMinecraftModContainer();
077                if (Loader.instance().hasReachedState(LoaderState.AVAILABLE))
078                {
079                    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.");
080                }
081            }
082            String itemType = item.getClass().getName();
083            ItemData itemData = new ItemData(item, mc);
084            if (idMap.containsKey(item.itemID))
085            {
086                ItemData id = idMap.get(item.itemID);
087                FMLLog.info("[ItemTracker] The mod %s is overwriting existing item at %d (%s from %s) with %s", mc.getModId(), id.getItemId(), id.getItemType(), id.getModId(), itemType);
088            }
089            idMap.put(item.itemID, itemData);
090            if (!"Minecraft".equals(mc.getModId()))
091            {
092                FMLLog.fine("[ItemTracker] Adding item %s(%d) owned by %s", item.getClass().getName(), item.itemID, mc.getModId());
093            }
094        }
095    
096        public static void validateWorldSave(Set<ItemData> worldSaveItems)
097        {
098            isSaveValid = true;
099            shouldContinue = true;
100            // allow ourselves to continue if there's no saved data
101            if (worldSaveItems == null)
102            {
103                serverValidationLatch.countDown();
104                try
105                {
106                    clientValidationLatch.await();
107                }
108                catch (InterruptedException e)
109                {
110                }
111                return;
112            }
113    
114            Function<? super ItemData, Integer> idMapFunction = new Function<ItemData, Integer>() {
115                public Integer apply(ItemData input) {
116                    return input.getItemId();
117                };
118            };
119    
120            Map<Integer,ItemData> worldMap = Maps.uniqueIndex(worldSaveItems,idMapFunction);
121            difference = Maps.difference(worldMap, idMap);
122            FMLLog.fine("The difference set is %s", difference);
123            if (!difference.entriesDiffering().isEmpty() || !difference.entriesOnlyOnLeft().isEmpty())
124            {
125                FMLLog.severe("FML has detected item discrepancies");
126                FMLLog.severe("Missing items : %s", difference.entriesOnlyOnLeft());
127                FMLLog.severe("Mismatched items : %s", difference.entriesDiffering());
128                boolean foundNonIgnored = false;
129                for (ItemData diff : difference.entriesOnlyOnLeft().values())
130                {
131                    if (!isModIgnoredForIdValidation(diff.getModId()))
132                    {
133                        foundNonIgnored = true;
134                    }
135                }
136                for (ValueDifference<ItemData> diff : difference.entriesDiffering().values())
137                {
138                    if (! ( isModIgnoredForIdValidation(diff.leftValue().getModId()) || isModIgnoredForIdValidation(diff.rightValue().getModId()) ) )
139                    {
140                        foundNonIgnored = true;
141                    }
142                }
143                if (!foundNonIgnored)
144                {
145                    FMLLog.severe("FML is ignoring these ID discrepancies because of configuration. YOUR GAME WILL NOW PROBABLY CRASH. HOPEFULLY YOU WON'T HAVE CORRUPTED YOUR WORLD. BLAME %s", ignoredMods.keySet());
146                }
147                isSaveValid = !foundNonIgnored;
148                serverValidationLatch.countDown();
149            }
150            else
151            {
152                isSaveValid = true;
153                serverValidationLatch.countDown();
154            }
155            try
156            {
157                clientValidationLatch.await();
158                if (!shouldContinue)
159                {
160                    throw new RuntimeException("This server instance is going to stop abnormally because of a fatal ID mismatch");
161                }
162            }
163            catch (InterruptedException e)
164            {
165            }
166        }
167    
168        public static void writeItemData(NBTTagList itemList)
169        {
170            for (ItemData dat : idMap.values())
171            {
172                itemList.appendTag(dat.toNBT());
173            }
174        }
175    
176        /**
177         * Initialize the server gate
178         * @param gateCount the countdown amount. If it's 2 we're on the client and the client and server
179         * will wait at the latch. 1 is a server and the server will proceed
180         */
181        public static void initializeServerGate(int gateCount)
182        {
183            serverValidationLatch = new CountDownLatch(gateCount - 1);
184            clientValidationLatch = new CountDownLatch(gateCount - 1);
185        }
186    
187        public static MapDifference<Integer, ItemData> gateWorldLoadingForValidation()
188        {
189            try
190            {
191                serverValidationLatch.await();
192                if (!isSaveValid)
193                {
194                    return difference;
195                }
196            }
197            catch (InterruptedException e)
198            {
199            }
200            difference = null;
201            return null;
202        }
203    
204    
205        public static void releaseGate(boolean carryOn)
206        {
207            shouldContinue = carryOn;
208            clientValidationLatch.countDown();
209        }
210    
211        public static Set<ItemData> buildWorldItemData(NBTTagList modList)
212        {
213            Set<ItemData> worldSaveItems = Sets.newHashSet();
214            for (int i = 0; i < modList.tagCount(); i++)
215            {
216                NBTTagCompound mod = (NBTTagCompound) modList.tagAt(i);
217                ItemData dat = new ItemData(mod);
218                worldSaveItems.add(dat);
219            }
220            return worldSaveItems;
221        }
222    
223        static void setName(Item item, String name, String modId)
224        {
225            int id = item.itemID;
226            ItemData itemData = idMap.get(id);
227            itemData.setName(name,modId);
228        }
229    }