public static List<String> processIdRematches(
      Iterable<MissingMapping> missedMappings,
      boolean isLocalWorld,
      GameData gameData,
      Map<String, Integer[]> remapBlocks,
      Map<String, Integer[]> remapItems) {
    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()).toString();
          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()).toString();
          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);
          (remap.type == Type.BLOCK ? remapBlocks : remapItems)
              .put(newName, new Integer[] {currId, newId});
        }
      } else if (action == FMLMissingMappingsEvent.Action.BLOCKONLY) {
        // Pulled out specifically so the block doesn't get reassigned a new ID just because it's
        // Item block has gone away
        FMLLog.fine(
            "The ItemBlock %s is no longer present in the game. The residual block will remain",
            remap.name);
      } 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 {
        String skip = System.getProperty("fml.doNotBackup");
        if (skip == null || !"true".equals(skip)) {
          ZipperUtil.backupWorld();
        } else {
          for (int x = 0; x < 10; x++)
            FMLLog.severe("!!!!!!!!!! UPDATING WORLD WITHOUT DOING BACKUP !!!!!!!!!!!!!!!!");
        }
      } 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;
  }
  public static List<String> injectSnapshot(
      GameDataSnapshot snapshot, boolean injectFrozenData, boolean isLocalWorld) {
    FMLLog.info(
        "Injecting existing block and item data into this %s instance",
        FMLCommonHandler.instance().getEffectiveSide().isServer() ? "server" : "client");
    Map<String, Integer[]> remapBlocks = Maps.newHashMap();
    Map<String, Integer[]> remapItems = Maps.newHashMap();
    LinkedHashMap<String, Integer> missingBlocks = new LinkedHashMap<String, Integer>();
    LinkedHashMap<String, Integer> missingItems = new LinkedHashMap<String, Integer>();
    getMain().testConsistency();
    getMain().iBlockRegistry.dump();
    getMain().iItemRegistry.dump();

    getMain().iItemRegistry.resetSubstitutionDelegates();

    GameDataSnapshot.Entry blocks = snapshot.entries.get("fml:blocks");
    GameDataSnapshot.Entry items = snapshot.entries.get("fml:items");

    GameData newData = new GameData();

    for (int id : blocks.blocked) {
      newData.block(id);
    }

    for (Map.Entry<String, String> entry : blocks.aliases.entrySet()) {
      newData.iBlockRegistry.addAlias(entry.getKey(), entry.getValue());
    }

    for (Map.Entry<String, String> entry : items.aliases.entrySet()) {
      newData.iItemRegistry.addAlias(entry.getKey(), entry.getValue());
    }

    for (String entry : blocks.substitutions) {
      newData.iBlockRegistry.activateSubstitution(entry);
    }
    for (String entry : items.substitutions) {
      newData.iItemRegistry.activateSubstitution(entry);
    }
    if (injectFrozenData) {
      for (String newBlockSubstitution : getMain().blockSubstitutions.keySet()) {
        if (!blocks.substitutions.contains(newBlockSubstitution)) {
          newData.iBlockRegistry.activateSubstitution(newBlockSubstitution);
        }
      }
      for (String newItemSubstitution : getMain().itemSubstitutions.keySet()) {
        if (!items.substitutions.contains(newItemSubstitution)) {
          newData.iItemRegistry.activateSubstitution(newItemSubstitution);
        }
      }
    }

    // Clear State map for it's ready for us to register below.
    GameData.BLOCKSTATE_TO_ID.clear();

    // process blocks and items in the world, blocks in the first pass, items in the second
    // blocks need to be added first for proper ItemBlock handling
    for (int pass = 0; pass < 2; pass++) {
      boolean isBlock = (pass == 0);
      Map<String, Integer> ids = (isBlock ? blocks.ids : items.ids);

      for (Entry<String, Integer> entry : ids.entrySet()) {
        String itemName = entry.getKey();
        int newId = entry.getValue();
        int currId =
            isBlock
                ? getMain().iBlockRegistry.getId(itemName)
                : getMain().iItemRegistry.getId(itemName);

        if (currId == -1) {
          FMLLog.info("Found a missing id from the world %s", itemName);
          (isBlock ? missingBlocks : missingItems).put(entry.getKey(), newId);
          continue; // no block/item -> nothing to add
        } else if (currId != newId) {
          FMLLog.fine(
              "Fixed %s id mismatch %s: %d (init) -> %d (map).",
              isBlock ? "block" : "item", itemName, currId, newId);
          (isBlock ? remapBlocks : remapItems).put(itemName, new Integer[] {currId, newId});
        }

        // register
        if (isBlock) {
          currId =
              newData.registerBlock(getMain().iBlockRegistry.getRaw(itemName), itemName, newId);
        } else {
          currId = newData.registerItem(getMain().iItemRegistry.getRaw(itemName), itemName, newId);
        }

        if (currId != newId) {
          throw new IllegalStateException(
              String.format(
                  "Can't map %s %s to id %d (seen at: %d), already occupied by %s, blocked %b, ItemBlock %b",
                  isBlock ? "block" : "item",
                  itemName,
                  newId,
                  currId,
                  isBlock
                      ? newData.iBlockRegistry.getRaw(newId)
                      : newData.iItemRegistry.getRaw(newId),
                  newData.blockedIds.contains(newId),
                  isBlock ? false : (getMain().iItemRegistry.getRaw(currId) instanceof ItemBlock)));
        }
      }
    }

    List<String> missedMappings =
        Loader.instance()
            .fireMissingMappingEvent(
                missingBlocks, missingItems, isLocalWorld, newData, remapBlocks, remapItems);
    if (!missedMappings.isEmpty()) return missedMappings;

    // If we got here - the load was accepted. We'll load generic repositories here.
    // Generic registries can fail by returning a missing mapping.
    missedMappings = newData.loadGenericRegistries(snapshot, getMain());
    if (!missedMappings.isEmpty()) return missedMappings;

    if (injectFrozenData) // add blocks + items missing from the map
    {
      Map<String, Integer> newBlocks =
          frozen.iBlockRegistry.getEntriesNotIn(newData.iBlockRegistry);
      Map<String, Integer> newItems = frozen.iItemRegistry.getEntriesNotIn(newData.iItemRegistry);

      if (!newBlocks.isEmpty() || !newItems.isEmpty()) {
        FMLLog.info("Injecting new block and item data into this server instance.");

        for (int pass = 0; pass < 2; pass++) {
          boolean isBlock = pass == 0;
          Map<String, Integer> missing = (pass == 0) ? newBlocks : newItems;
          Map<String, Integer[]> remaps = (isBlock ? remapBlocks : remapItems);

          for (Entry<String, Integer> entry : missing.entrySet()) {
            String itemName = entry.getKey();
            int currId = entry.getValue();
            int newId;

            if (isBlock) {
              newId =
                  newData.registerBlock(frozen.iBlockRegistry.getRaw(itemName), itemName, currId);
            } else {
              newId = newData.registerItem(frozen.iItemRegistry.getRaw(itemName), itemName, currId);
            }

            FMLLog.info(
                "Injected new block/item %s: %d (init) -> %d (map).", itemName, currId, newId);

            if (newId != currId) // a new id was assigned
            {
              remaps.put(itemName, new Integer[] {entry.getValue(), newId});
            }
          }
        }
      }
    }

    newData.testConsistency();
    getMain().set(newData);

    getMain().iBlockRegistry.dump();
    getMain().iItemRegistry.dump();
    Loader.instance().fireRemapEvent(remapBlocks, remapItems);
    // The id map changed, ensure we apply object holders
    ObjectHolderRegistry.INSTANCE.applyObjectHolders();
    return ImmutableList.of();
  }