HMCL-dev/HMCL · error · IOException

Built-in theme-pack asset is missing

Error message

Built-in theme-pack asset is missing: ${entryName}

What it means

ThemePackManager resolves built-in theme-pack assets from the classpath at /assets/themes/<id>/<entryName>. This IOException is thrown when that classpath resource does not exist, meaning the packaged jar is missing the expected bundled asset. It signals a build/packaging defect rather than a user problem, since built-in assets ship with the library.

Solutions

  1. Verify /assets/themes/<id>/<entryName> exists in the jar on the classpath (unzip -l hmcl.jar | grep assets/themes).
  2. Rebuild the project so theme resources are copied into the jar; check build config resource excludes.
  3. Confirm the theme id and entryName are valid for this library version (names may have changed between versions).
  4. If using a custom classloader, ensure it can load resources from the HMCL jar.

Example fix

// before
ThemePackResource res = ThemePackManager.resolveBuiltinAsset("legacy", "theme.css");
// after
if (ThemePackManager.builtinAssetExists("modern", "theme.css")) {
    ThemePackResource res = ThemePackManager.resolveBuiltinAsset("modern", "theme.css");
} else {
    ThemePackResource res = ThemePackManager.resolveDefaultAsset();
}
Defensive patterns

Strategy: fallback

Validate before calling

boolean ok = ThemePackManager.class.getResourceAsStream("/assets/themes/" + id + "/" + entryName) != null;
if (!ok) useDefaultThemePack();

Try / catch

try {
    resource = ThemePackManager.resolveBuiltinAsset(id, entryName);
} catch (IOException e) {
    LOG.warn("builtin theme asset missing: {}", e.getMessage());
    resource = ThemePackManager.resolveDefaultAsset();
}

Prevention

When it happens

Trigger: Calling the public built-in asset resolver (ThemePackManager, public API) with a theme-pack id + entryName whose path /assets/themes/<id>/<entryName> has no classpath resource; input from getResourceAsStream is null.

Common situations: Running against a partially built or shaded jar where assets were excluded by build filters; referencing a theme id or entry that was removed/renamed in a newer version; custom classloaders that do not expose the jar's resources.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at HMCL/src/main/java/org/jackhuang/hmcl/theme/ThemePackManager.java:1241

        try (ZipArchiveReader zipFile = new ZipArchiveReader(installedFile, StandardCharsets.UTF_8)) {
            ZipArchiveEntry entry = zipFile.getEntry(normalizedEntryName);
            if (entry == null || entry.isDirectory()) {
                throw new IOException("Installed theme-pack asset is missing: " + normalizedEntryName);
            }
        }
        return new ThemePackResource.Zip(installedFile, normalizedEntryName);
    }

    /// Resolves one asset stored in a bundled theme pack.
    private static ThemePackResource resolveBuiltinAsset(ThemePackLocation.Builtin builtin, String entryName) throws IOException {
        String id = ThemePackManifest.requirePackageId(builtin.id());
        String resourcePath = "/assets/themes/" + id + "/" + entryName;
        try (InputStream input = ThemePackManager.class.getResourceAsStream(resourcePath)) {
            if (input != null) {
                return new ThemePackResource.Builtin(resourcePath, entryName);
            }
        }
        throw new IOException("Built-in theme-pack asset is missing: " + entryName);
    }

    /// Returns the installed theme-pack file under a theme-pack directory for one package ID.
    private static Path installedThemePackFile(Path themePacksDirectory, String packId) {
        String id = ThemePackManifest.requirePackageId(packId);
        return themePacksDirectory
                .resolve(id + ThemePackExporter.FILE_EXTENSION)
                .toAbsolutePath()
                .normalize();
    }

    /// Returns whether a path is an installed theme-pack file candidate.
    private static boolean isInstalledThemePackFile(Path path) {
        String fileName = path.getFileName().toString();
        return !fileName.startsWith(".")
                && fileName.endsWith(ThemePackExporter.FILE_EXTENSION);
    }

View on GitHub (pinned to 24702dc5a0)