/* * Forge Mod Loader * Copyright (c) 2012-2013 cpw. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v2.1 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * * Contributors: * cpw - implementation */ package cpw.mods.fml.common.registry; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import net.minecraft.block.Block; import net.minecraft.block.BlockSand; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Throwables; import com.google.common.base.Joiner.MapJoiner; import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableTable; import com.google.common.collect.ImmutableTable.Builder; import com.google.common.collect.MapDifference; import com.google.common.collect.Tables; import com.google.common.collect.MapDifference.ValueDifference; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.collect.Table; import com.google.common.collect.Table.Cell; import com.google.common.io.Files; import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.LoaderState; import cpw.mods.fml.common.ModContainer; public class GameData { private static Map<Integer, ItemData> idMap = Maps.newHashMap(); private static CountDownLatch serverValidationLatch; private static CountDownLatch clientValidationLatch; private static MapDifference<Integer, ItemData> difference; private static boolean shouldContinue = true; private static boolean isSaveValid = true; private static ImmutableTable<String, String, Integer> modObjectTable; private static Table<String, String, ItemStack> customItemStacks = HashBasedTable.create(); private static Map<String,String> ignoredMods; private static boolean isModIgnoredForIdValidation(String modId) { if (ignoredMods == null) { File f = new File(Loader.instance().getConfigDir(),"fmlIDChecking.properties"); if (f.exists()) { Properties p = new Properties(); try { p.load(new FileInputStream(f)); ignoredMods = Maps.fromProperties(p); if (ignoredMods.size()>0) { FMLLog.log("fml.ItemTracker", Level.WARNING, "Using non-empty ignored mods configuration file %s", ignoredMods.keySet()); } } catch (Exception e) { Throwables.propagateIfPossible(e); FMLLog.log("fml.ItemTracker", Level.SEVERE, e, "Failed to read ignored ID checker mods properties file"); ignoredMods = ImmutableMap.<String, String>of(); } } else { ignoredMods = ImmutableMap.<String, String>of(); } } return ignoredMods.containsKey(modId); } public static void newItemAdded(Item item) { ModContainer mc = Loader.instance().activeModContainer(); if (mc == null) { mc = Loader.instance().getMinecraftModContainer(); if (Loader.instance().hasReachedState(LoaderState.AVAILABLE)) { 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."); } } String itemType = item.getClass().getName(); ItemData itemData = new ItemData(item, mc); if (idMap.containsKey(item.itemID)) { ItemData id = idMap.get(item.itemID); FMLLog.log("fml.ItemTracker", Level.INFO, "The mod %s is overwriting existing item at %d (%s from %s) with %s", mc.getModId(), id.getItemId(), id.getItemType(), id.getModId(), itemType); } idMap.put(item.itemID, itemData); if (!"Minecraft".equals(mc.getModId())) { FMLLog.log("fml.ItemTracker",Level.FINE, "Adding item %s(%d) owned by %s", item.getClass().getName(), item.itemID, mc.getModId()); } } public static void validateWorldSave(Set<ItemData> worldSaveItems) { isSaveValid = true; shouldContinue = true; // allow ourselves to continue if there's no saved data if (worldSaveItems == null) { serverValidationLatch.countDown(); try { clientValidationLatch.await(); } catch (InterruptedException e) { } return; } Function<? super ItemData, Integer> idMapFunction = new Function<ItemData, Integer>() { public Integer apply(ItemData input) { return input.getItemId(); }; }; Map<Integer,ItemData> worldMap = Maps.uniqueIndex(worldSaveItems,idMapFunction); difference = Maps.difference(worldMap, idMap); FMLLog.log("fml.ItemTracker", Level.FINE, "The difference set is %s", difference); if (!difference.entriesDiffering().isEmpty() || !difference.entriesOnlyOnLeft().isEmpty()) { FMLLog.log("fml.ItemTracker", Level.SEVERE, "FML has detected item discrepancies"); FMLLog.log("fml.ItemTracker", Level.SEVERE, "Missing items : %s", difference.entriesOnlyOnLeft()); FMLLog.log("fml.ItemTracker", Level.SEVERE, "Mismatched items : %s", difference.entriesDiffering()); boolean foundNonIgnored = false; for (ItemData diff : difference.entriesOnlyOnLeft().values()) { if (!isModIgnoredForIdValidation(diff.getModId())) { foundNonIgnored = true; } } for (ValueDifference<ItemData> diff : difference.entriesDiffering().values()) { if (! ( isModIgnoredForIdValidation(diff.leftValue().getModId()) || isModIgnoredForIdValidation(diff.rightValue().getModId()) ) ) { foundNonIgnored = true; } } if (!foundNonIgnored) { FMLLog.log("fml.ItemTracker", Level.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()); } isSaveValid = !foundNonIgnored; serverValidationLatch.countDown(); } else { isSaveValid = true; serverValidationLatch.countDown(); } try { clientValidationLatch.await(); if (!shouldContinue) { throw new RuntimeException("This server instance is going to stop abnormally because of a fatal ID mismatch"); } } catch (InterruptedException e) { } } public static void writeItemData(NBTTagList itemList) { for (ItemData dat : idMap.values()) { itemList.appendTag(dat.toNBT()); } } /** * Initialize the server gate * @param gateCount the countdown amount. If it's 2 we're on the client and the client and server * will wait at the latch. 1 is a server and the server will proceed */ public static void initializeServerGate(int gateCount) { serverValidationLatch = new CountDownLatch(gateCount - 1); clientValidationLatch = new CountDownLatch(gateCount - 1); } public static MapDifference<Integer, ItemData> gateWorldLoadingForValidation() { try { serverValidationLatch.await(); if (!isSaveValid) { return difference; } } catch (InterruptedException e) { } difference = null; return null; } public static void releaseGate(boolean carryOn) { shouldContinue = carryOn; clientValidationLatch.countDown(); } public static Set<ItemData> buildWorldItemData(NBTTagList modList) { Set<ItemData> worldSaveItems = Sets.newHashSet(); for (int i = 0; i < modList.tagCount(); i++) { NBTTagCompound mod = (NBTTagCompound) modList.tagAt(i); ItemData dat = new ItemData(mod); worldSaveItems.add(dat); } return worldSaveItems; } static void setName(Item item, String name, String modId) { int id = item.itemID; ItemData itemData = idMap.get(id); itemData.setName(name,modId); } public static void buildModObjectTable() { if (modObjectTable != null) { throw new IllegalStateException("Illegal call to buildModObjectTable!"); } Map<Integer, Cell<String, String, Integer>> map = Maps.transformValues(idMap, new Function<ItemData,Cell<String,String,Integer>>() { public Cell<String,String,Integer> apply(ItemData data) { if ("Minecraft".equals(data.getModId()) || !data.isOveridden()) { return null; } return Tables.immutableCell(data.getModId(), data.getItemType(), data.getItemId()); } }); Builder<String, String, Integer> tBuilder = ImmutableTable.builder(); for (Cell<String, String, Integer> c : map.values()) { if (c!=null) { tBuilder.put(c); } } modObjectTable = tBuilder.build(); } static Item findItem(String modId, String name) { if (modObjectTable == null || !modObjectTable.contains(modId, name)) { return null; } return Item.itemsList[modObjectTable.get(modId, name)]; } static Block findBlock(String modId, String name) { if (modObjectTable == null) { return null; } Integer blockId = modObjectTable.get(modId, name); if (blockId == null || blockId >= Block.blocksList.length) { return null; } return Block.blocksList[blockId]; } static ItemStack findItemStack(String modId, String name) { ItemStack is = customItemStacks.get(modId, name); if (is == null) { Item i = findItem(modId, name); if (i != null) { is = new ItemStack(i, 0 ,0); } } if (is == null) { Block b = findBlock(modId, name); if (b != null) { is = new ItemStack(b, 0, Short.MAX_VALUE); } } return is; } static void registerCustomItemStack(String name, ItemStack itemStack) { customItemStacks.put(Loader.instance().activeModContainer().getModId(), name, itemStack); } public static void dumpRegistry(File minecraftDir) { if (customItemStacks == null) { return; } if (Boolean.valueOf(System.getProperty("fml.dumpRegistry", "false")).booleanValue()) { ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder(); for (String modId : customItemStacks.rowKeySet()) { builder.putAll(modId, customItemStacks.row(modId).keySet()); } File f = new File(minecraftDir, "itemStackRegistry.csv"); MapJoiner mapJoiner = Joiner.on("\n").withKeyValueSeparator(","); try { Files.write(mapJoiner.join(builder.build().entries()), f, Charsets.UTF_8); FMLLog.log(Level.INFO, "Dumped item registry data to %s", f.getAbsolutePath()); } catch (IOException e) { FMLLog.log(Level.SEVERE, e, "Failed to write registry data to %s", f.getAbsolutePath()); } } } }