HMCL-dev/HMCL · error · IllegalArgumentException

Theme-pack asset entry must be relative:

Error message

Theme-pack asset entry must be relative: 

What it means

normalizeEntryName rejects entry names that are absolute paths: names starting with '/' or matching a Windows drive prefix like 'C:' throw IllegalArgumentException("Theme-pack asset entry must be relative: <name>"). Theme-pack assets are looked up inside the pack under assets/, so absolute paths are never valid.

Solutions

  1. Strip the leading '/' or drive prefix and use a pack-relative name beginning with assets/
  2. Use Paths/zip-entry relative names when building the pack instead of absolute file paths
  3. Normalize with the pack root before passing the name

Example fix

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

Strategy: validation

Validate before calling

static String toRelativeEntry(String name) {
    String n = name.replace('\\', '/');
    if (n.matches("^[A-Za-z]:.*")) n = n.substring(2);
    while (n.startsWith("/")) n = n.substring(1);
    return n;
}

Type guard

static boolean isRelative(String n) {
    String s = n.replace('\\', '/');
    return !s.startsWith("/") && !s.matches("^[A-Za-z]:.*");
}

Try / catch

try {
    ThemePackAsset.of(entryName);
} catch (IllegalArgumentException e) {
    log.warn("Ignoring non-relative asset entry: " + e.getMessage());
}

Prevention

When it happens

Trigger: Passing entries like "/assets/icon.png", "C:\\assets\\icon.png", or "D:/x.png" to ThemePackAsset / normalizeEntryName.

Common situations: Theme pack built on Windows with absolute file paths in its manifest, or code concatenating a filesystem path instead of a pack-relative entry name.

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/d041910771b1e0dc. Report an issue: GitHub.

Appendix: source

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

    public ThemePackAsset {
        Objects.requireNonNull(source);
        entryName = normalizeEntryName(entryName);
    }

    /// 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);
                }
            }

View on GitHub (pinned to 24702dc5a0)