HMCL-dev/HMCL · error · IOException

Theme-pack entry name is not normalized

Error message

Theme-pack entry name is not normalized: ${rawEntryName}

What it means

When installing a theme pack, ThemePackManager iterates zip entries and normalizes each name (backslashes to forward slashes, trailing slash stripped). If the normalized name differs from the raw zip entry name, the archive is rejected with this IOException. This enforces a strict, portable entry-name layout so extraction paths are deterministic.

Solutions

  1. Recreate the zip so every entry uses forward-slash paths and no redundant trailing slashes (e.g. `zip -r pack.zip assets theme-pack.json` from a Unix tool).
  2. Re-export the theme pack with the library's own ThemePackExporter instead of repacking manually.
  3. Normalize entry names in the archive with a zip-repair tool before installing.
  4. If you control the producing code, write ZipArchiveEntry names via Path.getName() joined with '/' rather than raw OS paths.

Example fix

// before
zip.putNextEntry(new ZipArchiveEntry("assets\\background.png"));
// after
zip.putNextEntry(new ZipArchiveEntry("assets/background.png"));
Defensive patterns

Strategy: validation

Validate before calling

try (ZipFile z = new ZipFile(pack)) {
    for (var e : Collections.list(z.entries())) {
        String n = e.getName().replace('\\', '/');
        if (!e.getName().equals(n) || n.endsWith("/")) throw new IllegalArgumentException("non-normalized entry: " + e.getName());
    }
}

Try / catch

try {
    ThemePackManager.install(pack, dir);
} catch (IOException e) {
    if (e.getMessage().contains("not normalized")) {
        repackNormalized(pack);
        ThemePackManager.install(pack, dir);
    } else throw e;
}

Prevention

When it happens

Trigger: Opening a theme-pack zip whose entries were created with Windows-style backslash separators, redundant trailing slashes on file entries, or non-canonical names produced by a third-party zip tool.

Common situations: Repacking a theme pack on Windows with a tool that writes \ separators; zips produced by scripts appending '/' inconsistently; archives edited/rewritten by older library versions with different normalization rules.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

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

            if (themePack.manifest().id().equals(packId)) {
                return themePack;
            }
        }
        return null;
    }

    /// Validates all zip entries in a theme-pack file.
    private static void validateThemePackFile(Path themePackFile) throws IOException {
        Set<String> entries = new HashSet<>();
        boolean hasManifest = false;

        try (ZipArchiveReader zipFile = new ZipArchiveReader(themePackFile, StandardCharsets.UTF_8)) {
            for (ZipArchiveEntry entry : zipFile.getEntries()) {
                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.

View on GitHub (pinned to 24702dc5a0)