Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
ab5c053
[Feature] 让HMCL可以识别到JIJ的打包形式并以下拉列表的形式显示,并支持搜索和导出JIJ信息,且在崩溃时若有JIJ信息则一并…
Lokins577 Jun 26, 2026
ba21105
更改按钮位置,自动隐藏溢出标题、恢复按钮
Lokins577 Jun 26, 2026
a7ca47f
支持模组关系检测,优化刷新列表时的关系与内嵌检测逻辑
Lokins577 Jun 27, 2026
d4d08b1
修复错误的动画渲染
Lokins577 Jun 27, 2026
08e40c3
模组禁用/删除时检测并提示反向依赖,支持级联处理
Lokins577 Jun 27, 2026
94cdf51
支持在模组下载页里对必需模组的安装状态进行检测与标记
Lokins577 Jun 28, 2026
9349260
优化已有功能、更改模组名过长的截断逻辑、修复歧义文案及缺失的模组依赖忽略集、增加平滑动画
Lokins577 Jun 28, 2026
7bb5da6
增加按钮点击动画
Lokins577 Jun 28, 2026
138a6eb
Merge remote-tracking branch 'upstream/main' into 5332JIJv2.0
Lokins577 Jun 30, 2026
91a80ac
Merge remote-tracking branch 'upstream/main' into 5332JIJv2.0
Lokins577 Jul 10, 2026
60272e3
Update HMCL/src/main/resources/assets/lang/I18N_zh.properties
Lokins577 Jul 10, 2026
2eb3041
copyFile 之前先驱逐同名路径的旧缓存条目
Lokins577 Jul 11, 2026
c694784
处理review反馈的问题
Lokins577 Jul 12, 2026
b47a9d6
引入递归扫描,渐进式触发,更新UI
Lokins577 Jul 12, 2026
522f875
将JIJ树写入持久化文件并建立复用机制
Lokins577 Jul 12, 2026
774b122
修复已知问题,增加了一些新功能
Lokins577 Jul 13, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ public Path getInstanceConfigDirectory(String id) {
}

/// Returns the HMCL-managed state directory under the instance metadata directory.
@Override
public Path getInstanceStateDirectory(String id) {
return getInstanceMetadataDirectory(id).resolve(INSTANCE_STATE_DIRECTORY);
}
Expand Down
164 changes: 164 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/LogExporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
*/
package org.jackhuang.hmcl.game;

import org.jackhuang.hmcl.addon.mod.LocalModFile;
import org.jackhuang.hmcl.addon.mod.MinecraftVersionMatcher;
import org.jackhuang.hmcl.addon.mod.ModManager;
import org.jackhuang.hmcl.addon.mod.NestedJarInspector.NestedJar;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.io.Zipper;
Expand All @@ -32,7 +36,11 @@
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

