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.warning("[ItemTracker] The mod %s is attempting to overwrite 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 }