HMCL-dev/HMCL · error · IOException

Theme pack does not contain

Error message

Theme pack does not contain ${ThemePackExporter.MANIFEST_ENTRY}

What it means

Theme packs must contain a manifest entry (ThemePackExporter.MANIFEST_ENTRY, e.g. 'theme-pack.json'). After scanning all zip entries, if none matched the manifest name, ThemePackManager throws this IOException. The manifest is required to identify and validate the pack's contents.

Solutions

  1. Add the required manifest file (theme-pack.json per ThemePackExporter.MANIFEST_ENTRY) at the archive root and repack.
  2. Re-export the pack with ThemePackExporter, which always writes the manifest.
  3. Verify you selected the correct file — this error often means the zip is not a theme pack at all.

Example fix

// before
zip -r pack.zip assets/
// after
zip -r pack.zip theme-pack.json assets/
Defensive patterns

Strategy: validation

Validate before calling

try (ZipFile z = new ZipFile(pack)) {
    boolean has = Collections.list(z.entries()).stream().anyMatch(e -> e.getName().equals("theme-pack.json"));
    if (!has) throw new IllegalArgumentException("not a theme pack: missing theme-pack.json");
}

Try / catch

try {
    ThemePackManager.install(pack, dir);
} catch (IOException e) {
    if (e.getMessage().contains("does not contain")) {
      ui.show("Selected file is not a theme pack (missing theme-pack.json)");
    } else throw e;
}

Prevention

When it happens

Trigger: Installing a zip that only contains an assets/ directory without the manifest file; renaming or deleting theme-pack.json before repacking; picking an unrelated zip as a theme pack.

Common situations: Hand-assembled packs missing the manifest; users renaming the manifest file; exporting assets without using ThemePackExporter; old-format theme files predating the manifest requirement.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java:1381

                String rawEntryName = entry.getName();
                String entryName = normalizeThemePackEntryName(rawEntryName);
                String canonicalEntryName = entry.isDirectory() ? entryName + "/" : entryName;
                if (!canonicalEntryName.equals(rawEntryName)) {
                    throw new IOException("Theme-pack entry name is not normalized: " + rawEntryName);
                }
                checkSupportedThemePackEntry(entryName);

                if (!entries.add(entryName)) {
                    throw new IOException("Duplicate theme-pack entry: " + entryName);
                }
                if (ThemePackExporter.MANIFEST_ENTRY.equals(entryName) && !entry.isDirectory()) {
                    hasManifest = true;
                }
            }
        }

        if (!hasManifest) {
            throw new IOException("Theme pack does not contain " + ThemePackExporter.MANIFEST_ENTRY);
        }
    }

    /// Moves a file into place, using an atomic move when the platform supports it.
    private static void moveReplacing(Path source, Path target) throws IOException {
        try {
            Files.move(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
        } catch (AtomicMoveNotSupportedException e) {
            Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    /// Returns a normalized and safe theme-pack zip entry name.
    private static String normalizeThemePackEntryName(String entryName) throws IOException {
        Objects.requireNonNull(entryName);

        String normalized = entryName.trim().replace('\\', '/');
        if (normalized.endsWith("/")) {

View on GitHub (pinned to 24702dc5a0)