{"record":{"id":"d02bc9a3dfaec560","repo":"HMCL-dev/HMCL","slug":"theme-pack-asset-entry-must-be-under-assets","errorCode":null,"errorMessage":"Theme-pack asset entry must be under assets/: ","messagePattern":"Theme-pack asset entry must be under assets/: ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackAsset.java","lineNumber":68,"sourceCode":"    }\n\n    /// Normalizes and validates a zip entry name.\n    ///\n    /// @param entryName the entry name to validate\n    /// @return the normalized entry name\n    /// @throws IllegalArgumentException if the entry name is unsafe or outside `assets/`\n    static String normalizeEntryName(String entryName) {\n        Objects.requireNonNull(entryName);\n\n        String normalized = entryName.trim().replace('\\\\', '/');\n        if (normalized.isEmpty()) {\n            throw new IllegalArgumentException(\"Theme-pack asset entry is empty\");\n        }\n        if (normalized.startsWith(\"/\") || normalized.matches(\"^[A-Za-z]:.*\")) {\n            throw new IllegalArgumentException(\"Theme-pack asset entry must be relative: \" + entryName);\n        }\n        if (!normalized.startsWith(ASSETS_PREFIX)) {\n            throw new IllegalArgumentException(\"Theme-pack asset entry must be under assets/: \" + entryName);\n        }\n        if (normalized.endsWith(\"/\")) {\n            throw new IllegalArgumentException(\"Theme-pack asset entry must be a file: \" + entryName);\n        }\n\n        for (String segment : normalized.split(\"/\")) {\n            if (segment.isEmpty() || \".\".equals(segment) || \"..\".equals(segment)) {\n                throw new IllegalArgumentException(\"Theme-pack asset entry contains an unsafe segment: \" + entryName);\n            }\n            for (int i = 0; i < segment.length(); i++) {\n                char ch = segment.charAt(i);\n                if (Character.isISOControl(ch) || ch == '\\0') {\n                    throw new IllegalArgumentException(\"Theme-pack asset entry contains a control character: \" + entryName);\n                }\n            }\n        }\n        return normalized;\n    }","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/HMCL-dev/HMCL/blob/24702dc5a0214034f4c27166d5fd30cad08cec19/HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackAsset.java#L50-L86","documentation":"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.","triggerScenarios":"Passing entry names like \"icon.png\", \"data/icon.png\", or \"theme.json\" (no assets/ prefix) to ThemePackAsset / normalizeEntryName.","commonSituations":"Author places files at the ZIP root instead of assets/, or code builds names from a config key missing the assets/ prefix.","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"],"exampleFix":"// before\nThemePackAsset.of(\"icon.png\");\n// after\nThemePackAsset.of(\"assets/icon.png\");","handlingStrategy":"validation","validationCode":"static boolean isUnderAssets(String name) {\n    return name.replace('\\\\', '/').startsWith(\"assets/\");\n}","typeGuard":"static boolean hasAssetsPrefix(String n) {\n    return n.startsWith(\"assets/\");\n}","tryCatchPattern":"try {\n    ThemePackAsset.of(entryName);\n} catch (IllegalArgumentException e) {\n    log.warn(\"Ignoring entry outside assets/: \" + e.getMessage());\n}","preventionTips":["Place all pack files under the assets/ directory","Prefix configured names with assets/ in calling code","Check pack layout after building the ZIP"],"tags":["validation","theme-pack","path"],"backgroundTag":"invalid-argument-value","analyzedSha":"24702dc5a0214034f4c27166d5fd30cad08cec19","analyzedAt":"2026-09-10T12:36:46.680Z","contentChangedAt":"2026-09-10T12:36:46.680Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}