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 }