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.shiftedIndex))
085            {
086                ItemData id = idMap.get(item.shiftedIndex);
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.shiftedIndex, itemData);
090            FMLLog.fine("[ItemTracker] Adding item %s(%d) owned by %s", item.getClass().getName(), item.shiftedIndex, mc.getModId());
091        }
092    
093        public static void validateWorldSave(Set<ItemData> worldSaveItems)
094        {
095            isSaveValid = true;
096            shouldContinue = true;
097            // allow ourselves to continue if there's no saved data
098            if (worldSaveItems == null)
099            {
100                serverValidationLatch.countDown();
101                try
102                {
103                    clientValidationLatch.await();
104                }
105                catch (InterruptedException e)
106                {
107                }
108                return;
109            }
110    
111            Function<? super ItemData, Integer> idMapFunction = new Function<ItemData, Integer>() {
112                public Integer apply(ItemData input) {
113                    return input.getItemId();
114                };
115            };
116    
117            Map<Integer,ItemData> worldMap = Maps.uniqueIndex(worldSaveItems,idMapFunction);
118            difference = Maps.difference(worldMap, idMap);
119            FMLLog.fine("The difference set is %s", difference);
120            if (!difference.entriesDiffering().isEmpty() || !difference.entriesOnlyOnLeft().isEmpty())
121            {
122                FMLLog.severe("FML has detected item discrepancies");
123                FMLLog.severe("Missing items : %s", difference.entriesOnlyOnLeft());
124                FMLLog.severe("Mismatched items : %s", difference.entriesDiffering());
125                boolean foundNonIgnored = false;
126                for (ItemData diff : difference.entriesOnlyOnLeft().values())
127                {
128                    if (!isModIgnoredForIdValidation(diff.getModId()))
129                    {
130                        foundNonIgnored = true;
131                    }
132                }
133                for (ValueDifference<ItemData> diff : difference.entriesDiffering().values())
134                {
135                    if (! ( isModIgnoredForIdValidation(diff.leftValue().getModId()) || isModIgnoredForIdValidation(diff.rightValue().getModId()) ) )
136                    {
137                        foundNonIgnored = true;
138                    }
139                }
140                if (!foundNonIgnored)
141                {
142                    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());
143                }
144                isSaveValid = !foundNonIgnored;
145                serverValidationLatch.countDown();
146            }
147            else
148            {
149                isSaveValid = true;
150                serverValidationLatch.countDown();
151            }
152            try
153            {
154                clientValidationLatch.await();
155                if (!shouldContinue)
156                {
157                    throw new RuntimeException("This server instance is going to stop abnormally because of a fatal ID mismatch");
158                }
159            }
160            catch (InterruptedException e)
161            {
162            }
163        }
164    
165        public static void writeItemData(NBTTagList itemList)
166        {
167            for (ItemData dat : idMap.values())
168            {
169                itemList.appendTag(dat.toNBT());
170            }
171        }
172    
173        /**
174         * Initialize the server gate
175         * @param gateCount the countdown amount. If it's 2 we're on the client and the client and server
176         * will wait at the latch. 1 is a server and the server will proceed
177         */
178        public static void initializeServerGate(int gateCount)
179        {
180            serverValidationLatch = new CountDownLatch(gateCount - 1);
181            clientValidationLatch = new CountDownLatch(gateCount - 1);
182        }
183    
184        public static MapDifference<Integer, ItemData> gateWorldLoadingForValidation()
185        {
186            try
187            {
188                serverValidationLatch.await();
189                if (!isSaveValid)
190                {
191                    return difference;
192                }
193            }
194            catch (InterruptedException e)
195            {
196            }
197            difference = null;
198            return null;
199        }
200    
201    
202        public static void releaseGate(boolean carryOn)
203        {
204            shouldContinue = carryOn;
205            clientValidationLatch.countDown();
206        }
207    
208        public static Set<ItemData> buildWorldItemData(NBTTagList modList)
209        {
210            Set<ItemData> worldSaveItems = Sets.newHashSet();
211            for (int i = 0; i < modList.tagCount(); i++)
212            {
213                NBTTagCompound mod = (NBTTagCompound) modList.tagAt(i);
214                ItemData dat = new ItemData(mod);
215                worldSaveItems.add(dat);
216            }
217            return worldSaveItems;
218        }
219    
220        static void setName(Item item, String name, String modId)
221        {
222            int id = item.shiftedIndex;
223            ItemData itemData = idMap.get(id);
224            itemData.setName(name,modId);
225        }
226    }