import static org.jackhuang.hmcl.util.logging.Logger.LOG;
Expand Down Expand Up @@ -74,6 +82,141 @@ public static CompletableFuture<Void> exportLogs(
zipper.putTextFile(logs, "minecraft.log");
zipper.putTextFile(Logger.filterForbiddenToken(launchScript), OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "launch.bat" : "launch.sh");

try {
ModManager modManager = gameRepository.getModManager(versionId);
modManager.refresh();
// Resolve the full Jar-in-Jar tree now (synchronously — this whole export runs on a
// background task), so the report is accurate even when the mod manager was never
// opened before the crash.
modManager.scanBundledTrees();

List<LocalModFile> activeMods = modManager.getLocalFiles().stream()
.filter(LocalModFile::isActive)
.sorted((m1, m2) -> String.CASE_INSENSITIVE_ORDER.compare(m1.getName(), m2.getName()))
.toList();

StringBuilder infoBuilder = new StringBuilder();

// Mods that exist both as a standalone file and inside another active mod's
// Jar-in-Jar payload. Such duplicates can conflict and crash the game, so list
// them at the very top of the report.
// Matched by exact mod id against the scanned Jar-in-Jar tree (all depths), so a
// standalone mod that is also shipped inside another active mod is caught precisely
// — no filename guessing, no false positives.
LinkedHashSet<String> duplicates = new LinkedHashSet<>();
for (LocalModFile host : activeMods) {
Set<String> bundledIds = host.getAllBundledModIds();
if (bundledIds.isEmpty()) continue;
for (LocalModFile other : activeMods) {
if (other == host) continue;
String id = other.getId();
if (StringUtils.isBlank(id)) continue;
if (bundledIds.contains(id)) {
duplicates.add(other.getName() + " [" + other.getFileName() + "] <-> bundled in " + host.getName() + " (" + id + ")");
}
}
}

infoBuilder.append("=== Potential Duplicate Mods (installed separately AND bundled via Jar-in-Jar) ===").append(System.lineSeparator());
if (duplicates.isEmpty()) {
infoBuilder.append("None").append(System.lineSeparator());
} else {
infoBuilder.append("These mods may conflict with their bundled copies and cause crashes:").append(System.lineSeparator());
infoBuilder.append("(Matched by exact mod id against the scanned Jar-in-Jar tree.)").append(System.lineSeparator());
for (String line : duplicates) {
infoBuilder.append("\t|-> ").append(line).append(System.lineSeparator());
}
}
infoBuilder.append(System.lineSeparator())
.append("----------------------------").append(System.lineSeparator())
.append(System.lineSeparator());

// Multi-version "wrapper" mods that bundle one copy per game version but have no
// copy targeting this instance's Minecraft version — the wrapper can't load
// anything, a likely crash cause.
String instanceMc = gameRepository.getGameVersion(versionId).orElse(null);
LinkedHashSet<String> incompatible = new LinkedHashSet<>();
if (StringUtils.isNotBlank(instanceMc)) {
for (LocalModFile host : activeMods) {
Map<String, List<NestedJar>> byId = new LinkedHashMap<>();
for (NestedJar node : host.getBundledTree())
if (StringUtils.isNotBlank(node.id()))
byId.computeIfAbsent(node.id(), k -> new ArrayList<>()).add(node);
for (Map.Entry<String, List<NestedJar>> e : byId.entrySet()) {
List<NestedJar> copies = e.getValue();
if (copies.size() > 1 && copies.stream().noneMatch(n -> MinecraftVersionMatcher.matches(n, instanceMc)))
incompatible.add(e.getKey() + " (bundled in " + host.getName() + ", " + copies.size() + " versions, none for MC " + instanceMc + ")");
}
}
}

infoBuilder.append("=== Incompatible Multi-Version Bundles (no copy for this instance's Minecraft version) ===").append(System.lineSeparator());
if (instanceMc == null) {
infoBuilder.append("Skipped: could not determine this instance's Minecraft version.").append(System.lineSeparator());
} else if (incompatible.isEmpty()) {
infoBuilder.append("None").append(System.lineSeparator());
} else {
infoBuilder.append("These bundled mods have no copy targeting MC ").append(instanceMc).append(" and likely fail to load:").append(System.lineSeparator());
for (String line : incompatible) {
infoBuilder.append("\t|-> ").append(line).append(System.lineSeparator());
}
}
infoBuilder.append(System.lineSeparator())
.append("----------------------------").append(System.lineSeparator())
.append(System.lineSeparator());

infoBuilder.append("=== Mod List ===").append(System.lineSeparator());
infoBuilder.append("Filesystem structure of: ").append(runDirectory.resolve("mods")).append(System.lineSeparator());
infoBuilder.append("|-> mods").append(System.lineSeparator());

for (LocalModFile mod : activeMods) {
infoBuilder.append("| |-> ").append(mod.getName());
if (StringUtils.isNotBlank(mod.getVersion()) && !"${version}".equals(mod.getVersion())) {
infoBuilder.append(" (").append(mod.getVersion()).append(")");
}
if (!mod.getName().equals(mod.getFileName())) {
infoBuilder.append(" [").append(mod.getFileName()).append("]");
}
infoBuilder.append(System.lineSeparator());
}

infoBuilder.append(System.lineSeparator())
.append("----------------------------").append(System.lineSeparator())
.append(System.lineSeparator())
.append("=== Jar-in-Jar Info List (active mods only) ===").append(System.lineSeparator());

boolean hasJij = false;
for (LocalModFile mod : activeMods) {
if (mod.hasBundledMods()) {
hasJij = true;
infoBuilder.append(mod.getName());
if (!mod.getName().equals(mod.getFileName())) {
infoBuilder.append(" [").append(mod.getFileName()).append("]");
}
infoBuilder.append(System.lineSeparator());
List<NestedJar> tree = mod.getBundledTree();
if (!tree.isEmpty()) {
appendJarTree(infoBuilder, tree, 1);
} else {
// Deep scan produced nothing usable — fall back to the declared filenames.
for (String bundled : mod.getBundledMods()) {
String name = bundled.contains("/") ? bundled.substring(bundled.lastIndexOf('/') + 1) : bundled;
infoBuilder.append("\t|-> ").append(name).append(System.lineSeparator());
}
}
infoBuilder.append(System.lineSeparator());
}
}

if (!hasJij) {
infoBuilder.append("No Jar-in-Jar info found").append(System.lineSeparator());
}

zipper.putTextFile(infoBuilder.toString(), "mods_info.txt");
} catch (Exception e) {
LOG.warning("Failed to export mod info to crash report package", e);
}

for (String id : versions) {
Path versionJson = baseDirectory.resolve("versions").resolve(id).resolve(id + ".json");
if (Files.exists(versionJson)) {
Expand All @@ -86,6 +229,27 @@ public static CompletableFuture<Void> exportLogs(
});
}

/// Prints a mod's Jar-in-Jar tree recursively (all nesting depths), one indented line per node.
private static void appendJarTree(StringBuilder sb, List<NestedJar> nodes, int depth) {
String indent = "\t".repeat(depth);
for (NestedJar node : nodes) {
sb.append(indent).append("|-> ").append(node.displayName());
if (StringUtils.isNotBlank(node.id())) {
sb.append(" (").append(node.id()).append(")");
}
if (StringUtils.isNotBlank(node.version())) {
sb.append(" ").append(node.version());
}
if (StringUtils.isNotBlank(node.minecraftVersion())) {
sb.append(" [MC ").append(node.minecraftVersion()).append("]");
}
sb.append(System.lineSeparator());
if (node.hasChildren()) {
appendJarTree(sb, node.children(), depth + 1);
}
}
}

private static void processLogs(Path directory, String fileExtension, String logDirectory, Zipper zipper, PathMatcher logMatcher) {
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory, fileExtension)) {
for (Path file : stream) {
Expand Down
1 change: 1 addition & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public enum SVG {
SELECT_ALL("M7 17V7H17V17H7ZM9 15H15V9H9V15ZM5 19V21Q4.175 21 3.5875 20.4125T3 19H5ZM3 17V15H5V17H3ZM3 13V11H5V13H3ZM3 9V7H5V9H3ZM5 5H3Q3 4.175 3.5875 3.5875T5 3V5ZM7 21V19H9V21H7ZM7 5V3H9V5H7ZM11 21V19H13V21H11ZM11 5V3H13V5H11ZM15 21V19H17V21H15ZM15 5V3H17V5H15ZM19 21V19H21Q21 19.825 20.4125 20.4125T19 21ZM19 17V15H21V17H19ZM19 13V11H21V13H19ZM19 9V7H21V9H19ZM19 5V3Q19.825 3 20.4125 3.5875T21 5H19Z"),
SETTINGS("M19.43 12.98C19.47 12.66 19.5 12.34 19.5 12 19.5 11.66 19.47 11.34 19.43 11.02L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.57 5.11 19.4 5.02 19.22 5.02 19.16 5.02 19.1 5.03 19.05 5.05L16.56 6.05C16.04 5.65 15.48 5.32 14.87 5.07L14.49 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.89 5.03 4.83 5.02 4.77 5.02 4.6 5.02 4.43 5.11 4.34 5.27L2.34 8.73C2.21 8.95 2.27 9.22 2.46 9.37L4.57 11.02C4.53 11.34 4.5 11.67 4.5 12 4.5 12.33 4.53 12.66 4.57 12.98L2.46 14.63C2.27 14.78 2.22 15.05 2.34 15.27L4.34 18.73C4.43 18.89 4.6 18.98 4.78 18.98 4.84 18.98 4.9 18.97 4.95 18.95L7.44 17.95C7.96 18.35 8.52 18.68 9.13 18.93L9.51 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.49 21.58L14.87 18.93C15.48 18.68 16.04 18.34 16.56 17.95L19.05 18.95C19.11 18.97 19.17 18.98 19.23 18.98 19.4 18.98 19.57 18.89 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.98ZM17.45 11.27C17.49 11.58 17.5 11.79 17.5 12 17.5 12.21 17.48 12.43 17.45 12.73L17.31 13.86 18.2 14.56 19.28 15.4 18.58 16.61 17.31 16.1 16.27 15.68 15.37 16.36C14.94 16.68 14.53 16.92 14.12 17.09L13.06 17.52 12.9 18.65 12.7 20H11.3L11.11 18.65 10.95 17.52 9.89 17.09C9.46 16.91 9.06 16.68 8.66 16.38L7.75 15.68 6.69 16.11 5.42 16.62 4.72 15.41 5.8 14.57 6.69 13.87 6.55 12.74C6.52 12.43 6.5 12.2 6.5 12S6.52 11.57 6.55 11.27L6.69 10.14 5.8 9.44 4.72 8.6 5.42 7.39 6.69 7.9 7.73 8.32 8.63 7.64C9.06 7.32 9.47 7.08 9.88 6.91L10.94 6.48 11.1 5.35 11.3 4H12.69L12.88 5.35 13.04 6.48 14.1 6.91C14.53 7.09 14.93 7.32 15.33 7.62L16.24 8.32 17.3 7.89 18.57 7.38 19.27 8.59 18.2 9.44 17.31 10.14 17.45 11.27ZM12 8C9.79 8 8 9.79 8 12S9.79 16 12 16 16 14.21 16 12 14.21 8 12 8ZM12 14C10.9 14 10 13.1 10 12S10.9 10 12 10 14 10.9 14 12 13.1 14 12 14Z"), // Material Icons
SETTINGS_FILL("M9.25 22l-.4-3.2q-.325-.125-.6125-.3t-.5625-.375L4.7 19.375l-2.75-4.75 2.575-1.95Q4.5 12.5 4.5 12.3375v-.675q0-.1625.025-.3375L1.95 9.375 4.7 4.625l2.975 1.25q.275-.2.575-.375t.6-.3L9.25 2h5.5l.4 3.2q.325.125.6125.3t.5625.375L19.3 4.625l2.75 4.75-2.575 1.95q.025.175.025.3375v.675q0 .1625-.05.3375l2.575 1.95-2.75 4.75-2.95-1.25q-.275.2-.575.375t-.6.3l-.4 3.2H9.25Zm2.8-6.5q1.45 0 2.475-1.025T15.55 12 14.525 9.525 12.05 8.5q-1.475 0-2.4875 1.025T8.55 12q0 1.45 1.0125 2.475T12.05 15.5Z"), // Material Icons
STACKS("M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z"),
STADIA_CONTROLLER("M4.725 20Q3.225 20 2.1625 18.925T1.05 16.325Q1.05 16.1 1.075 15.875T1.15 15.425L3.25 7.025Q3.6 5.675 4.675 4.8375T7.125 4H16.875Q18.25 4 19.325 4.8375T20.75 7.025L22.85 15.425Q22.9 15.65 22.9375 15.8875T22.975 16.35Q22.975 17.875 21.8875 18.9375T19.275 20Q18.225 20 17.325 19.45T15.975 17.95L15.275 16.5Q15.15 16.25 14.9 16.125T14.375 16H9.625Q9.35 16 9.1 16.125T8.725 16.5L8.025 17.95Q7.575 18.9 6.675 19.45T4.725 20ZM4.8 18Q5.275 18 5.6625 17.75T6.25 17.075L6.95 15.65Q7.325 14.875 8.05 14.4375T9.625 14H14.375Q15.225 14 15.95 14.45T17.075 15.65L17.775 17.075Q17.975 17.5 18.3625 17.75T19.225 18Q19.925 18 20.425 17.5375T20.95 16.375Q20.95 16.4 20.9 15.9L18.8 7.525Q18.625 6.85 18.1 6.425T16.875 6H7.125Q6.425 6 5.8875 6.425T5.2 7.525L3.1 15.9Q3.05 16.05 3.05 16.35 3.05 17.05 3.5625 17.525T4.8 18ZM13.5 11Q13.925 11 14.2125 10.7125T14.5 10Q14.5 9.575 14.2125 9.2875T13.5 9Q13.075 9 12.7875 9.2875T12.5 10Q12.5 10.425 12.7875 10.7125T13.5 11ZM15.5 9Q15.925 9 16.2125 8.7125T16.5 8Q16.5 7.575 16.2125 7.2875T15.5 7Q15.075 7 14.7875 7.2875T14.5 8Q14.5 8.425 14.7875 8.7125T15.5 9ZM15.5 13Q15.925 13 16.2125 12.7125T16.5 12Q16.5 11.575 16.2125 11.2875T15.5 11Q15.075 11 14.7875 11.2875T14.5 12Q14.5 12.425 14.7875 12.7125T15.5 13ZM17.5 11Q17.925 11 18.2125 10.7125T18.5 10Q18.5 9.575 18.2125 9.2875T17.5 9Q17.075 9 16.7875 9.2875T16.5 10Q16.5 10.425 16.7875 10.7125T17.5 11ZM8.5 12.5Q8.825 12.5 9.0375 12.2875T9.25 11.75V10.75H10.25Q10.575 10.75 10.7875 10.5375T11 10Q11 9.675 10.7875 9.4625T10.25 9.25H9.25V8.25Q9.25 7.925 9.0375 7.7125T8.5 7.5Q8.175 7.5 7.9625 7.7125T7.75 8.25V9.25H6.75Q6.425 9.25 6.2125 9.4625T6 10Q6 10.325 6.2125 10.5375T6.75 10.75H7.75V11.75Q7.75 12.075 7.9625 12.2875T8.5 12.5ZM12 12Z"),
STADIA_CONTROLLER_FILL("M4.725 20q-1.5 0-2.5625-1.075T1.05 16.325q0-.225.025-.45t.075-.45l2.1-8.4q.35-1.35 1.425-2.1875T7.125 4h9.75q1.375 0 2.45.8375T20.75 7.025l2.1 8.4q.05.225.0875.4625t.0375.4625q0 1.525-1.0875 2.5875T19.275 20q-1.05 0-1.95-.55t-1.35-1.5l-.7-1.45q-.125-.25-.375-.375T14.375 16H9.625q-.275 0-.525.125t-.375.375l-.7 1.45q-.45.95-1.35 1.5T4.725 20ZM13.5 11q.425 0 .7125-.2875T14.5 10t-.2875-.7125T13.5 9t-.7125.2875T12.5 10t.2875.7125T13.5 11Zm2-2q.425 0 .7125-.2875T16.5 8q0-.425-.2875-.7125T15.5 7q-.425 0-.7125.2875T14.5 8t.2875.7125T15.5 9Zm0 4q.425 0 .7125-.2875T16.5 12q0-.425-.2875-.7125T15.5 11q-.425 0-.7125.2875T14.5 12t.2875.7125T15.5 13Zm2-2q.425 0 .7125-.2875T18.5 10q0-.425-.2875-.7125T17.5 9q-.425 0-.7125.2875T16.5 10q0 .425.2875.7125T17.5 11Zm-9 1.5q.325 0 .5375-.2125T9.25 11.75v-1h1q.325 0 .5375-.2125T11 10t-.2125-.5375T10.25 9.25h-1v-1q0-.325-.2125-.5375T8.5 7.5q-.325 0-.5375.2125T7.75 8.25v1h-1q-.325 0-.5375.2125T6 10q0 .325.2125.5375T6.75 10.75h1v1q0 .325.2125.5375T8.5 12.5Z"),
STYLE("M3.975 19.8 3.125 19.45Q2.35 19.125 2.0875 18.325T2.175 16.75L3.975 12.85V19.8ZM7.975 22Q7.15 22 6.5625 21.4125T5.975 20V14L8.625 21.35Q8.7 21.525 8.775 21.6875T8.975 22H7.975ZM13.125 21.9Q12.325 22.2 11.575 21.825T10.525 20.65L6.075 8.45Q5.775 7.65 6.125 6.8875T7.275 5.85L14.825 3.1Q15.625 2.8 16.375 3.175T17.425 4.35L21.875 16.55Q22.175 17.35 21.825 18.1125T20.675 19.15L13.125 21.9ZM10.975 10Q11.4 10 11.6875 9.7125T11.975 9Q11.975 8.575 11.6875 8.2875T10.975 8Q10.55 8 10.2625 8.2875T9.975 9Q9.975 9.425 10.2625 9.7125T10.975 10ZM12.425 20 19.975 17.25 15.525 5 7.975 7.75 12.425 20ZM7.975 7.75 15.525 5 7.975 7.75Z"),
Expand Down
Loading