Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
98 changes: 98 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,8 @@
*/
package org.jackhuang.hmcl.game;

import org.jackhuang.hmcl.addon.mod.LocalModFile;
import org.jackhuang.hmcl.addon.mod.ModManager;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.io.Zipper;
Expand All @@ -32,7 +34,9 @@
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;

import static org.jackhuang.hmcl.util.logging.Logger.LOG;
Expand Down Expand Up @@ -74,6 +78,100 @@ 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();

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.
LinkedHashSet<String> duplicates = new LinkedHashSet<>();
for (LocalModFile host : activeMods) {
if (!host.hasBundledMods()) continue;
for (String bundled : host.getBundledMods()) {
String bundledName = bundled.contains("/") ? bundled.substring(bundled.lastIndexOf('/') + 1) : bundled;
String bundledLower = bundledName.toLowerCase(Locale.ROOT);
String bundledNorm = bundledLower.replace("-", "").replace("_", "");
for (LocalModFile other : activeMods) {
if (other == host) continue;
String id = other.getId();
if (id == null || id.length() < 3) continue;
String idLower = id.toLowerCase(Locale.ROOT);
if (bundledLower.contains(idLower)
|| bundledNorm.contains(idLower.replace("-", "").replace("_", ""))) {
duplicates.add(other.getName() + " [" + other.getFileName() + "] <-> bundled in " + host.getName() + " (" + bundledName + ")");
}
}
}
}

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("(Heuristic: matched by mod id appearing in the bundled jar file name; may include false positives.)").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());

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());
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 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.ui.FXUtils;

Expand All @@ -46,9 +49,41 @@ public TwoLineListItem() {
lblTitle = new Label();
lblTitle.getStyleClass().add("title");

this.firstLine = new HBox(lblTitle);
// Custom layout for the title row: when there isn't enough room, the tags shrink (and clip)
// first; only once they are gone does the title itself start to ellipsize. The preferred
// width stays the natural title+tags width (no width bindings), so this works in any parent
// without circular layout dependencies.
this.firstLine = new HBox(lblTitle) {
@Override
protected void layoutChildren() {
var managed = getManagedChildren();
if (managed.size() != 2) {
super.layoutChildren();
return;
}

Insets insets = getInsets();
double x = insets.getLeft();
double y = insets.getTop();
double availableWidth = Math.max(0, getWidth() - x - insets.getRight());
double availableHeight = Math.max(0, getHeight() - y - insets.getBottom());

Node title = managed.get(0);
Node tagsNode = managed.get(1);
double spacing = getSpacing();

double titleWidth = Math.min(title.prefWidth(availableHeight), availableWidth);
double tagsWidth = Math.min(tagsNode.prefWidth(availableHeight),
Math.max(0, availableWidth - titleWidth - spacing));

layoutInArea(title, x, y, titleWidth, availableHeight, 0, HPos.LEFT, VPos.CENTER);
layoutInArea(tagsNode, x + titleWidth + spacing, y, tagsWidth, availableHeight, 0, HPos.LEFT, VPos.CENTER);
}
};
firstLine.getStyleClass().add("first-line");
firstLine.setAlignment(Pos.CENTER_LEFT);
// Safety net: never let the row's content render past its own right edge.
FXUtils.setOverflowHidden(firstLine);

this.getChildren().setAll(firstLine);
}
Expand Down Expand Up @@ -168,15 +203,13 @@ public ObservableList<Label> getTags() {
tagsBox.getStyleClass().add("tags");
tagsBox.setAlignment(Pos.CENTER_LEFT);
tagsBox.setMinWidth(0);
HBox.setHgrow(tagsBox, Priority.ALWAYS);
Bindings.bindContent(tagsBox.getChildren(), tags);
var isNotEmpty = Bindings.isNotEmpty(tags);
tagsBox.managedProperty().bind(isNotEmpty);
tagsBox.visibleProperty().bind(isNotEmpty);

FXUtils.setOverflowHidden(tagsBox);

lblTitle.setMinWidth(Label.USE_PREF_SIZE);
// The title/tags shrink priority is handled by firstLine's custom layout (see constructor).
firstLine.getChildren().setAll(lblTitle, tagsBox);
}
return tags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@

public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage {
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_MOD =
(downloadProvider, repository, version, mod, file) -> download(downloadProvider, repository, version, file, "mods");
(downloadProvider, repository, version, mod, file) -> download(downloadProvider, repository, version, file, "mods", mod);
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_RESOURCE_PACK =
(downloadProvider, repository, version, pack, file) -> download(downloadProvider, repository, version, file, "resourcepacks");
(downloadProvider, repository, version, pack, file) -> download(downloadProvider, repository, version, file, "resourcepacks", null);
public static final org.jackhuang.hmcl.ui.versions.DownloadPage.DownloadCallback FOR_SHADER =
(downloadProvider, repository, version, shader, file) -> download(downloadProvider, repository, version, file, "shaderpacks");
(downloadProvider, repository, version, shader, file) -> download(downloadProvider, repository, version, file, "shaderpacks", null);

private final ReadOnlyObjectWrapper<DecoratorPage.State> state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("download"), -1));
private final TabHeader tab;
Expand Down Expand Up @@ -141,9 +141,11 @@ private static <T extends Node> Supplier<T> loadVersionFor(Supplier<T> nodeSuppl
};
}

public static void download(DownloadProvider downloadProvider, HMCLGameRepository repository, @Nullable String version, RemoteAddon.Version file, String subdirectoryName) {
public static void download(DownloadProvider downloadProvider, HMCLGameRepository repository, @Nullable String version, RemoteAddon.Version file, String subdirectoryName, @Nullable RemoteAddon mod) {
if (version == null) version = repository.getSelectedInstance();

String finalVersion = version;

Path runDirectory = repository.hasVersion(version) ? repository.getRunDirectory(version) : repository.getBaseDirectory();

Set<String> existingFiles;
Expand Down Expand Up @@ -175,6 +177,9 @@ public static void download(DownloadProvider downloadProvider, HMCLGameRepositor
}
} else {
Controllers.showToast(i18n("install.success"));
if (mod != null)
org.jackhuang.hmcl.ui.versions.DownloadPage.markModInstalled(
new HMCLGameRepository.InstanceReference(repository, finalVersion), mod);
}
}), i18n("message.downloading"), TaskCancellationAction.NORMAL);
handler.resolve();
Expand Down
Loading