Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,7 @@
import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.upgrade.UpdateHandler;
import org.jackhuang.hmcl.util.CrashReporter;
import org.jackhuang.hmcl.util.FileSaver;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.*;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

建议避免使用通配符导入(wildcard import)。根据 Google Java 编程风格指南以及本项目现有的代码风格,应当使用具体的类导入,以提高代码的可读性并避免潜在的命名冲突。

Suggested change
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.CrashReporter;
import org.jackhuang.hmcl.util.FileSaver;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.ZlibUtils;

import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.platform.*;

Expand Down Expand Up @@ -347,6 +344,8 @@ public static void main(String[] args) {
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
}

LOG.info("Zlib Compatible: " + ZlibUtils.IS_ZLIB_COMPATIBLE);

Lang.thread(SystemInfo::initialize, "Detection System Information", true);

launch(Launcher.class, args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.ZlibUtils;
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
Expand Down Expand Up @@ -168,6 +169,22 @@ public void execute() throws Exception {
}

if (!Objects.equals(code, entry.getValue())) {
if (!ZlibUtils.IS_ZLIB_COMPATIBLE && FileUtils.getExtension(artifact).equals("jar")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

检查文件扩展名时,建议使用不区分大小写的比较(如 equalsIgnoreCase),以确保在处理具有大写扩展名(如 .JAR)的文件时也能正确触发逻辑。

Suggested change
if (!ZlibUtils.IS_ZLIB_COMPATIBLE && FileUtils.getExtension(artifact).equals("jar")) {
if (!ZlibUtils.IS_ZLIB_COMPATIBLE && "jar".equalsIgnoreCase(FileUtils.getExtension(artifact))) {

// Forge/NeoForge generates JARs dynamically during installation.
// When native compression libraries such as zlib-ng are in use,
// the resulting JAR may be compressed differently, causing its
// SHA-1 hash to differ from the expected value recorded in the
// install profile. In this case, fall back to verifying that the
// file is at least a structurally valid ZIP/JAR archive.
try {
FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER.checkIntegrity(artifact, artifact);
LOG.info("Ignoring SHA-1 mismatch for " + artifact + " due to non-standard zlib compression output");
continue;
} catch (Exception ignored) {
}
}


Files.delete(artifact);
throw new ChecksumMismatchException("SHA-1", entry.getValue(), code);
}
Expand Down
77 changes: 77 additions & 0 deletions HMCLCore/src/main/java/org/jackhuang/hmcl/util/ZlibUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;

import org.jetbrains.annotations.NotNullByDefault;

import java.util.Arrays;
import java.util.zip.Deflater;

/// Utilities for detecting the compatibility of the system's zlib implementation.
///
/// Some systems replace the JVM's default zlib with an alternative such as zlib-ng,
/// which may produce compressed output that differs byte-for-byte from the output of
/// the standard zlib. This can cause hash mismatches when verifying artifacts whose
/// expected checksums were computed with a specific zlib implementation.
///
/// @author Glavo
@NotNullByDefault
public final class ZlibUtils {

/// Whether the JVM's [Deflater] produces output that is byte-for-byte identical
/// to the expected output of the standard reference zlib for a known test vector.
///
/// This flag is `true` when the system's zlib behaves like the standard reference
/// implementation, and `false` when an alternative library such as zlib-ng is in
/// use and may produce different compressed bytes.
///
/// When this flag is `false`, hash verification for dynamically generated JAR
/// files (e.g., those produced by the Forge/NeoForge installer processors) may
/// be relaxed to a structural integrity check rather than an exact hash match,
/// because the alternative zlib may legitimately produce files with different
/// checksums.
public static boolean IS_ZLIB_COMPATIBLE;
Comment thread
Glavo marked this conversation as resolved.
Outdated

static {
var expectedCompressed = new byte[]{120, -100, 99, 96, 0, 2, 0, 0, 5, 0, 1};
byte[] compressed = new byte[64];
int compressedLength = 0;

var deflater = new Deflater();
try {
deflater.setInput(new byte[5]);
deflater.finish();

while (!deflater.finished()) {
compressedLength += deflater.deflate(compressed, compressedLength, compressed.length - compressedLength);
}
} finally {
deflater.end();
}

IS_ZLIB_COMPATIBLE = Arrays.equals(
expectedCompressed,
0, expectedCompressed.length,
compressed,
0, compressedLength
);
}

private ZlibUtils() {
}
}