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
- Move the file into the assets/ directory of the theme pack and use "assets/<file>" as the entry name
- Prefix the configured name with assets/ in the calling code
- 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
- Place all pack files under the assets/ directory
- Prefix configured names with assets/ in calling code
- Check pack layout after building the ZIP
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
- Theme-pack asset entry must be relative:
- Theme-pack asset entry must be a file:
- Theme-pack asset entry is empty
- Theme-pack asset entry contains a control character:
- Theme-pack author must be an object or a string
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)