HMCL-dev/HMCL · error · IOException
Theme-pack entry contains a control character
Error message
Theme-pack entry contains a control character: ${entryName} What it means
ThemePackManager scans every character of each entry-name segment and rejects names containing ISO control characters or NUL bytes. Control characters are invalid in filesystem path components and can be used to obfuscate malicious paths, so such archives are refused.
Solutions
- Repack the archive ensuring entry names use only printable, filesystem-safe characters.
- Regenerate the pack with ThemePackExporter instead of editing an existing zip.
- Treat the source archive as untrusted/corrupted; do not attempt to strip control characters to bypass the check.
Example fix
// before
zip.putNextEntry(new ZipArchiveEntry("assets/bg\u0000.png"));
// after
String safe = name.chars().filter(c -> !Character.isISOControl(c)).collect(...); // better: validate & reject before writing Defensive patterns
Strategy: validation
Validate before calling
for (var e : Collections.list(new ZipFile(pack).entries())) {
if (e.getName().chars().anyMatch(Character::isISOControl)) throw new IllegalArgumentException("control char in entry: " + e.getName());
} Try / catch
try {
ThemePackManager.install(pack, dir);
} catch (IOException e) {
if (e.getMessage().contains("control character")) {
ui.show("Theme pack rejected: entry names contain invalid characters.");
} else throw e;
} Prevention
- Validate entry names against a printable-character whitelist when creating packs.
- Reject rather than sanitize hostile archives.
- Avoid hand-editing zip structures with binary tools.
When it happens
Trigger: Installing a zip whose entry names contain characters like \u0000, \n, \t, or other control codes — typically from binary-corrupted archives or hostile packs.
Common situations: Zips whose central directory was corrupted or hand-edited; archives generated by non-Java tools embedding raw bytes in names; deliberately malicious packs.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Theme-pack entry name is not normalized
- Theme-pack entry is empty
- Theme-pack entry contains an unsafe segment
- Unsecure path:
- Not a zip file
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/e8500e707091e374.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java:1416
String normalized = entryName.trim().replace('\\', '/');
if (normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
if (normalized.isEmpty()) {
throw new IOException("Theme-pack entry is empty");
}
if (normalized.startsWith("/") || normalized.matches("^[A-Za-z]:.*")) {
throw new IOException("Theme-pack entry must be relative: " + entryName);
}
for (String segment : normalized.split("/")) {
if (segment.isEmpty() || ".".equals(segment) || "..".equals(segment)) {
throw new IOException("Theme-pack 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 IOException("Theme-pack entry contains a control character: " + entryName);
}
}
}
return normalized;
}
/// Checks that a theme-pack zip entry belongs to the current file layout.
private static void checkSupportedThemePackEntry(String entryName) throws IOException {
if (!ThemePackExporter.MANIFEST_ENTRY.equals(entryName)
&& !"assets".equals(entryName)
&& !entryName.startsWith("assets/")) {
throw new IOException("Unsupported theme-pack entry: " + entryName);
}
}
/// Deletes an existing file, symbolic link, or directory tree.
private static void deleteIfExists(Path path) throws IOException {
if (Files.exists(path) || Files.isSymbolicLink(path)) {View on GitHub (pinned to 24702dc5a0)