public static List<String> processIdRematches( Iterable<MissingMapping> missedMappings, boolean isLocalWorld, GameData gameData, Map<String, Integer[]> remaps) { List<String> failed = Lists.newArrayList(); List<String> ignored = Lists.newArrayList(); List<String> warned = Lists.newArrayList(); List<String> defaulted = Lists.newArrayList(); for (MissingMapping remap : missedMappings) { FMLMissingMappingsEvent.Action action = remap.getAction(); if (action == FMLMissingMappingsEvent.Action.REMAP) { // block/item re-mapped, finish the registration with the new name/object, but the old id int currId, newId; String newName; if (remap.type == Type.BLOCK) { currId = getMain().iBlockRegistry.getId((Block) remap.getTarget()); newName = getMain().iBlockRegistry.getNameForObject(remap.getTarget()); FMLLog.fine("The Block %s is being remapped to %s.", remap.name, newName); newId = gameData.registerBlock((Block) remap.getTarget(), newName, remap.id); gameData.iBlockRegistry.addAlias(remap.name, newName); } else { currId = getMain().iItemRegistry.getId((Item) remap.getTarget()); newName = getMain().iItemRegistry.getNameForObject(remap.getTarget()); FMLLog.fine("The Item %s is being remapped to %s.", remap.name, newName); newId = gameData.registerItem((Item) remap.getTarget(), newName, remap.id); gameData.iItemRegistry.addAlias(remap.name, newName); } if (newId != remap.id) throw new IllegalStateException(); if (currId != newId) { FMLLog.info( "Fixed %s id mismatch %s: %d (init) -> %d (map).", remap.type == Type.BLOCK ? "block" : "item", newName, currId, newId); remaps.put(newName, new Integer[] {currId, newId}); } } else { // block item missing, warn as requested and block the id if (action == FMLMissingMappingsEvent.Action.DEFAULT) { defaulted.add(remap.name); } else if (action == FMLMissingMappingsEvent.Action.IGNORE) { ignored.add(remap.name); } else if (action == FMLMissingMappingsEvent.Action.FAIL) { failed.add(remap.name); } else if (action == FMLMissingMappingsEvent.Action.WARN) { warned.add(remap.name); } gameData.block(remap.id); // prevent the id from being reused later } } if (!defaulted.isEmpty()) { String text = "Forge Mod Loader detected missing blocks/items.\n\n" + "There are " + defaulted.size() + " missing blocks and items in this save.\n" + "If you continue the missing blocks/items will get removed.\n" + "A world backup will be automatically created in your saves directory.\n\n" + "Missing Blocks/Items:\n"; for (String s : defaulted) text += s + "\n"; boolean confirmed = StartupQuery.confirm(text); if (!confirmed) StartupQuery.abort(); try { ZipperUtil.backupWorld(); } catch (IOException e) { StartupQuery.notify("The world backup couldn't be created.\n\n" + e); StartupQuery.abort(); } warned.addAll(defaulted); } if (!failed.isEmpty()) { FMLLog.severe( "This world contains blocks and items that refuse to be remapped. The world will not be loaded"); return failed; } if (!warned.isEmpty()) { FMLLog.severe("This world contains block and item mappings that may cause world breakage"); return failed; } else if (!ignored.isEmpty()) { FMLLog.fine("There were %d missing mappings that have been ignored", ignored.size()); } return failed; }
/** * Fix IDs improperly allocated by early versions of the registry, best-effort. * * <p>Items sharing the same ID with a block, but not sharing the same registry name will be * mapped to an unused id. Losing items instead of blocks should minimize the damage. * * @param dataList List containing the IDs to fix */ public static void fixBrokenIds(Map<String, Integer> dataList, Set<Integer> blockedIds) { BitSet availabilityMap = new BitSet(MAX_ITEM_ID + 1); // reserve all ids occupied by blocks for (Entry<String, Integer> entry : dataList.entrySet()) { String itemName = entry.getKey(); @SuppressWarnings("unused") String realName = itemName.substring(1); if (itemName.charAt(0) == '\u0001') // is a block { availabilityMap.set(entry.getValue()); } } Set<Integer> newBlockedIds = new HashSet<Integer>(); Set<String> itemsToRemove = new HashSet<String>(); Map<String, Integer> itemsToRelocate = new HashMap<String, Integer>(); // check all ids occupied by items for (Entry<String, Integer> entry : dataList.entrySet()) { String itemName = entry.getKey(); if (itemName.charAt(0) != '\u0001') // is an item { int oldId = entry.getValue(); String realName = itemName.substring(1); String blockName = '\u0001' + realName; Item item = getMain().iItemRegistry.getRaw(realName); boolean blockThisId = false; // block oldId unless it's used by a block if (item == null) // item no longer available { // can't fix items without reliably checking if they are ItemBlocks FMLLog.warning( "Item %s (old id %d) is no longer available and thus can't be fixed.", realName, oldId); itemsToRemove.add(itemName); blockThisId = true; } else if (item instanceof ItemBlock) { if (dataList.containsKey(blockName)) // the item was an ItemBlock before { int blockId = dataList.get(blockName); if (blockId != oldId) // mis-located ItemBlock { // relocate to the matching block FMLLog.warning( "ItemBlock %s (old id %d) doesn't have the same id as its block (%d).", realName, oldId, blockId); itemsToRelocate.put(entry.getKey(), blockId); blockThisId = true; } else // intact ItemBlock { availabilityMap.set(oldId); // occupy id } } else // the item hasn't been an ItemBlock before, but it's now { // can't fix these, drop them FMLLog.warning( "Item %s (old id %d) has been migrated to an ItemBlock and can't be fixed.", realName, oldId); itemsToRemove.add(itemName); blockThisId = true; } } else if (availabilityMap.get(oldId)) // normal item, id is already occupied { // remove the item mapping FMLLog.warning( "Item %s (old id %d) is conflicting with another block/item and can't be fixed.", realName, oldId); itemsToRemove.add(itemName); } else // intact Item { availabilityMap.set(oldId); // occupy id } // handle blocking the id from future use if possible (i.e. not used by a conflicting block) // blockThisId requests don't modify availabilityMap, it could only be set by a block (or // another item, which isn't being handled) if (blockThisId && !availabilityMap.get(oldId)) { // there's no block occupying this id, thus block the id from future use // as there may still be ItemStacks in the world referencing it newBlockedIds.add(oldId); availabilityMap.set(oldId); } } } if (itemsToRemove.isEmpty() && itemsToRelocate.isEmpty()) return; // nothing to do // confirm String text = "Forge Mod Loader detected that this save is damaged.\n\n" + "It's likely that an automatic repair can successfully restore\n" + "most of it, except some items which may get swapped with others.\n\n" + "A world backup will be created as a zip file in your saves\n" + "directory automatically.\n\n" + itemsToRemove.size() + " items need to be removed.\n" + itemsToRelocate.size() + " items need to be relocated."; boolean confirmed = StartupQuery.confirm(text); if (!confirmed) StartupQuery.abort(); // confirm missing mods causing item removal Set<String> modsMissing = new HashSet<String>(); for (String itemName : itemsToRemove) { modsMissing.add(itemName.substring(1, itemName.indexOf(':'))); } for (Iterator<String> it = modsMissing.iterator(); it.hasNext(); ) { String mod = it.next(); if (mod.equals("minecraft") || Loader.isModLoaded(mod)) it.remove(); } if (!modsMissing.isEmpty()) { text = "Forge Mod Loader detected that " + modsMissing.size() + " mods are missing.\n\n" + "If you continue items previously provided by those mods will be\n" + "removed while repairing this world save.\n\n" + "Missing mods:\n"; for (String mod : modsMissing) text += mod + "\n"; confirmed = StartupQuery.confirm(text); if (!confirmed) StartupQuery.abort(); } // backup try { ZipperUtil.backupWorld(); } catch (IOException e) { StartupQuery.notify("The world backup couldn't be created.\n\n" + e); StartupQuery.abort(); } // apply fix for (String itemName : itemsToRemove) { int id = dataList.remove(itemName); FMLLog.warning("Removed Item %s, old id %d.", itemName.substring(1), id); } for (Map.Entry<String, Integer> entry : itemsToRelocate.entrySet()) { String itemName = entry.getKey(); int newId = entry.getValue(); int oldId = dataList.put(itemName, newId); FMLLog.warning("Remapped Item %s to id %d, old id %d.", itemName.substring(1), newId, oldId); } blockedIds.addAll(newBlockedIds); }