HMCL-dev/HMCL · error · IllegalArgumentException

Theme-pack asset entry must be under assets/:

Error message

Theme-pack asset entry must be under assets/: 

What it means

All theme-pack asset entries must live under the assets/ prefix. A normalized name that does not start with ASSETS_PREFIX throws IllegalArgumentException("Theme-pack asset entry must be under assets/: <name>"). This confines lookups to the pack's asset directory.

Solutions

  1. Move the file into the assets/ directory of the theme pack and use "assets/<file>" as the entry name
  2. Prefix the configured name with assets/ in the calling code
  3. Fix the pack manifest so entry names include the assets/ prefix

Example fix

// before
ThemePackAsset.of("icon.png");
// after
ThemePackAsset.of("assets/icon.png");
Defensive patterns

Strategy: validation

Validate before calling

static boolean isUnderAssets(String name) {
    return name.replace('\\', '/').startsWith("assets/");
}

Type guard

static boolean hasAssetsPrefix(String n) {
    return n.startsWith("assets/");
}

Try / catch

try {
    ThemePackAsset.of(entryName);
} catch (IllegalArgumentException e) {
    log.warn("Ignoring entry outside assets/: " + e.getMessage());
}

Prevention

When it happens

Trigger: Passing entry names like "icon.png", "data/icon.png", or "theme.json" (no assets/ prefix) to ThemePackAsset / normalizeEntryName.

Common situations: Author places files at the ZIP root instead of assets/, or code builds names from a config key missing the assets/ prefix.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackAsset.java:68

    }

    /// Normalizes and validates a zip entry name.
    ///
    /// @param entryName the entry name to validate
    /// @return the normalized entry name
    /// @throws IllegalArgumentException if the entry name is unsafe or outside `assets/`
    static String normalizeEntryName(String entryName) {
        Objects.requireNonNull(entryName);

        String normalized = entryName.trim().replace('\\', '/');
        if (normalized.isEmpty()) {
            throw new IllegalArgumentException("Theme-pack asset entry is empty");
        }
        if (normalized.startsWith("/") || normalized.matches("^[A-Za-z]:.*")) {
            throw new IllegalArgumentException("Theme-pack asset entry must be relative: " + entryName);
        }
        if (!normalized.startsWith(ASSETS_PREFIX)) {
            throw new IllegalArgumentException("Theme-pack asset entry must be under assets/: " + entryName);
        }
        if (normalized.endsWith("/")) {
            throw new IllegalArgumentException("Theme-pack asset entry must be a file: " + entryName);
        }

        for (String segment : normalized.split("/")) {
            if (segment.isEmpty() || ".".equals(segment) || "..".equals(segment)) {
                throw new IllegalArgumentException("Theme-pack asset entry contains an unsafe segment: " + entryName);
            }
            for (int i = 0; i < segment.length(); i++) {
                char ch = segment.charAt(i);
                if (Character.isISOControl(ch) || ch == '\0') {
                    throw new IllegalArgumentException("Theme-pack asset entry contains a control character: " + entryName);
                }
            }
        }
        return normalized;
    }

View on GitHub (pinned to 24702dc5a0)