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 }