HMCL-dev/HMCL · error · IOException

"File " + modFile + " is not a Forge mod."

Error message

"File " + modFile + " is not a Forge mod."

What it means

Thrown by ForgeOldModMetadata.fromFile when the jar contains no mcmod.info entry. HMCL identifies legacy Forge (1.12 and older) mods by parsing mcmod.info at the jar root; without it the jar cannot be recognized as an old Forge mod.

Solutions

  1. Verify the jar contains mcmod.info at its root (unzip -l mod.jar | grep mcmod.info).
  2. For Forge 1.13+/NeoForge jars, use ForgeNewModMetadata.fromFile/fromNeoForgeFile instead.
  3. Re-download the mod; official legacy builds always include mcmod.info.
  4. If repackaging a legacy mod, keep mcmod.info at the archive root.

Example fix

// before
LocalModFile mod = ForgeOldModMetadata.fromFile(modManager, modernForgeJar, tree); // throws: no mcmod.info
// after
if (tree.getEntry("mcmod.info") == null && tree.getEntry("META-INF/mods.toml") != null) {
    LocalModFile mod = ForgeNewModMetadata.fromFile(modManager, modernForgeJar, tree, ModLoaderType.FORGE);
}
Defensive patterns

Strategy: validation

Validate before calling

if (tree.getEntry("mcmod.info") == null) {
    // legacy Forge parser will throw; route elsewhere
    return null;
}

Type guard

boolean isLegacyForgeMod(ZipFileTree tree) { return tree.getEntry("mcmod.info") != null; }

Try / catch

try {
    LocalModFile mod = ForgeOldModMetadata.fromFile(modManager, modFile, tree);
} catch (IOException e) {
    LOG.warning("Skipping jar without mcmod.info: " + modFile);
}

Prevention

When it happens

Trigger: Calling ForgeOldModMetadata.fromFile(modManager, modFile, tree) on a jar whose ZipFileTree has no entry named 'mcmod.info'.

Common situations: Pointing the legacy parser at a 1.13+ Forge/NeoForge mod (uses mods.toml) or a Fabric mod (fabric.mod.json); a repackaged jar where mcmod.info was deleted; non-mod library jars in the mods folder.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10). Data as JSON: /api/errors/0d568dbbb73c7518. Report an issue: GitHub.

Appendix: source

Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java:130

        return updateUrl;
    }

    public String getCredits() {
        return credits;
    }

    public String[] getAuthorList() {
        return authorList;
    }

    public String[] getAuthors() {
        return authors;
    }

    public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {
        ZipArchiveEntry mcmod = tree.getEntry("mcmod.info");
        if (mcmod == null)
            throw new IOException("File " + modFile + " is not a Forge mod.");

        List<ForgeOldModMetadata> modList;

        try (var reader = tree.getBufferedReader(mcmod);
             var jsonReader = new JsonReader(reader)) {
            JsonToken firstToken = jsonReader.peek();

            if (firstToken == JsonToken.BEGIN_ARRAY)
                modList = JsonUtils.GSON.fromJson(jsonReader, listTypeOf(ForgeOldModMetadata.class));
            else if (firstToken == JsonToken.BEGIN_OBJECT) {
                ForgeOldModMetadataLst list = JsonUtils.GSON.fromJson(jsonReader, ForgeOldModMetadataLst.class);
                if (list == null)
                    throw new IOException("Mod " + modFile + " `mcmod.info` is malformed");
                modList = list.modList();
            } else {
                throw new JsonParseException("Unexpected first token: " + firstToken);
            }
        }

View on GitHub (pinned to 24702dc5a0)