    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            if (!"Minecraft".equals(mc.getModId()))
    091            {
    092                FMLLog.fine("[ItemTracker] Adding item %s(%d) owned by %s", item.getClass().getName(), item.shiftedIndex, 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.shiftedIndex;
    226            ItemData itemData = idMap.get(id);
    227            itemData.setName(name,modId);
    228        }
    229    }