Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7024a4c0f | ||
|
|
31c7e3a60f | ||
|
|
43cfe29eeb | ||
|
|
4921804283 |
No files matched your search
@@ -66,7 +66,6 @@ dependencies {
|
||||
}
|
||||
|
||||
embedResources(libs.authlib.injector)
|
||||
embedResources(libs.lwjgl.unsafe.agent)
|
||||
}
|
||||
|
||||
fun digest(algorithm: String, bytes: ByteArray): ByteArray = MessageDigest.getInstance(algorithm).digest(bytes)
|
||||
@@ -156,7 +155,6 @@ val hmclProperties = buildList {
|
||||
add("hmcl.microsoft.auth.id" to microsoftAuthId)
|
||||
add("hmcl.curseforge.apikey" to curseForgeApiKey)
|
||||
add("hmcl.authlib-injector.version" to libs.authlib.injector.get().version!!)
|
||||
add("hmcl.lwjgl-unsafe-agent.version" to libs.lwjgl.unsafe.agent.get().version!!)
|
||||
}
|
||||
|
||||
val hmclPropertiesFile = layout.buildDirectory.file("hmcl.properties")
|
||||
|
||||
@@ -49,6 +49,7 @@ import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
@@ -154,6 +155,7 @@ public class JFXDialog extends StackPane {
|
||||
});
|
||||
|
||||
contentHolder = new StackPane();
|
||||
contentHolder.setBackground(new Background(new BackgroundFill(Color.WHITE, new CornerRadii(2), null)));
|
||||
JFXDepthManager.setDepth(contentHolder, 4);
|
||||
contentHolder.setPickOnBounds(false);
|
||||
// ensure stackpane is never resized beyond it's preferred size
|
||||
|
||||
@@ -179,7 +179,7 @@ public class JFXCustomColorPickerDialog extends StackPane {
|
||||
(int) (newColor.getRed() * 255),
|
||||
(int) (newColor.getGreen() * 255),
|
||||
(int) (newColor.getBlue() * 255));
|
||||
String rgb = String.format("rgb(%d, %d, %d)",
|
||||
String rgb = String.format("rgba(%d, %d, %d, 1)",
|
||||
(int) (newColor.getRed() * 255),
|
||||
(int) (newColor.getGreen() * 255),
|
||||
(int) (newColor.getBlue() * 255));
|
||||
@@ -294,11 +294,7 @@ public class JFXCustomColorPickerDialog extends StackPane {
|
||||
if (!systemChange) {
|
||||
userChange = true;
|
||||
try {
|
||||
Color color = Color.valueOf(colorWebString);
|
||||
if (color.getOpacity() != 1.0) {
|
||||
color = Color.color(color.getRed(), color.getGreen(), color.getBlue());
|
||||
}
|
||||
curvedColorPicker.setColor(color);
|
||||
curvedColorPicker.setColor(Color.valueOf(colorWebString));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// if color is not valid then do nothing
|
||||
}
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.countly;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
|
||||
@@ -21,16 +21,12 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.launch.DefaultLauncher;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.util.NativePatcher;
|
||||
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
@@ -154,60 +150,4 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
generateOptionsTxt();
|
||||
super.makeLaunchScript(scriptFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendJvmArgs(CommandBuilder result) {
|
||||
super.appendJvmArgs(result);
|
||||
|
||||
if (config().getAllowAutoAgent()
|
||||
&& !options.isNoGeneratedJVMArgs()
|
||||
&& !options.isNoGeneratedOptimizingJVMArgs()
|
||||
&& NativePatcher.needPatchMemoryUtil(version, options.getJava().getParsedVersion())) {
|
||||
LOG.info("Attempting to patch game with lwjgl-unsafe-agent");
|
||||
try {
|
||||
result.add("-javaagent:" + extractLwjglUnsafeAgent());
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to extract lwjgl-unsafe-agent", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Path extractLwjglUnsafeAgent() throws IOException {
|
||||
String agentVersion = JarUtils.getAttribute("hmcl.lwjgl-unsafe-agent.version", null);
|
||||
if (agentVersion == null) {
|
||||
throw new IOException("Missing hmcl.lwjgl-unsafe-agent.version attribute");
|
||||
}
|
||||
|
||||
Library library = new Library(new Artifact("org.glavo", "lwjgl-unsafe-agent", agentVersion));
|
||||
String fileName = library.getArtifact().getFileName();
|
||||
|
||||
Path agentPath = repository.getLibraryFile(version, library).toAbsolutePath().normalize();
|
||||
if (agentPath.toString().contains("=")) {
|
||||
throw new IOException("Invalid library path: " + agentPath);
|
||||
}
|
||||
|
||||
byte[] bytes;
|
||||
try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/" + fileName)) {
|
||||
if (input == null) {
|
||||
throw new IOException("/assets/" + fileName + " not found");
|
||||
}
|
||||
|
||||
bytes = input.readAllBytes();
|
||||
}
|
||||
|
||||
if (Files.isRegularFile(agentPath)) {
|
||||
try {
|
||||
if (Files.size(agentPath) == bytes.length) {
|
||||
return agentPath;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to check size of " + agentPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
Files.createDirectories(agentPath.getParent());
|
||||
FileUtils.saveSafely(agentPath, output -> output.write(bytes));
|
||||
return agentPath;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,7 +65,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static javafx.application.Platform.runLater;
|
||||
import static javafx.application.Platform.setImplicitExit;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.Lang.resolveException;
|
||||
@@ -76,8 +75,6 @@ import static org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java;
|
||||
|
||||
public final class LauncherHelper {
|
||||
|
||||
private static final String LWJGL_3_4_1_TIP = "lwjgl3.4.1-ffm";
|
||||
|
||||
private final Profile profile;
|
||||
private Account account;
|
||||
private final String selectedVersion;
|
||||
@@ -200,28 +197,6 @@ public final class LauncherHelper {
|
||||
);
|
||||
}).withStage("launch.state.dependencies")
|
||||
.thenComposeAsync(() -> gameVersion.map(s -> new GameVerificationFixTask(dependencyManager, s, version.get())).orElse(null))
|
||||
.thenComposeAsync(() -> {
|
||||
if (config().getAllowAutoAgent()
|
||||
|| setting.isNoJVMArgs()
|
||||
|| setting.isNoOptimizingJVMArgs()
|
||||
|| Boolean.TRUE.equals(config().getShownTips().get(LWJGL_3_4_1_TIP))
|
||||
|| !NativePatcher.needPatchMemoryUtil(version.get(), javaVersionRef.get().getParsedVersion())) {
|
||||
return Task.completed(null);
|
||||
} else {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
runInFX(() -> {
|
||||
Controllers.confirm(i18n("launch.advice.lwjgl_3_4_1"), i18n("launch.advice.lwjgl_3_4_1.title"), MessageType.QUESTION, () -> {
|
||||
config().getShownTips().put(LWJGL_3_4_1_TIP, true);
|
||||
config().setAllowAutoAgent(true);
|
||||
future.complete(null);
|
||||
}, () -> {
|
||||
config().getShownTips().put(LWJGL_3_4_1_TIP, true);
|
||||
future.complete(null);
|
||||
});
|
||||
});
|
||||
return Task.fromCompletableFuture(future);
|
||||
}
|
||||
})
|
||||
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions.Builder launchOptionsBuilder = repository.getLaunchOptions(
|
||||
|
||||
@@ -689,21 +689,6 @@ public final class Config extends ObservableSetting {
|
||||
this.disableAutoGameOptions.set(disableAutoGameOptions);
|
||||
}
|
||||
|
||||
@SerializedName("allowAutoAgent")
|
||||
private final BooleanProperty allowAutoAgent = new SimpleBooleanProperty(false);
|
||||
|
||||
public BooleanProperty allowAutoAgentProperty() {
|
||||
return allowAutoAgent;
|
||||
}
|
||||
|
||||
public boolean getAllowAutoAgent() {
|
||||
return allowAutoAgent.get();
|
||||
}
|
||||
|
||||
public void setAllowAutoAgent(boolean allowAutoAgent) {
|
||||
this.allowAutoAgent.set(allowAutoAgent);
|
||||
}
|
||||
|
||||
// Accounts
|
||||
|
||||
@SerializedName("authlibInjectorServers")
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.setting;
|
||||
|
||||
public final class SambaException extends RuntimeException {
|
||||
|
||||
@@ -42,7 +42,6 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
@@ -274,10 +273,10 @@ public class GameCrashWindow extends Stage {
|
||||
logWindow.show();
|
||||
}
|
||||
|
||||
private CompletableFuture<Path> exportGameCrashInfo() {
|
||||
private void exportGameCrashInfo() {
|
||||
Path logFile = Paths.get("minecraft-exported-crash-info-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".zip").toAbsolutePath();
|
||||
|
||||
return CompletableFuture.supplyAsync(() ->
|
||||
CompletableFuture.supplyAsync(() ->
|
||||
logs.stream().map(Log::getLog).collect(Collectors.joining("\n")))
|
||||
.thenComposeAsync(logs -> {
|
||||
long processStartTime = managedProcess.getProcess().info()
|
||||
@@ -302,7 +301,20 @@ public class GameCrashWindow extends Stage {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}).thenApply(ignored -> logFile);
|
||||
})
|
||||
.handleAsync((result, exception) -> {
|
||||
if (exception == null) {
|
||||
FXUtils.showFileInExplorer(logFile);
|
||||
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.success", logFile), i18n("message.success"), MessageDialogPane.MessageType.SUCCESS).ok(null).build();
|
||||
DialogUtils.show(stackPane, dialog);
|
||||
} else {
|
||||
LOG.warning("Failed to export game crash info", exception);
|
||||
var dialog = new MessageDialogPane.Builder(i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception), i18n("message.error"), MessageDialogPane.MessageType.ERROR).ok(null).build();
|
||||
DialogUtils.show(stackPane, dialog);
|
||||
}
|
||||
|
||||
return null;
|
||||
}, Schedulers.javafx());
|
||||
}
|
||||
|
||||
private final class View extends VBox {
|
||||
@@ -432,35 +444,8 @@ public class GameCrashWindow extends Stage {
|
||||
HBox toolBar = new HBox();
|
||||
VBox.setMargin(toolBar, new Insets(0, 0, 4, 0));
|
||||
{
|
||||
SpinnerPane exportButtonPane = new SpinnerPane();
|
||||
exportButtonPane.getStyleClass().add("small-spinner-pane");
|
||||
|
||||
JFXButton exportButton = FXUtils.newRaisedButton(i18n("logwindow.export_game_crash_logs"));
|
||||
exportButtonPane.setContent(exportButton);
|
||||
exportButton.setOnAction(e -> {
|
||||
exportButtonPane.showSpinner();
|
||||
exportGameCrashInfo().whenCompleteAsync((result, exception) -> {
|
||||
exportButtonPane.hideSpinner();
|
||||
|
||||
if (exception == null) {
|
||||
FXUtils.showFileInExplorer(result);
|
||||
var dialog = new MessageDialogPane.Builder(
|
||||
i18n("settings.launcher.launcher_log.export.success", result),
|
||||
i18n("message.success"),
|
||||
MessageDialogPane.MessageType.SUCCESS
|
||||
).ok(null).build();
|
||||
DialogUtils.show(stackPane, dialog);
|
||||
} else {
|
||||
LOG.warning("Failed to export game crash info", exception);
|
||||
var dialog = new MessageDialogPane.Builder(
|
||||
i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception),
|
||||
i18n("message.error"),
|
||||
MessageDialogPane.MessageType.ERROR
|
||||
).ok(null).build();
|
||||
DialogUtils.show(stackPane, dialog);
|
||||
}
|
||||
}, Schedulers.javafx());
|
||||
});
|
||||
JFXButton exportGameCrashInfoButton = FXUtils.newRaisedButton(i18n("logwindow.export_game_crash_logs"));
|
||||
exportGameCrashInfoButton.setOnAction(e -> exportGameCrashInfo());
|
||||
|
||||
JFXButton logButton = FXUtils.newRaisedButton(i18n("logwindow.title"));
|
||||
logButton.setOnAction(e -> showLogWindow());
|
||||
@@ -472,7 +457,7 @@ public class GameCrashWindow extends Stage {
|
||||
toolBar.setPadding(new Insets(8));
|
||||
toolBar.setSpacing(8);
|
||||
toolBar.getStyleClass().add("jfx-tool-bar");
|
||||
toolBar.getChildren().setAll(exportButtonPane, logButton, helpButton);
|
||||
toolBar.getChildren().setAll(exportGameCrashInfoButton, logButton, helpButton);
|
||||
}
|
||||
|
||||
getChildren().setAll(titlePane, infoPane, moddedPane, gameDirPane, toolBar);
|
||||
|
||||
@@ -36,7 +36,6 @@ import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||
import org.jackhuang.hmcl.game.Log;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
@@ -242,10 +241,6 @@ public final class LogWindow extends Stage {
|
||||
});
|
||||
}
|
||||
|
||||
private ManagedProcess getGameProcess() {
|
||||
return gameProcess;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new LogWindowSkin(this);
|
||||
@@ -430,13 +425,6 @@ public final class LogWindow extends Stage {
|
||||
clearButton.setOnAction(e -> getSkinnable().onClear());
|
||||
hBox.getChildren().setAll(autoScrollCheckBox, exportLogsButton, terminateButton, exportDumpPane, clearButton);
|
||||
|
||||
control.getGameProcess().getProcess()
|
||||
.onExit()
|
||||
.thenRunAsync(() -> {
|
||||
terminateButton.setDisable(true);
|
||||
exportDumpButton.setDisable(true);
|
||||
}, Schedulers.javafx());
|
||||
|
||||
vbox.getChildren().add(bottom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* 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.ui;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.util.FXThread;
|
||||
import org.jackhuang.hmcl.util.MathUtils;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/// @author Glavo
|
||||
public final class MemoryStatusBar extends Control {
|
||||
|
||||
private static final String DEFAULT_STYLE_CLASS = "memory-status-bar";
|
||||
|
||||
public MemoryStatusBar() {
|
||||
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
|
||||
setPrefWidth(200);
|
||||
}
|
||||
|
||||
private final ReadOnlyObjectProperty<PhysicalMemoryStatus> memoryStatus = UpdateMemoryStatus.memoryStatusProperty();
|
||||
|
||||
/// The memory status of the system.
|
||||
///
|
||||
/// The property will be automatically updated.
|
||||
///
|
||||
/// The property has no bean, because it may share between multiple MemoryStatusBar instances.
|
||||
public ReadOnlyObjectProperty<PhysicalMemoryStatus> memoryStatusProperty() {
|
||||
return memoryStatus;
|
||||
}
|
||||
|
||||
public PhysicalMemoryStatus getMemoryStatus() {
|
||||
return memoryStatus.get();
|
||||
}
|
||||
|
||||
private final DoubleProperty memoryAllocated = new SimpleDoubleProperty();
|
||||
|
||||
public DoubleProperty memoryAllocatedProperty() {
|
||||
return memoryAllocated;
|
||||
}
|
||||
|
||||
public double getMemoryAllocated() {
|
||||
return memoryAllocated.get();
|
||||
}
|
||||
|
||||
public void setMemoryAllocated(double memoryAllocated) {
|
||||
this.memoryAllocated.set(memoryAllocated);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SkinBase<MemoryStatusBar> createDefaultSkin() {
|
||||
return new Skin(this);
|
||||
}
|
||||
|
||||
private static final class Skin extends SkinBase<MemoryStatusBar> {
|
||||
private static final int HEIGHT = 4;
|
||||
|
||||
private final StackPane track;
|
||||
private final Rectangle memoryUsed;
|
||||
private final Rectangle memoryAllocate;
|
||||
|
||||
private double contentWidth = -1;
|
||||
|
||||
Skin(MemoryStatusBar control) {
|
||||
super(control);
|
||||
|
||||
track = new StackPane();
|
||||
track.getStyleClass().add("track");
|
||||
track.setPadding(new Insets(HEIGHT, 0, 0, 0));
|
||||
|
||||
memoryUsed = new Rectangle();
|
||||
memoryUsed.setManaged(false);
|
||||
memoryUsed.setArcWidth(HEIGHT);
|
||||
memoryUsed.setArcHeight(HEIGHT);
|
||||
memoryUsed.getStyleClass().add("memory-used");
|
||||
|
||||
memoryAllocate = new Rectangle();
|
||||
memoryAllocate.setManaged(false);
|
||||
memoryAllocate.setArcWidth(HEIGHT);
|
||||
memoryAllocate.setArcHeight(HEIGHT);
|
||||
memoryAllocate.getStyleClass().add("memory-allocate");
|
||||
|
||||
this.getChildren().setAll(track, memoryAllocate, memoryUsed);
|
||||
|
||||
registerInvalidationListener(control.memoryStatusProperty(), it -> updateBar());
|
||||
registerInvalidationListener(control.memoryAllocatedProperty(), it -> updateBar());
|
||||
}
|
||||
|
||||
private void updateBar() {
|
||||
if (contentWidth <= 0) {
|
||||
// not yet layout
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicalMemoryStatus status = getSkinnable().getMemoryStatus();
|
||||
double total = status.getTotal();
|
||||
double used = status.getUsed();
|
||||
double allocated = getSkinnable().getMemoryAllocated();
|
||||
|
||||
if (total <= 0) {
|
||||
memoryUsed.setWidth(contentWidth);
|
||||
memoryAllocate.setWidth(contentWidth);
|
||||
return;
|
||||
}
|
||||
|
||||
double usedWidth = MathUtils.clamp(contentWidth * (used / total), 0, contentWidth);
|
||||
double allocatedWidth = MathUtils.clamp(contentWidth * (allocated / total) + usedWidth, 0, contentWidth);
|
||||
|
||||
memoryUsed.setWidth(usedWidth);
|
||||
memoryAllocate.setWidth(allocatedWidth);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) {
|
||||
track.resizeRelocate(contentX, contentY, contentWidth, contentHeight);
|
||||
memoryUsed.relocate(contentX, contentY);
|
||||
memoryAllocate.relocate(contentX, contentY);
|
||||
|
||||
this.contentWidth = contentWidth;
|
||||
|
||||
memoryUsed.setHeight(contentHeight);
|
||||
memoryAllocate.setHeight(contentHeight);
|
||||
|
||||
updateBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double computeBaselineOffset(double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
return Node.BASELINE_OFFSET_SAME_AS_HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
return leftInset + track.prefWidth(height) + rightInset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
return topInset + HEIGHT + bottomInset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double computeMaxHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
|
||||
return computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UpdateMemoryStatus extends Thread {
|
||||
|
||||
private static final int UPDATE_INTERVAL = 3000;
|
||||
|
||||
@FXThread
|
||||
private static WeakReference<ObjectProperty<PhysicalMemoryStatus>> memoryStatusPropertyCache;
|
||||
|
||||
@FXThread
|
||||
static ReadOnlyObjectProperty<PhysicalMemoryStatus> memoryStatusProperty() {
|
||||
if (memoryStatusPropertyCache != null) {
|
||||
var property = memoryStatusPropertyCache.get();
|
||||
if (property != null) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectProperty<PhysicalMemoryStatus> property = new SimpleObjectProperty<>(PhysicalMemoryStatus.INVALID);
|
||||
memoryStatusPropertyCache = new WeakReference<>(property);
|
||||
new UpdateMemoryStatus(memoryStatusPropertyCache).start();
|
||||
return property;
|
||||
}
|
||||
|
||||
private final WeakReference<ObjectProperty<PhysicalMemoryStatus>> memoryStatusPropertyRef;
|
||||
|
||||
UpdateMemoryStatus(WeakReference<ObjectProperty<PhysicalMemoryStatus>> memoryStatusPropertyRef) {
|
||||
this.memoryStatusPropertyRef = memoryStatusPropertyRef;
|
||||
|
||||
setName("UpdateMemoryStatus");
|
||||
setDaemon(true);
|
||||
setPriority(Thread.MIN_PRIORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
PhysicalMemoryStatus status = SystemInfo.getPhysicalMemoryStatus();
|
||||
|
||||
var memoryStatusProperty = memoryStatusPropertyRef.get();
|
||||
if (memoryStatusProperty == null)
|
||||
return;
|
||||
|
||||
if (Controllers.isStopped())
|
||||
return;
|
||||
|
||||
Platform.runLater(() -> memoryStatusProperty.set(status));
|
||||
|
||||
try {
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(UPDATE_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import org.jackhuang.hmcl.util.MathUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
// Referenced in root.css
|
||||
@SuppressWarnings("unused")
|
||||
@@ -75,7 +75,7 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
*/
|
||||
if (getSkinnable().getMax() > getSkinnable().getMin()) {
|
||||
dragStart = thumb.localToParent(me.getX(), me.getY());
|
||||
double clampedValue = MathUtils.clamp(getSkinnable().getValue(), getSkinnable().getMin(), getSkinnable().getMax());
|
||||
double clampedValue = Lang.clamp(getSkinnable().getMin(), getSkinnable().getValue(), getSkinnable().getMax());
|
||||
preDragThumbPos = (clampedValue - getSkinnable().getMin()) / (getSkinnable().getMax() - getSkinnable().getMin());
|
||||
me.consume();
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
getSkinnable().requestFocus();
|
||||
double newValue = (position * (getSkinnable().getMax() - getSkinnable().getMin())) + getSkinnable().getMin();
|
||||
if (!Double.isNaN(newValue)) {
|
||||
getSkinnable().setValue(MathUtils.clamp(newValue, getSkinnable().getMin(), getSkinnable().getMax()));
|
||||
getSkinnable().setValue(Lang.clamp(getSkinnable().getMin(), newValue, getSkinnable().getMax()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
public class LineButton extends LineButtonBase {
|
||||
private static final String DEFAULT_STYLE_CLASS = "line-button";
|
||||
|
||||
private static final int IDX_TRAILING_TEXT = IDX_TRAILING;
|
||||
private static final int IDX_TRAILING_ICON = IDX_TRAILING + 1;
|
||||
|
||||
public static LineButton createNavigationButton() {
|
||||
var button = new LineButton();
|
||||
button.setTrailingIcon(SVG.ARROW_FORWARD);
|
||||
@@ -47,14 +50,7 @@ public class LineButton extends LineButtonBase {
|
||||
|
||||
public LineButton() {
|
||||
getStyleClass().add(DEFAULT_STYLE_CLASS);
|
||||
}
|
||||
|
||||
protected int getTrailingTextIndex() {
|
||||
return IDX_TRAILING;
|
||||
}
|
||||
|
||||
protected int getTrailingIconIndex() {
|
||||
return getTrailingTextIndex() + 1;
|
||||
container.setMouseTransparent(true);
|
||||
}
|
||||
|
||||
private ObjectProperty<EventHandler<ActionEvent>> onAction;
|
||||
@@ -114,13 +110,12 @@ public class LineButton extends LineButtonBase {
|
||||
trailingTextLabel = new Label();
|
||||
trailingTextLabel.getStyleClass().add("trailing-label");
|
||||
trailingTextLabel.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
||||
trailingTextLabel.setMouseTransparent(true);
|
||||
}
|
||||
trailingTextLabel.setText(message);
|
||||
setNode(getTrailingTextIndex(), trailingTextLabel);
|
||||
setNode(IDX_TRAILING_TEXT, trailingTextLabel);
|
||||
} else if (trailingTextLabel != null) {
|
||||
trailingTextLabel.setText("");
|
||||
setNode(getTrailingTextIndex(), null);
|
||||
setNode(IDX_TRAILING_TEXT, null);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -154,7 +149,7 @@ public class LineButton extends LineButtonBase {
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
setNode(getTrailingIconIndex(), get());
|
||||
setNode(IDX_TRAILING_ICON, get());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -176,7 +171,6 @@ public class LineButton extends LineButtonBase {
|
||||
public void setTrailingIcon(SVG rightIcon, double size) {
|
||||
Node rightIconNode = rightIcon.createIcon(size);
|
||||
rightIconNode.getStyleClass().add("trailing-icon");
|
||||
rightIconNode.setMouseTransparent(true);
|
||||
setTrailingIcon(rightIconNode);
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,6 @@ public abstract class LineComponent extends StackPane implements NoPaddingCompon
|
||||
this.container = new HBox(SPACING);
|
||||
container.getStyleClass().add("line-component-container");
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
container.setPickOnBounds(true);
|
||||
|
||||
this.titleLabel = new Label();
|
||||
titleLabel.getStyleClass().add("title-label");
|
||||
|
||||
@@ -43,7 +43,7 @@ import java.util.function.Function;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.determineOptimalPopupPosition;
|
||||
|
||||
/// @author Glavo
|
||||
public class LineSelectButton<T> extends LineButton {
|
||||
public final class LineSelectButton<T> extends LineButton {
|
||||
|
||||
private static final String DEFAULT_STYLE_CLASS = "line-select-button";
|
||||
private static final PseudoClass SELECTED_PSEUDO_CLASS = PseudoClass.getPseudoClass("selected");
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.StringPropertyBase;
|
||||
@@ -169,13 +168,11 @@ public class TwoLineListItem extends VBox {
|
||||
|
||||
var scrollPane = new ScrollPane(tagsBox);
|
||||
HBox.setHgrow(scrollPane, Priority.ALWAYS);
|
||||
lblTitle.setMinWidth(Label.USE_PREF_SIZE);
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
FXUtils.onChangeAndOperate(tagsBox.heightProperty(), height -> FXUtils.setLimitHeight(scrollPane, height.doubleValue()));
|
||||
scrollPane.setPrefWidth(50);
|
||||
firstLine.getChildren().setAll(lblTitle, scrollPane);
|
||||
|
||||
tags.addListener((InvalidationListener) ignored -> scrollPane.requestLayout());
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ import org.jackhuang.hmcl.ui.construct.JFXDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.wizard.Refreshable;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.MathUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -218,7 +218,7 @@ public class DecoratorController {
|
||||
return new Background(new BackgroundFill(new Color(1, 1, 1, 0.5), CornerRadii.EMPTY, Insets.EMPTY));
|
||||
case PAINT:
|
||||
Paint paint = config().getBackgroundPaint();
|
||||
double opacity = MathUtils.clamp(config().getBackgroundImageOpacity(), 0, 100) / 100.;
|
||||
double opacity = Lang.clamp(0, config().getBackgroundImageOpacity(), 100) / 100.;
|
||||
if (paint instanceof Color || paint == null) {
|
||||
Color color = (Color) paint;
|
||||
if (color == null)
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.ui.download;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
|
||||
@@ -18,24 +18,24 @@
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -46,7 +46,6 @@ import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.AprilFools;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
@@ -62,16 +61,20 @@ import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class SettingsPage extends ScrollPane {
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final ToggleGroup updateChannelGroup;
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final InvalidationListener updateListener;
|
||||
|
||||
@@ -118,59 +121,82 @@ public final class SettingsPage extends ScrollPane {
|
||||
settingsPane.getContent().add(sponsorPane);
|
||||
}
|
||||
|
||||
ObjectProperty<UpdateChannel> updateChannel;
|
||||
{
|
||||
|
||||
JFXButton updateButton = new JFXButton();
|
||||
updateButton.setGraphic(SVG.UPDATE.createIcon(20));
|
||||
updateButton.setOnAction(e -> onUpdate());
|
||||
updateButton.setPadding(Insets.EMPTY);
|
||||
FXUtils.installFastTooltip(updateButton, i18n("update.tooltip"));
|
||||
|
||||
var updatePane = new LineSelectButton<UpdateChannel>() {
|
||||
|
||||
{
|
||||
getStyleClass().add("update-pane");
|
||||
setNode(IDX_TRAILING, updateButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getTrailingTextIndex() {
|
||||
return LineComponent.IDX_TRAILING + 1;
|
||||
}
|
||||
};
|
||||
updateChannel = updatePane.valueProperty();
|
||||
ComponentSublist updatePane = new ComponentSublist();
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setValue(UpdateChannel.getChannel());
|
||||
updatePane.setHasSubtitle(true);
|
||||
|
||||
updatePane.setConverter(channel -> i18n("update.channel." + channel.channelName));
|
||||
updatePane.setItems(List.of(UpdateChannel.STABLE, UpdateChannel.DEVELOPMENT));
|
||||
updatePane.setDescriptionConverter(channel -> i18n("update.note." + channel.channelName));
|
||||
final Label lblUpdate;
|
||||
final Label lblUpdateSub;
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
|
||||
final StringProperty lblUpdateSubProperty = updatePane.subtitleProperty();
|
||||
lblUpdate = new Label(i18n("update"));
|
||||
lblUpdate.getStyleClass().add("title-label");
|
||||
lblUpdateSub = new Label();
|
||||
lblUpdateSub.getStyleClass().add("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(lblUpdate, lblUpdateSub);
|
||||
updatePane.setHeaderLeft(headerLeft);
|
||||
}
|
||||
|
||||
{
|
||||
updateListener = any -> {
|
||||
boolean outdated = UpdateChecker.isOutdated();
|
||||
JFXButton btnUpdate = FXUtils.newToggleButton4(SVG.UPDATE, 20);
|
||||
btnUpdate.setOnAction(e -> onUpdate());
|
||||
FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
|
||||
updateButton.setVisible(outdated);
|
||||
updateButton.setManaged(outdated);
|
||||
updatePane.pseudoClassStateChanged(PseudoClass.getPseudoClass("active"), outdated);
|
||||
updateListener = any -> {
|
||||
btnUpdate.setVisible(UpdateChecker.isOutdated());
|
||||
|
||||
if (UpdateChecker.isOutdated()) {
|
||||
lblUpdateSubProperty.set(i18n("update.newest_version", UpdateChecker.getLatestVersion().getVersion()));
|
||||
lblUpdateSub.setText(i18n("update.newest_version", UpdateChecker.getLatestVersion().getVersion()));
|
||||
lblUpdateSub.getStyleClass().setAll("update-label");
|
||||
|
||||
lblUpdate.setText(i18n("update.found"));
|
||||
lblUpdate.getStyleClass().setAll("update-label");
|
||||
} else if (UpdateChecker.isCheckingUpdate()) {
|
||||
lblUpdateSubProperty.set(i18n("update.checking"));
|
||||
lblUpdateSub.setText(i18n("update.checking"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll("title-label");
|
||||
} else {
|
||||
lblUpdateSubProperty.set(i18n("update.latest"));
|
||||
lblUpdateSub.setText(i18n("update.latest"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll("title-label");
|
||||
}
|
||||
};
|
||||
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
updateListener.invalidated(null);
|
||||
|
||||
updatePane.setHeaderRight(btnUpdate);
|
||||
}
|
||||
|
||||
{
|
||||
VBox content = new VBox(12);
|
||||
content.setPadding(new Insets(8, 0, 0, 0));
|
||||
|
||||
updateChannelGroup = new ToggleGroup();
|
||||
|
||||
JFXRadioButton chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable"));
|
||||
chkUpdateStable.setUserData(UpdateChannel.STABLE);
|
||||
chkUpdateStable.setToggleGroup(updateChannelGroup);
|
||||
|
||||
JFXRadioButton chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev"));
|
||||
chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateDev.setToggleGroup(updateChannelGroup);
|
||||
|
||||
Label noteWrapper = new Label(i18n("update.note"));
|
||||
VBox.setMargin(noteWrapper, new Insets(8, 0, 0, 0));
|
||||
|
||||
content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);
|
||||
|
||||
updatePane.getContent().add(content);
|
||||
}
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
@@ -180,6 +206,8 @@ public final class SettingsPage extends ScrollPane {
|
||||
previewPane.setSubtitle(i18n("update.preview.subtitle"));
|
||||
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());
|
||||
|
||||
ObjectProperty<UpdateChannel> updateChannel = selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class);
|
||||
updateChannel.set(UpdateChannel.getChannel());
|
||||
InvalidationListener checkUpdateListener = e -> {
|
||||
UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());
|
||||
};
|
||||
@@ -260,15 +288,6 @@ public final class SettingsPage extends ScrollPane {
|
||||
settingsPane.getContent().add(disableAutoGameOptionsPane);
|
||||
}
|
||||
|
||||
{
|
||||
LineToggleButton allowAutoAgentPane = new LineToggleButton();
|
||||
allowAutoAgentPane.setTitle(i18n("settings.launcher.allow_auto_agent"));
|
||||
allowAutoAgentPane.setSubtitle(i18n("settings.launcher.allow_auto_agent.subtitle"));
|
||||
allowAutoAgentPane.selectedProperty().bindBidirectional(config().allowAutoAgentProperty());
|
||||
|
||||
settingsPane.getContent().add(allowAutoAgentPane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane debugPane = new BorderPane();
|
||||
|
||||
@@ -282,31 +301,12 @@ public final class SettingsPage extends ScrollPane {
|
||||
if (LOG.getLogFile() == null)
|
||||
openLogFolderButton.setDisable(true);
|
||||
|
||||
SpinnerPane exportLogPane = new SpinnerPane();
|
||||
|
||||
JFXButton logButton = FXUtils.newBorderButton(i18n("settings.launcher.launcher_log.export"));
|
||||
exportLogPane.setContent(logButton);
|
||||
logButton.setOnAction(e -> {
|
||||
exportLogPane.showSpinner();
|
||||
onExportLogs().whenCompleteAsync((result, exception) -> {
|
||||
exportLogPane.hideSpinner();
|
||||
if (exception == null) {
|
||||
Controllers.dialog(i18n("settings.launcher.launcher_log.export.success", result));
|
||||
FXUtils.showFileInExplorer(result);
|
||||
} else {
|
||||
LOG.warning("Failed to export logs", exception);
|
||||
Controllers.dialog(
|
||||
i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception),
|
||||
null,
|
||||
MessageType.ERROR
|
||||
);
|
||||
}
|
||||
}, Schedulers.javafx());
|
||||
});
|
||||
logButton.setOnAction(e -> onExportLogs());
|
||||
|
||||
HBox buttonBox = new HBox();
|
||||
buttonBox.setSpacing(10);
|
||||
buttonBox.getChildren().addAll(openLogFolderButton, exportLogPane);
|
||||
buttonBox.getChildren().addAll(openLogFolderButton, logButton);
|
||||
BorderPane.setAlignment(buttonBox, Pos.CENTER_RIGHT);
|
||||
debugPane.setRight(buttonBox);
|
||||
|
||||
@@ -378,79 +378,86 @@ public final class SettingsPage extends ScrollPane {
|
||||
}
|
||||
}
|
||||
|
||||
private CompletableFuture<Path> onExportLogs() {
|
||||
return CompletableFuture.supplyAsync(Lang.wrap(() -> {
|
||||
private void onExportLogs() {
|
||||
thread(() -> {
|
||||
String nameBase = "hmcl-exported-logs-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss"));
|
||||
List<Path> recentLogFiles = LOG.findRecentLogFiles(5);
|
||||
|
||||
Path outputFile;
|
||||
if (recentLogFiles.isEmpty()) {
|
||||
outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + ".log");
|
||||
try {
|
||||
if (recentLogFiles.isEmpty()) {
|
||||
outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + ".log");
|
||||
|
||||
LOG.info("Exporting latest logs to " + outputFile);
|
||||
try (OutputStream output = Files.newOutputStream(outputFile)) {
|
||||
LOG.exportLogs(output);
|
||||
}
|
||||
} else {
|
||||
outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + ".zip");
|
||||
LOG.info("Exporting latest logs to " + outputFile);
|
||||
try (OutputStream output = Files.newOutputStream(outputFile)) {
|
||||
LOG.exportLogs(output);
|
||||
}
|
||||
} else {
|
||||
outputFile = Metadata.CURRENT_DIRECTORY.resolve(nameBase + ".zip");
|
||||
|
||||
LOG.info("Exporting latest logs to " + outputFile);
|
||||
LOG.info("Exporting latest logs to " + outputFile);
|
||||
|
||||
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
try (var os = Files.newOutputStream(outputFile);
|
||||
var zos = new ZipOutputStream(os)) {
|
||||
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
try (var os = Files.newOutputStream(outputFile);
|
||||
var zos = new ZipOutputStream(os)) {
|
||||
|
||||
Set<String> entryNames = new HashSet<>();
|
||||
Set<String> entryNames = new HashSet<>();
|
||||
|
||||
for (Path path : recentLogFiles) {
|
||||
String fileName = FileUtils.getName(path);
|
||||
String extension = StringUtils.substringAfterLast(fileName, '.');
|
||||
for (Path path : recentLogFiles) {
|
||||
String fileName = FileUtils.getName(path);
|
||||
String extension = StringUtils.substringAfterLast(fileName, '.');
|
||||
|
||||
if ("gz".equals(extension) || "xz".equals(extension)) {
|
||||
// If an exception occurs while decompressing the input file, we should
|
||||
// ensure the input file and the current zip entry are closed,
|
||||
// then copy the compressed file content as-is into a new entry in the zip file.
|
||||
if ("gz".equals(extension) || "xz".equals(extension)) {
|
||||
// If an exception occurs while decompressing the input file, we should
|
||||
// ensure the input file and the current zip entry are closed,
|
||||
// then copy the compressed file content as-is into a new entry in the zip file.
|
||||
|
||||
InputStream input = null;
|
||||
try {
|
||||
input = Files.newInputStream(path);
|
||||
input = "gz".equals(extension)
|
||||
? new GZIPInputStream(input)
|
||||
: new XZInputStream(input);
|
||||
} catch (Throwable ex) {
|
||||
LOG.warning("Failed to open log file " + path, ex);
|
||||
IOUtils.closeQuietly(input, ex);
|
||||
input = null;
|
||||
InputStream input = null;
|
||||
try {
|
||||
input = Files.newInputStream(path);
|
||||
input = "gz".equals(extension)
|
||||
? new GZIPInputStream(input)
|
||||
: new XZInputStream(input);
|
||||
} catch (Throwable ex) {
|
||||
LOG.warning("Failed to open log file " + path, ex);
|
||||
IOUtils.closeQuietly(input, ex);
|
||||
input = null;
|
||||
}
|
||||
|
||||
String entryName = getEntryName(entryNames, StringUtils.substringBeforeLast(fileName, "."));
|
||||
if (input != null && exportLogFile(zos, path, entryName, input, buffer))
|
||||
continue;
|
||||
}
|
||||
|
||||
String entryName = getEntryName(entryNames, StringUtils.substringBeforeLast(fileName, "."));
|
||||
if (input != null && exportLogFile(zos, path, entryName, input, buffer))
|
||||
// Copy the log file content as-is into a new entry in the zip file.
|
||||
// If an exception occurs while decompressing the input file, we should
|
||||
// ensure the input file and the current zip entry are closed.
|
||||
|
||||
InputStream input;
|
||||
try {
|
||||
input = Files.newInputStream(path);
|
||||
} catch (Throwable ex) {
|
||||
LOG.warning("Failed to open log file " + path, ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
exportLogFile(zos, path, getEntryName(entryNames, fileName), input, buffer);
|
||||
}
|
||||
|
||||
// Copy the log file content as-is into a new entry in the zip file.
|
||||
// If an exception occurs while decompressing the input file, we should
|
||||
// ensure the input file and the current zip entry are closed.
|
||||
|
||||
InputStream input;
|
||||
try {
|
||||
input = Files.newInputStream(path);
|
||||
} catch (Throwable ex) {
|
||||
LOG.warning("Failed to open log file " + path, ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
exportLogFile(zos, path, getEntryName(entryNames, fileName), input, buffer);
|
||||
zos.putNextEntry(new ZipEntry(getEntryName(entryNames, "hmcl-latest.log")));
|
||||
LOG.exportLogs(zos);
|
||||
zos.closeEntry();
|
||||
}
|
||||
|
||||
zos.putNextEntry(new ZipEntry(getEntryName(entryNames, "hmcl-latest.log")));
|
||||
LOG.exportLogs(zos);
|
||||
zos.closeEntry();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to export logs", e);
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(e), null, MessageType.ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
return outputFile;
|
||||
}), Schedulers.io());
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.success", outputFile)));
|
||||
FXUtils.showFileInExplorer(outputFile);
|
||||
});
|
||||
}
|
||||
|
||||
private void onSponsor() {
|
||||
|
||||
@@ -22,14 +22,8 @@ import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.control.skin.TreeViewSkin;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.glavo.nbt.NBTElement;
|
||||
import org.glavo.nbt.tag.Tag;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -39,7 +33,6 @@ import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
@@ -63,9 +56,8 @@ public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
|
||||
|
||||
this.state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("nbt.title", file.toString())));
|
||||
this.file = file;
|
||||
|
||||
//noinspection DataFlowIssue
|
||||
this.type = NBTFileType.ofFile(file);
|
||||
|
||||
if (type == null) {
|
||||
throw new IOException("Unknown type of file " + file);
|
||||
}
|
||||
@@ -93,33 +85,14 @@ public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
|
||||
|
||||
actions.getChildren().setAll(saveButton, cancelButton);
|
||||
|
||||
Task.supplyAsync(() -> type.read(file))
|
||||
Task.supplyAsync(() -> type.readAsTree(file))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
setLoading(false);
|
||||
|
||||
NBTTreeItem root = new NBTTreeItem(result, FileUtils.getName(file));
|
||||
var view = new TreeView<>(root) {
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new TreeViewSkin<>(this) {
|
||||
{
|
||||
FXUtils.smoothScrolling(getVirtualFlow());
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
view.setCellFactory(ignored -> new NBTTreeCell());
|
||||
view.addEventHandler(TreeItem.<NBTElement>branchExpandedEvent(), event -> {
|
||||
TreeItem<NBTElement> item = event.getTreeItem();
|
||||
if (item.getValue() instanceof Tag && item.getChildren().size() == 1)
|
||||
item.getChildren().get(0).setExpanded(true);
|
||||
});
|
||||
root.setExpanded(true);
|
||||
|
||||
NBTTreeView view = new NBTTreeView(result);
|
||||
BorderPane.setMargin(view, new Insets(10));
|
||||
onEscPressed(view, cancelButton::fire);
|
||||
this.root.setCenter(view);
|
||||
root.setCenter(view);
|
||||
} else {
|
||||
LOG.warning("Fail to open nbt file", exception);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(exception), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
|
||||
|
||||
@@ -17,34 +17,148 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import org.glavo.nbt.NBTElement;
|
||||
import org.glavo.nbt.chunk.ChunkRegion;
|
||||
import org.glavo.nbt.io.NBTCodec;
|
||||
import org.glavo.nbt.tag.Tag;
|
||||
import com.github.steveice10.opennbt.NBTIO;
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.IntTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import kala.compress.utils.BoundedInputStream;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
/// @author Glavo
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum NBTFileType {
|
||||
COMPRESSED("dat", "dat_old") {
|
||||
@Override
|
||||
public Tag read(Path file) throws IOException {
|
||||
return NBTCodec.of().readTag(file);
|
||||
try (BufferedInputStream fileInputStream = new BufferedInputStream(Files.newInputStream(file))) {
|
||||
fileInputStream.mark(3);
|
||||
byte[] header = new byte[3];
|
||||
if (fileInputStream.read(header) < 3) {
|
||||
throw new IOException("File is too small");
|
||||
}
|
||||
fileInputStream.reset();
|
||||
|
||||
InputStream input;
|
||||
if (Arrays.equals(header, new byte[]{0x1f, (byte) 0x8b, 0x08})) {
|
||||
input = new GZIPInputStream(fileInputStream);
|
||||
} else {
|
||||
input = fileInputStream;
|
||||
}
|
||||
|
||||
Tag tag = NBTIO.readTag(input);
|
||||
if (!(tag instanceof CompoundTag))
|
||||
throw new IOException("Unexpected tag: " + tag);
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
},
|
||||
ANVIL("mca") {
|
||||
@Override
|
||||
public NBTElement read(Path file) throws IOException {
|
||||
return NBTCodec.of().readRegion(file);
|
||||
public Tag read(Path file) throws IOException {
|
||||
return REGION.read(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
return REGION.readAsTree(file);
|
||||
}
|
||||
},
|
||||
REGION("mcr") {
|
||||
@Override
|
||||
public ChunkRegion read(Path file) throws IOException {
|
||||
return NBTCodec.of().readRegion(file);
|
||||
public Tag read(Path file) throws IOException {
|
||||
try (RandomAccessFile r = new RandomAccessFile(file.toFile(), "r")) {
|
||||
ListTag tag = new ListTag(file.getFileName().toString(), CompoundTag.class);
|
||||
if (r.length() == 0) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
byte[] header = new byte[4096];
|
||||
byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB
|
||||
Inflater inflater = new Inflater();
|
||||
|
||||
r.readFully(header);
|
||||
for (int i = 0; i < 4096; i += 4) {
|
||||
int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff);
|
||||
int length = header[i + 3] & 0xff;
|
||||
|
||||
if (offset == 0 || length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
r.seek(offset * 4096L);
|
||||
r.readFully(buffer, 0, length * 4096);
|
||||
|
||||
int chunkLength = ((buffer[0] & 0xff) << 24) + ((buffer[1] & 0xff) << 16) + ((buffer[2] & 0xff) << 8) + (buffer[3] & 0xff);
|
||||
|
||||
InputStream input = new ByteArrayInputStream(buffer);
|
||||
input.skip(5);
|
||||
input = new BoundedInputStream(input, chunkLength - 1);
|
||||
|
||||
switch (buffer[4]) {
|
||||
case 0x01:
|
||||
// GZip
|
||||
input = new GZIPInputStream(input);
|
||||
break;
|
||||
case 0x02:
|
||||
// Zlib
|
||||
inflater.reset();
|
||||
input = new InflaterInputStream(input, inflater);
|
||||
break;
|
||||
case 0x03:
|
||||
// Uncompressed
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported compression method: " + Integer.toHexString(buffer[4] & 0xff));
|
||||
}
|
||||
|
||||
try (InputStream in = input) {
|
||||
Tag chunk = NBTIO.readTag(in);
|
||||
if (!(chunk instanceof CompoundTag))
|
||||
throw new IOException("Unexpected tag: " + chunk);
|
||||
|
||||
tag.add(chunk);
|
||||
}
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
NBTTreeView.Item item = new NBTTreeView.Item(read(file));
|
||||
|
||||
for (Tag tag : ((ListTag) item.getValue())) {
|
||||
CompoundTag chunk = (CompoundTag) tag;
|
||||
|
||||
NBTTreeView.Item tree = NBTTreeView.buildTree(chunk);
|
||||
|
||||
Tag xPos = chunk.get("xPos");
|
||||
Tag zPos = chunk.get("zPos");
|
||||
|
||||
if (xPos instanceof IntTag && zPos instanceof IntTag) {
|
||||
tree.setText(String.format("Chunk: %d %d", xPos.getValue(), zPos.getValue()));
|
||||
} else {
|
||||
tree.setText("Chunk: Unknown");
|
||||
}
|
||||
|
||||
item.getChildren().add(tree);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -54,7 +168,7 @@ public enum NBTFileType {
|
||||
return NBTFileType.ofFile(file) != null;
|
||||
}
|
||||
|
||||
public static @Nullable NBTFileType ofFile(Path file) {
|
||||
public static NBTFileType ofFile(Path file) {
|
||||
String ext = FileUtils.getExtension(file);
|
||||
for (NBTFileType type : types) {
|
||||
for (String extension : type.extensions) {
|
||||
@@ -72,6 +186,11 @@ public enum NBTFileType {
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
public abstract NBTElement read(Path file) throws IOException;
|
||||
public abstract Tag read(Path file) throws IOException;
|
||||
|
||||
public NBTTreeView.Item readAsTree(Path file) throws IOException {
|
||||
NBTTreeView.Item root = NBTTreeView.buildTree(read(file));
|
||||
root.setName(file.getFileName().toString());
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 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.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public enum NBTTagType {
|
||||
BYTE, SHORT, INT, LONG, FLOAT, DOUBLE,
|
||||
BYTE_ARRAY, INT_ARRAY, LONG_ARRAY,
|
||||
STRING,
|
||||
LIST, COMPOUND;
|
||||
|
||||
private static final Map<String, NBTTagType> lookupTable = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (NBTTagType type : values()) {
|
||||
lookupTable.put(type.getTagClassName(), type);
|
||||
}
|
||||
}
|
||||
|
||||
public static NBTTagType typeOf(Tag tag) {
|
||||
NBTTagType type = lookupTable.get(tag.getClass().getSimpleName());
|
||||
if (type == null) {
|
||||
throw new IllegalArgumentException("Unknown tag: " + type);
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private final String iconUrl;
|
||||
private final String tagClassName;
|
||||
|
||||
NBTTagType() {
|
||||
String tagName;
|
||||
String className;
|
||||
|
||||
int idx = name().indexOf('_');
|
||||
if (idx < 0) {
|
||||
tagName = name().charAt(0) + name().substring(1).toLowerCase(Locale.ROOT);
|
||||
className = tagName + "Tag";
|
||||
} else {
|
||||
tagName = name().charAt(0) + name().substring(1, idx + 1).toLowerCase(Locale.ROOT)
|
||||
+ name().charAt(idx + 1) + name().substring(idx + 2).toLowerCase(Locale.ROOT);
|
||||
className = tagName.substring(0, idx) + tagName.substring(idx + 1) + "Tag";
|
||||
}
|
||||
|
||||
this.iconUrl = "/assets/img/nbt/TAG_" + tagName + ".png";
|
||||
this.tagClassName = className;
|
||||
}
|
||||
|
||||
public String getIconUrl() {
|
||||
return iconUrl;
|
||||
}
|
||||
|
||||
public String getTagClassName() {
|
||||
return tagClassName;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* 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.ui.nbt;
|
||||
|
||||
import javafx.scene.control.TreeCell;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.glavo.nbt.NBTElement;
|
||||
import org.glavo.nbt.chunk.Chunk;
|
||||
import org.glavo.nbt.chunk.ChunkRegion;
|
||||
import org.glavo.nbt.tag.*;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class NBTTreeCell extends TreeCell<@Nullable NBTElement> {
|
||||
|
||||
private static @Nullable Image getIcon(NBTElement element) {
|
||||
if (element instanceof Tag tag) {
|
||||
TagType<?> type = tag.getType();
|
||||
if (type == TagType.BYTE) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Byte.png");
|
||||
} else if (type == TagType.SHORT) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Short.png");
|
||||
} else if (type == TagType.INT) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Int.png");
|
||||
} else if (type == TagType.LONG) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Long.png");
|
||||
} else if (type == TagType.FLOAT) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Float.png");
|
||||
} else if (type == TagType.DOUBLE) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Double.png");
|
||||
} else if (type == TagType.STRING) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_String.png");
|
||||
} else if (type == TagType.BYTE_ARRAY) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Byte_Array.png");
|
||||
} else if (type == TagType.INT_ARRAY) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Int_Array.png");
|
||||
} else if (type == TagType.LONG_ARRAY) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Long_Array.png");
|
||||
} else if (type == TagType.LIST) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_List.png");
|
||||
} else if (type == TagType.COMPOUND) {
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Compound.png");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if (element instanceof ChunkRegion)
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_List.png");
|
||||
else if (element instanceof Chunk)
|
||||
return FXUtils.newBuiltinImage("/assets/img/nbt/TAG_Compound.png");
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
private final ImageView imageView;
|
||||
|
||||
public NBTTreeCell() {
|
||||
imageView = new ImageView();
|
||||
imageView.setFitHeight(16);
|
||||
imageView.setFitWidth(16);
|
||||
setGraphic(imageView);
|
||||
}
|
||||
|
||||
private NBTTreeItem getNBTTreeItem() {
|
||||
return (NBTTreeItem) getTreeItem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateItem(@Nullable NBTElement item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
imageView.setImage(null);
|
||||
setText(null);
|
||||
return;
|
||||
}
|
||||
|
||||
imageView.setImage(getIcon(item));
|
||||
|
||||
String name = getNBTTreeItem().getOverrideName();
|
||||
if (name == null) {
|
||||
NBTElement value = getNBTTreeItem().getValue();
|
||||
|
||||
if (value instanceof Tag tag) {
|
||||
if (tag.getParent() instanceof ListTag<?>) {
|
||||
name = Integer.toString(tag.getIndex());
|
||||
} else {
|
||||
name = tag.getName();
|
||||
}
|
||||
} else if (value instanceof Chunk chunk) {
|
||||
name = "Chunk (" + chunk.getLocalX() + ", " + chunk.getLocalZ() + ")";
|
||||
} else {
|
||||
name = "";
|
||||
}
|
||||
}
|
||||
|
||||
String text;
|
||||
if (item instanceof ParentTag<?> parentTag) {
|
||||
text = i18n("nbt.entries", parentTag.size());
|
||||
} else if (item instanceof ValueTag<?> valueTag) {
|
||||
text = valueTag.getAsString();
|
||||
} else {
|
||||
text = null;
|
||||
}
|
||||
|
||||
if (text == null) {
|
||||
setText(name);
|
||||
} else {
|
||||
setText(name + ": " + text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* 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.ui.nbt;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import org.glavo.nbt.NBTElement;
|
||||
import org.glavo.nbt.NBTParent;
|
||||
import org.glavo.nbt.chunk.Chunk;
|
||||
import org.glavo.nbt.tag.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/// @author Glavo
|
||||
public final class NBTTreeItem extends TreeItem<NBTElement> {
|
||||
private final @Nullable String overrideName;
|
||||
|
||||
public NBTTreeItem(NBTElement value) {
|
||||
this(value, null);
|
||||
}
|
||||
|
||||
public NBTTreeItem(NBTElement value, @Nullable String overrideName) {
|
||||
super(value);
|
||||
this.overrideName = overrideName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return !(getValue() instanceof NBTParent<?> parent) || parent.isEmpty();
|
||||
}
|
||||
|
||||
private boolean needInitChildren = true;
|
||||
|
||||
@Override
|
||||
public ObservableList<TreeItem<NBTElement>> getChildren() {
|
||||
ObservableList<TreeItem<NBTElement>> children = super.getChildren();
|
||||
if (needInitChildren) {
|
||||
needInitChildren = false;
|
||||
|
||||
if (getValue() instanceof Chunk chunk) {
|
||||
if (chunk.getRootTag() != null) {
|
||||
children.setAll(chunk.getRootTag().stream().map(NBTTreeItem::new).toList());
|
||||
}
|
||||
} else if (getValue() instanceof NBTParent<?> parent) {
|
||||
children.setAll(parent.stream().map(NBTTreeItem::new).toList());
|
||||
}
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
public @Nullable String getOverrideName() {
|
||||
return overrideName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 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.ui.nbt;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.ListTag;
|
||||
import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import javafx.scene.control.TreeCell;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.control.TreeView;
|
||||
import javafx.scene.control.skin.TreeViewSkin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.util.Callback;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.EnumMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class NBTTreeView extends TreeView<Tag> {
|
||||
|
||||
public NBTTreeView(NBTTreeView.Item tree) {
|
||||
this.setRoot(tree);
|
||||
if (tree != null) tree.setExpanded(true);
|
||||
this.setCellFactory(cellFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected javafx.scene.control.Skin<?> createDefaultSkin() {
|
||||
return new TreeViewSkin<Tag>(this) {
|
||||
{
|
||||
FXUtils.smoothScrolling(getVirtualFlow());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Callback<TreeView<Tag>, TreeCell<Tag>> cellFactory() {
|
||||
EnumMap<NBTTagType, Image> icons = new EnumMap<>(NBTTagType.class);
|
||||
|
||||
return view -> new TreeCell<>() {
|
||||
private void setTagText(String text) {
|
||||
String name = ((Item) getTreeItem()).getName();
|
||||
|
||||
if (name == null) {
|
||||
setText(text);
|
||||
} else if (text == null) {
|
||||
setText(name);
|
||||
} else {
|
||||
setText(name + ": " + text);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTagText(int nEntries) {
|
||||
setTagText(i18n("nbt.entries", nEntries));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateItem(Tag item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
ImageView imageView = (ImageView) this.getGraphic();
|
||||
if (imageView == null) {
|
||||
imageView = new ImageView();
|
||||
this.setGraphic(imageView);
|
||||
}
|
||||
|
||||
if (item == null) {
|
||||
imageView.setImage(null);
|
||||
setText(null);
|
||||
return;
|
||||
}
|
||||
|
||||
NBTTagType tagType = NBTTagType.typeOf(item);
|
||||
imageView.setImage(icons.computeIfAbsent(tagType, type -> new Image(type.getIconUrl())));
|
||||
imageView.setFitHeight(16);
|
||||
imageView.setFitWidth(16);
|
||||
|
||||
if (((Item) getTreeItem()).getText() != null) {
|
||||
setText(((Item) getTreeItem()).getText());
|
||||
} else {
|
||||
switch (tagType) {
|
||||
case BYTE:
|
||||
case SHORT:
|
||||
case INT:
|
||||
case LONG:
|
||||
case FLOAT:
|
||||
case DOUBLE:
|
||||
case STRING:
|
||||
setTagText(item.getValue().toString());
|
||||
break;
|
||||
case BYTE_ARRAY:
|
||||
case INT_ARRAY:
|
||||
case LONG_ARRAY:
|
||||
setTagText(Array.getLength(item.getValue()));
|
||||
break;
|
||||
case LIST:
|
||||
setTagText(((ListTag) item).size());
|
||||
break;
|
||||
case COMPOUND:
|
||||
setTagText(((CompoundTag) item).size());
|
||||
break;
|
||||
default:
|
||||
setTagText(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Item buildTree(Tag tag) {
|
||||
Item item = new Item(tag);
|
||||
|
||||
if (tag instanceof CompoundTag) {
|
||||
for (Tag subTag : ((CompoundTag) tag)) {
|
||||
item.getChildren().add(buildTree(subTag));
|
||||
}
|
||||
} else if (tag instanceof ListTag) {
|
||||
int idx = 0;
|
||||
for (Tag subTag : ((ListTag) tag)) {
|
||||
Item subTree = buildTree(subTag);
|
||||
subTree.setName(String.valueOf(idx++));
|
||||
item.getChildren().add(subTree);
|
||||
}
|
||||
}
|
||||
FXUtils.onChangeAndOperate(item.expandedProperty(), expanded -> {
|
||||
if (expanded && item.getChildren().size() == 1) item.getChildren().get(0).setExpanded(true);
|
||||
});
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public CompoundTag getRootTag() {
|
||||
return ((CompoundTag) getRoot().getValue());
|
||||
}
|
||||
|
||||
public static class Item extends TreeItem<Tag> {
|
||||
|
||||
private String text;
|
||||
private String name;
|
||||
|
||||
public Item() {
|
||||
}
|
||||
|
||||
public Item(Tag value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name == null ? getValue().getName() : name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
@NotNullByDefault
|
||||
package org.jackhuang.hmcl.ui.nbt;
|
||||
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
@@ -212,11 +195,9 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
|
||||
useNativeGLFWPane = new LineToggleButton();
|
||||
useNativeGLFWPane.setTitle(i18n("settings.advanced.use_native_glfw"));
|
||||
useNativeGLFWPane.setSubtitle(i18n("settings.advanced.linux_freebsd_only"));
|
||||
|
||||
useNativeOpenALPane = new LineToggleButton();
|
||||
useNativeOpenALPane.setTitle(i18n("settings.advanced.use_native_openal"));
|
||||
useNativeOpenALPane.setSubtitle(i18n("settings.advanced.linux_freebsd_only"));
|
||||
|
||||
workaroundPane.getContent().setAll(
|
||||
nativesDirSublist, rendererPane, noJVMArgsPane, noOptimizingJVMArgsPane, noGameCheckPane,
|
||||
|
||||
+25
-25
@@ -22,7 +22,7 @@ import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.mod.DataPack;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -44,45 +44,45 @@ import java.util.regex.Pattern;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class DataPackListPage extends ListPageBase<DataPackListPageSkin.DataPackInfoObject> implements WorldManagePage.WorldRefreshable {
|
||||
public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> implements WorldManagePage.WorldRefreshable {
|
||||
private final World world;
|
||||
private final DataPack dataPack;
|
||||
private final Datapack datapack;
|
||||
final BooleanProperty readOnly;
|
||||
|
||||
public DataPackListPage(WorldManagePage worldManagePage) {
|
||||
public DatapackListPage(WorldManagePage worldManagePage) {
|
||||
world = worldManagePage.getWorld();
|
||||
dataPack = new DataPack(world.getFile().resolve("datapacks"));
|
||||
setItems(MappedObservableList.create(dataPack.getPacks(), DataPackListPageSkin.DataPackInfoObject::new));
|
||||
datapack = new Datapack(world.getFile().resolve("datapacks"));
|
||||
setItems(MappedObservableList.create(datapack.getPacks(), DatapackListPageSkin.DatapackInfoObject::new));
|
||||
readOnly = worldManagePage.readOnlyProperty();
|
||||
FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)),
|
||||
this::installMultiDataPack, this::refresh);
|
||||
this::installMultiDatapack, this::refresh);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
private void installMultiDataPack(List<Path> dataPackPath) {
|
||||
dataPackPath.forEach(this::installSingleDataPack);
|
||||
private void installMultiDatapack(List<Path> datapackPath) {
|
||||
datapackPath.forEach(this::installSingleDatapack);
|
||||
if (readOnly.get()) {
|
||||
Controllers.showToast(i18n("datapack.reload.toast"));
|
||||
}
|
||||
}
|
||||
|
||||
private void installSingleDataPack(Path dataPack) {
|
||||
private void installSingleDatapack(Path datapack) {
|
||||
try {
|
||||
this.dataPack.installPack(dataPack, world.getGameVersion());
|
||||
this.datapack.installPack(datapack, world.getGameVersion());
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
LOG.warning("Unable to parse datapack file " + dataPack, e);
|
||||
LOG.warning("Unable to parse datapack file " + datapack, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new DataPackListPageSkin(this);
|
||||
return new DatapackListPageSkin(this);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
setLoading(true);
|
||||
Task.runAsync(dataPack::loadFromDir)
|
||||
Task.runAsync(datapack::loadFromDir)
|
||||
.withRunAsync(Schedulers.javafx(), () -> setLoading(false))
|
||||
.start();
|
||||
}
|
||||
@@ -94,18 +94,18 @@ public final class DataPackListPage extends ListPageBase<DataPackListPageSkin.Da
|
||||
List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
|
||||
if (res != null) {
|
||||
installMultiDataPack(res);
|
||||
installMultiDatapack(res);
|
||||
}
|
||||
|
||||
dataPack.loadFromDir();
|
||||
datapack.loadFromDir();
|
||||
}
|
||||
|
||||
void removeSelected(ObservableList<DataPackListPageSkin.DataPackInfoObject> selectedItems) {
|
||||
void removeSelected(ObservableList<DatapackListPageSkin.DatapackInfoObject> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(DataPackListPageSkin.DataPackInfoObject::getPackInfo)
|
||||
.map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)
|
||||
.forEach(pack -> {
|
||||
try {
|
||||
dataPack.deletePack(pack);
|
||||
datapack.deletePack(pack);
|
||||
} catch (IOException e) {
|
||||
// Fail to remove mods if the game is running or the datapack is absent.
|
||||
LOG.warning("Failed to delete datapack \"" + pack.getId() + "\"", e);
|
||||
@@ -113,23 +113,23 @@ public final class DataPackListPage extends ListPageBase<DataPackListPageSkin.Da
|
||||
});
|
||||
}
|
||||
|
||||
void enableSelected(ObservableList<DataPackListPageSkin.DataPackInfoObject> selectedItems) {
|
||||
void enableSelected(ObservableList<DatapackListPageSkin.DatapackInfoObject> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(DataPackListPageSkin.DataPackInfoObject::getPackInfo)
|
||||
.map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)
|
||||
.forEach(pack -> pack.setActive(true));
|
||||
}
|
||||
|
||||
void disableSelected(ObservableList<DataPackListPageSkin.DataPackInfoObject> selectedItems) {
|
||||
void disableSelected(ObservableList<DatapackListPageSkin.DatapackInfoObject> selectedItems) {
|
||||
selectedItems.stream()
|
||||
.map(DataPackListPageSkin.DataPackInfoObject::getPackInfo)
|
||||
.map(DatapackListPageSkin.DatapackInfoObject::getPackInfo)
|
||||
.forEach(pack -> pack.setActive(false));
|
||||
}
|
||||
|
||||
void openDataPackFolder() {
|
||||
FXUtils.openFolder(dataPack.getPath());
|
||||
FXUtils.openFolder(datapack.getPath());
|
||||
}
|
||||
|
||||
@NotNull Predicate<DataPackListPageSkin.DataPackInfoObject> updateSearchPredicate(String queryString) {
|
||||
@NotNull Predicate<DatapackListPageSkin.DatapackInfoObject> updateSearchPredicate(String queryString) {
|
||||
if (queryString.isBlank()) {
|
||||
return dataPack -> true;
|
||||
}
|
||||
+16
-16
@@ -42,7 +42,7 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.mod.DataPack;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -67,7 +67,7 @@ import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
final class DatapackListPageSkin extends SkinBase<DatapackListPage> {
|
||||
|
||||
private final TransitionPane toolbarPane;
|
||||
private final HBox searchBar;
|
||||
@@ -75,8 +75,8 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
private final HBox selectingToolbar;
|
||||
InvalidationListener updateBarByStateWeakListener;
|
||||
|
||||
private final JFXListView<DataPackInfoObject> listView;
|
||||
private final FilteredList<DataPackInfoObject> filteredList;
|
||||
private final JFXListView<DatapackInfoObject> listView;
|
||||
private final FilteredList<DatapackInfoObject> filteredList;
|
||||
|
||||
private final BooleanProperty isSearching = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty isSelecting = new SimpleBooleanProperty(false);
|
||||
@@ -85,7 +85,7 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
private static final AtomicInteger lastShiftClickIndex = new AtomicInteger(-1);
|
||||
final Consumer<Integer> toggleSelect;
|
||||
|
||||
DataPackListPageSkin(DataPackListPage skinnable) {
|
||||
DatapackListPageSkin(DatapackListPage skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
StackPane pane = new StackPane();
|
||||
@@ -181,7 +181,7 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
ComponentList.setVgrow(center, Priority.ALWAYS);
|
||||
center.loadingProperty().bind(skinnable.loadingProperty());
|
||||
|
||||
listView.setCellFactory(x -> new DataPackInfoListCell(listView, getSkinnable().readOnly));
|
||||
listView.setCellFactory(x -> new DatapackInfoListCell(listView, getSkinnable().readOnly));
|
||||
listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
this.listView.setItems(filteredList);
|
||||
|
||||
@@ -216,13 +216,13 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
static class DataPackInfoObject extends RecursiveTreeObject<DataPackInfoObject> {
|
||||
static class DatapackInfoObject extends RecursiveTreeObject<DatapackInfoObject> {
|
||||
private final BooleanProperty activeProperty;
|
||||
private final DataPack.Pack packInfo;
|
||||
private final Datapack.Pack packInfo;
|
||||
|
||||
private SoftReference<CompletableFuture<Image>> iconCache;
|
||||
|
||||
DataPackInfoObject(DataPack.Pack packInfo) {
|
||||
DatapackInfoObject(Datapack.Pack packInfo) {
|
||||
this.packInfo = packInfo;
|
||||
this.activeProperty = packInfo.activeProperty();
|
||||
}
|
||||
@@ -235,7 +235,7 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
return packInfo.getDescription().toString();
|
||||
}
|
||||
|
||||
DataPack.Pack getPackInfo() {
|
||||
Datapack.Pack getPackInfo() {
|
||||
return packInfo;
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<DataPackInfoObject>> current) {
|
||||
public void loadIcon(ImageContainer imageContainer, @Nullable WeakReference<ObjectProperty<DatapackInfoObject>> current) {
|
||||
SoftReference<CompletableFuture<Image>> iconCache = this.iconCache;
|
||||
CompletableFuture<Image> imageFuture;
|
||||
if (iconCache != null && (imageFuture = iconCache.get()) != null) {
|
||||
@@ -286,7 +286,7 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
imageContainer.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png"));
|
||||
imageFuture.thenAcceptAsync(image -> {
|
||||
if (current != null) {
|
||||
ObjectProperty<DataPackInfoObject> infoObjectProperty = current.get();
|
||||
ObjectProperty<DatapackInfoObject> infoObjectProperty = current.get();
|
||||
if (infoObjectProperty == null || infoObjectProperty.get() != this) {
|
||||
// The current ListCell has already switched to another object
|
||||
return;
|
||||
@@ -298,13 +298,13 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
private final class DataPackInfoListCell extends MDListCell<DataPackInfoObject> {
|
||||
private final class DatapackInfoListCell extends MDListCell<DatapackInfoObject> {
|
||||
final JFXCheckBox checkBox = new JFXCheckBox();
|
||||
ImageContainer imageContainer = new ImageContainer(32);
|
||||
final TwoLineListItem content = new TwoLineListItem();
|
||||
BooleanProperty booleanProperty;
|
||||
|
||||
DataPackInfoListCell(JFXListView<DataPackInfoObject> listView, BooleanProperty isReadOnlyProperty) {
|
||||
DatapackInfoListCell(JFXListView<DatapackInfoObject> listView, BooleanProperty isReadOnlyProperty) {
|
||||
super(listView);
|
||||
|
||||
HBox container = new HBox(8);
|
||||
@@ -326,7 +326,7 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(DataPackInfoObject dataItem, boolean empty) {
|
||||
protected void updateControl(DatapackInfoObject dataItem, boolean empty) {
|
||||
if (empty) return;
|
||||
content.setTitle(dataItem.getTitle());
|
||||
content.setSubtitle(dataItem.getSubtitle());
|
||||
@@ -338,7 +338,7 @@ final class DataPackListPageSkin extends SkinBase<DataPackListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
public void handleSelect(DataPackInfoListCell cell, MouseEvent mouseEvent) {
|
||||
public void handleSelect(DatapackInfoListCell cell, MouseEvent mouseEvent) {
|
||||
if (cell.isEmpty()) {
|
||||
mouseEvent.consume();
|
||||
return;
|
||||
@@ -27,8 +27,16 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
@@ -46,7 +54,11 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
@@ -54,7 +66,14 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -294,55 +313,25 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
}
|
||||
|
||||
final class Versions {
|
||||
static ComponentSublist createSublist(DownloadPage control, String title, List<String> target) {
|
||||
var sublist = new ComponentSublist(() -> {
|
||||
ArrayList<ModItem> items = new ArrayList<>();
|
||||
for (String gv : target) {
|
||||
List<RemoteMod.Version> versions = control.versions.get(gv);
|
||||
if (versions != null) {
|
||||
for (RemoteMod.Version v : versions) {
|
||||
items.add(new ModItem(control.addon, v, control));
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
});
|
||||
|
||||
sublist.setTitle(title);
|
||||
sublist.getStyleClass().add("no-padding");
|
||||
|
||||
return sublist;
|
||||
for (String gameVersion : control.versions.keys().stream()
|
||||
.sorted(Collections.reverseOrder(GameVersionNumber::compare))
|
||||
.toList()) {
|
||||
List<RemoteMod.Version> versions = control.versions.get(gameVersion);
|
||||
if (versions == null || versions.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final List<String> releases = new ArrayList<>();
|
||||
final List<String> snapshots = new ArrayList<>();
|
||||
}
|
||||
var sublist = new ComponentSublist(() -> {
|
||||
ArrayList<ModItem> items = new ArrayList<>(versions.size());
|
||||
for (RemoteMod.Version v : versions) {
|
||||
items.add(new ModItem(control.addon, v, control));
|
||||
}
|
||||
return items;
|
||||
});
|
||||
sublist.getStyleClass().add("no-padding");
|
||||
sublist.setTitle("Minecraft " + gameVersion);
|
||||
|
||||
TreeMap<GameVersionNumber, Versions> map = new TreeMap<>(Collections.reverseOrder());
|
||||
for (String version : control.versions.keys()) {
|
||||
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(version);
|
||||
GameVersionNumber releaseOfSnapshot = GameVersionNumber.getReleaseOfSnapshot(gameVersion);
|
||||
GameVersionNumber releaseVersion = Objects.requireNonNullElse(releaseOfSnapshot, gameVersion);
|
||||
|
||||
Versions lists = map.computeIfAbsent(releaseVersion, v -> new Versions());
|
||||
|
||||
if (releaseOfSnapshot == null) {
|
||||
lists.releases.add(version);
|
||||
} else {
|
||||
lists.snapshots.add(version);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<GameVersionNumber, Versions> entry : map.entrySet()) {
|
||||
GameVersionNumber gameVersion = entry.getKey();
|
||||
Versions versions = entry.getValue();
|
||||
|
||||
if (!versions.releases.isEmpty())
|
||||
list.getContent().add(Versions.createSublist(control, i18n("addon.download.title.release", gameVersion), versions.releases));
|
||||
|
||||
if (!versions.snapshots.isEmpty())
|
||||
list.getContent().add(Versions.createSublist(control, i18n("addon.download.title.snapshot", gameVersion), versions.snapshots));
|
||||
list.getContent().add(sublist);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+1
-6
@@ -61,12 +61,7 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
repository = new Repository(type, curseForge, modrinth);
|
||||
|
||||
supportChinese.set(true);
|
||||
|
||||
downloadSources.setAll("mods.modrinth");
|
||||
if (CurseForgeRemoteModRepository.isAvailable()) {
|
||||
downloadSources.add("mods.curseforge");
|
||||
}
|
||||
|
||||
downloadSources.get().setAll("mods.modrinth", "mods.curseforge");
|
||||
if (modrinth != null) {
|
||||
downloadSource.set("mods.modrinth");
|
||||
} else if (curseForge != null) {
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
@@ -42,7 +43,6 @@ import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.MemoryStatusBar;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
@@ -57,6 +57,7 @@ import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -114,6 +115,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
private final IntegerProperty maxMemory = new SimpleIntegerProperty();
|
||||
private final BooleanProperty modpack = new SimpleBooleanProperty();
|
||||
|
||||
private final ReadOnlyObjectProperty<PhysicalMemoryStatus> memoryStatus = UpdateMemoryStatus.memoryStatusProperty();
|
||||
|
||||
public VersionSettingsPage(boolean globalSetting) {
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setFitToHeight(true);
|
||||
@@ -293,14 +296,30 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
lowerBoundPane.getChildren().setAll(label, slider, txtMaxMemory, new Label(i18n("settings.memory.unit.mib")));
|
||||
}
|
||||
|
||||
MemoryStatusBar memoryStatusBar = new MemoryStatusBar();
|
||||
VBox.setMargin(memoryStatusBar, new Insets(8, 0, 0, 16));
|
||||
memoryStatusBar.memoryAllocatedProperty().bind(Bindings.createDoubleBinding(() ->
|
||||
(double) HMCLGameRepository.getAllocatedMemory(
|
||||
maxMemory.get() * 1024L * 1024L,
|
||||
memoryStatusBar.getMemoryStatus().getAvailable(),
|
||||
chkAutoAllocate.isSelected()),
|
||||
memoryStatusBar.memoryStatusProperty(), maxMemory, chkAutoAllocate.selectedProperty()));
|
||||
StackPane progressBarPane = new StackPane();
|
||||
progressBarPane.setAlignment(Pos.CENTER_LEFT);
|
||||
VBox.setMargin(progressBarPane, new Insets(8, 0, 0, 16));
|
||||
{
|
||||
progressBarPane.setMinHeight(4);
|
||||
progressBarPane.getStyleClass().add("memory-total");
|
||||
|
||||
StackPane usedMemory = new StackPane();
|
||||
usedMemory.getStyleClass().add("memory-used");
|
||||
usedMemory.maxWidthProperty().bind(Bindings.createDoubleBinding(() ->
|
||||
progressBarPane.getWidth() *
|
||||
(memoryStatus.get().getUsed() * 1.0 / memoryStatus.get().getTotal()), progressBarPane.widthProperty(),
|
||||
memoryStatus));
|
||||
StackPane allocateMemory = new StackPane();
|
||||
allocateMemory.getStyleClass().add("memory-allocate");
|
||||
allocateMemory.maxWidthProperty().bind(Bindings.createDoubleBinding(() ->
|
||||
progressBarPane.getWidth() *
|
||||
Math.min(1.0,
|
||||
(double) (HMCLGameRepository.getAllocatedMemory(maxMemory.get() * 1024L * 1024L, memoryStatus.get().getAvailable(), chkAutoAllocate.isSelected())
|
||||
+ memoryStatus.get().getUsed()) / memoryStatus.get().getTotal()), progressBarPane.widthProperty(),
|
||||
maxMemory, memoryStatus, chkAutoAllocate.selectedProperty()));
|
||||
|
||||
progressBarPane.getChildren().setAll(allocateMemory, usedMemory);
|
||||
}
|
||||
|
||||
BorderPane digitalPane = new BorderPane();
|
||||
VBox.setMargin(digitalPane, new Insets(0, 0, 0, 16));
|
||||
@@ -308,29 +327,26 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
Label lblPhysicalMemory = new Label();
|
||||
lblPhysicalMemory.getStyleClass().add("memory-label");
|
||||
digitalPane.setLeft(lblPhysicalMemory);
|
||||
lblPhysicalMemory.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
PhysicalMemoryStatus memoryStatus = memoryStatusBar.getMemoryStatus();
|
||||
return i18n("settings.memory.used_per_total",
|
||||
GIGABYTES.convertFromBytes(memoryStatus.getUsed()),
|
||||
GIGABYTES.convertFromBytes(memoryStatus.getTotal()));
|
||||
}, memoryStatusBar.memoryStatusProperty()));
|
||||
lblPhysicalMemory.textProperty().bind(Bindings.createStringBinding(() ->
|
||||
i18n("settings.memory.used_per_total",
|
||||
GIGABYTES.convertFromBytes(memoryStatus.get().getUsed()),
|
||||
GIGABYTES.convertFromBytes(memoryStatus.get().getTotal())), memoryStatus));
|
||||
|
||||
Label lblAllocateMemory = new Label();
|
||||
lblAllocateMemory.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
PhysicalMemoryStatus memoryStatus = memoryStatusBar.getMemoryStatus();
|
||||
long maxMemory = Lang.parseInt(this.maxMemory.get(), 0) * 1024L * 1024L;
|
||||
return i18n(memoryStatus.hasAvailable() && maxMemory > memoryStatus.getAvailable()
|
||||
return i18n(memoryStatus.get().hasAvailable() && maxMemory > memoryStatus.get().getAvailable()
|
||||
? (chkAutoAllocate.isSelected() ? "settings.memory.allocate.auto.exceeded" : "settings.memory.allocate.manual.exceeded")
|
||||
: (chkAutoAllocate.isSelected() ? "settings.memory.allocate.auto" : "settings.memory.allocate.manual"),
|
||||
GIGABYTES.convertFromBytes(maxMemory),
|
||||
GIGABYTES.convertFromBytes(HMCLGameRepository.getAllocatedMemory(maxMemory, memoryStatus.getAvailable(), chkAutoAllocate.isSelected())),
|
||||
GIGABYTES.convertFromBytes(memoryStatus.getAvailable()));
|
||||
}, memoryStatusBar.memoryStatusProperty(), maxMemory, chkAutoAllocate.selectedProperty()));
|
||||
GIGABYTES.convertFromBytes(HMCLGameRepository.getAllocatedMemory(maxMemory, memoryStatus.get().getAvailable(), chkAutoAllocate.isSelected())),
|
||||
GIGABYTES.convertFromBytes(memoryStatus.get().getAvailable()));
|
||||
}, memoryStatus, maxMemory, chkAutoAllocate.selectedProperty()));
|
||||
lblAllocateMemory.getStyleClass().add("memory-label");
|
||||
digitalPane.setRight(lblAllocateMemory);
|
||||
}
|
||||
|
||||
maxMemoryPane.getChildren().setAll(title, chkAutoAllocate, lowerBoundPane, memoryStatusBar, digitalPane);
|
||||
maxMemoryPane.getChildren().setAll(title, chkAutoAllocate, lowerBoundPane, progressBarPane, digitalPane);
|
||||
}
|
||||
|
||||
launcherVisibilityPane = new LineSelectButton<>();
|
||||
@@ -456,7 +472,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
componentList.disableProperty().bind(enableSpecificSettings.not());
|
||||
}
|
||||
|
||||
|
||||
private void fireWorkingDirChanged() {
|
||||
FXUtils.runInFX(() -> fireEvent(new VersionPage.WorkingDirChangedEvent()));
|
||||
}
|
||||
@@ -760,4 +776,57 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
return state.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private static final class UpdateMemoryStatus extends Thread {
|
||||
|
||||
@FXThread
|
||||
private static WeakReference<ObjectProperty<PhysicalMemoryStatus>> memoryStatusPropertyCache;
|
||||
|
||||
@FXThread
|
||||
static ReadOnlyObjectProperty<PhysicalMemoryStatus> memoryStatusProperty() {
|
||||
if (memoryStatusPropertyCache != null) {
|
||||
var property = memoryStatusPropertyCache.get();
|
||||
if (property != null) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
ObjectProperty<PhysicalMemoryStatus> property = new SimpleObjectProperty<>(PhysicalMemoryStatus.INVALID);
|
||||
memoryStatusPropertyCache = new WeakReference<>(property);
|
||||
new UpdateMemoryStatus(memoryStatusPropertyCache).start();
|
||||
return property;
|
||||
}
|
||||
|
||||
private final WeakReference<ObjectProperty<PhysicalMemoryStatus>> memoryStatusPropertyRef;
|
||||
|
||||
UpdateMemoryStatus(WeakReference<ObjectProperty<PhysicalMemoryStatus>> memoryStatusPropertyRef) {
|
||||
this.memoryStatusPropertyRef = memoryStatusPropertyRef;
|
||||
|
||||
setName("UpdateMemoryStatus");
|
||||
setDaemon(true);
|
||||
setPriority(Thread.MIN_PRIORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
PhysicalMemoryStatus status = SystemInfo.getPhysicalMemoryStatus();
|
||||
|
||||
var memoryStatusProperty = memoryStatusPropertyRef.get();
|
||||
if (memoryStatusProperty == null)
|
||||
return;
|
||||
|
||||
if (Controllers.isStopped())
|
||||
return;
|
||||
|
||||
Platform.runLater(() -> memoryStatusProperty.set(status));
|
||||
|
||||
try {
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
@@ -33,7 +34,6 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.glavo.nbt.tag.*;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -76,7 +76,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
}
|
||||
|
||||
private void updateControls() {
|
||||
CompoundTag dataTag = (CompoundTag) levelData.get("Data");
|
||||
CompoundTag dataTag = levelData.get("Data");
|
||||
CompoundTag playerTag = playerData;
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
@@ -100,7 +100,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
setRightTextField(worldNamePane, worldNameField, 200);
|
||||
|
||||
if (dataTag.get("LevelName") instanceof StringTag worldNameTag) {
|
||||
var worldName = new SimpleStringProperty(worldNameTag.get());
|
||||
var worldName = new SimpleStringProperty(worldNameTag.getValue());
|
||||
FXUtils.bindString(worldNameField, worldName);
|
||||
worldNameField.getProperties().put(WorldInfoPage.class.getName() + ".worldNameProperty", worldName);
|
||||
worldName.addListener((observable, oldValue, newValue) -> {
|
||||
@@ -324,7 +324,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
{
|
||||
locationPane.setTitle(i18n("world.info.player.location"));
|
||||
Dimension dimension = Dimension.of(playerTag.get("Dimension"));
|
||||
if (dimension != null && playerTag.get("Pos") instanceof ListTag<?> posTag) {
|
||||
if (dimension != null && playerTag.get("Pos") instanceof ListTag posTag) {
|
||||
locationPane.setText(dimension.formatPosition(posTag));
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
if (playerTag.get("respawn") instanceof CompoundTag respawnTag
|
||||
&& respawnTag.get("dimension") instanceof StringTag dimensionTag
|
||||
&& respawnTag.get("pos") instanceof IntArrayTag intArrayTag
|
||||
&& intArrayTag.size() >= 3) { // Valid after 25w07a
|
||||
&& intArrayTag.length() >= 3) { // Valid after 25w07a
|
||||
spawnPane.setText(Dimension.of(dimensionTag).formatPosition(intArrayTag));
|
||||
} else if (playerTag.get("SpawnX") instanceof IntTag intX
|
||||
&& playerTag.get("SpawnY") instanceof IntTag intY
|
||||
@@ -541,7 +541,7 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
|
||||
static Dimension of(Tag tag) {
|
||||
if (tag instanceof IntTag intTag) {
|
||||
return switch (intTag.get()) {
|
||||
return switch (intTag.getValue()) {
|
||||
case 0 -> OVERWORLD;
|
||||
case -1 -> THE_NETHER;
|
||||
case 1 -> THE_END;
|
||||
@@ -561,23 +561,30 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
}
|
||||
|
||||
String formatPosition(Tag tag) {
|
||||
if (tag instanceof ListTag<?> listTag && listTag.size() == 3) {
|
||||
if (tag instanceof ListTag listTag && listTag.size() == 3) {
|
||||
|
||||
if (listTag.getTag(0) instanceof DoubleTag x
|
||||
&& listTag.getTag(1) instanceof DoubleTag y
|
||||
&& listTag.getTag(2) instanceof DoubleTag z) {
|
||||
return formatPosition(x.get(), y.get(), z.get());
|
||||
Tag x = listTag.get(0);
|
||||
Tag y = listTag.get(1);
|
||||
Tag z = listTag.get(2);
|
||||
|
||||
if (x instanceof DoubleTag && y instanceof DoubleTag && z instanceof DoubleTag) {
|
||||
return this == OVERWORLD
|
||||
? String.format("(%.2f, %.2f, %.2f)", x.getValue(), y.getValue(), z.getValue())
|
||||
: String.format("%s (%.2f, %.2f, %.2f)", name, x.getValue(), y.getValue(), z.getValue());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tag instanceof IntArrayTag intArrayTag) {
|
||||
int x = intArrayTag.get(0);
|
||||
int y = intArrayTag.get(1);
|
||||
int z = intArrayTag.get(2);
|
||||
|
||||
return formatPosition(x, y, z);
|
||||
int x = intArrayTag.getValue(0);
|
||||
int y = intArrayTag.getValue(1);
|
||||
int z = intArrayTag.getValue(2);
|
||||
|
||||
return this == OVERWORLD
|
||||
? String.format("(%d, %d, %d)", x, y, z)
|
||||
: String.format("%s (%d, %d, %d)", name, x, y, z);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -585,14 +592,14 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage.
|
||||
|
||||
String formatPosition(int x, int y, int z) {
|
||||
return this == OVERWORLD
|
||||
? "(%d, %d, %d)".formatted(x, y, z)
|
||||
: "%s (%d, %d, %d)".formatted(name, x, y, z);
|
||||
? String.format("(%d, %d, %d)", x, y, z)
|
||||
: String.format("%s (%d, %d, %d)", name, x, y, z);
|
||||
}
|
||||
|
||||
String formatPosition(double x, double y, double z) {
|
||||
return this == OVERWORLD
|
||||
? "(%.2f, %.2f, %.2f)".formatted(x, y, z)
|
||||
: "%s (%.2f, %.2f, %.2f)".formatted(name, x, y, z);
|
||||
? String.format("(%.2f, %.2f, %.2f)", x, y, z)
|
||||
: String.format("%s (%.2f, %.2f, %.2f)", name, x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
private final TabHeader header = new TabHeader(transitionPane);
|
||||
private final TabHeader.Tab<WorldInfoPage> worldInfoTab = new TabHeader.Tab<>("worldInfoPage");
|
||||
private final TabHeader.Tab<WorldBackupsPage> worldBackupsTab = new TabHeader.Tab<>("worldBackupsPage");
|
||||
private final TabHeader.Tab<DataPackListPage> dataPackTab = new TabHeader.Tab<>("dataPackListPage");
|
||||
private final TabHeader.Tab<DatapackListPage> datapackTab = new TabHeader.Tab<>("datapackListPage");
|
||||
|
||||
public WorldManagePage(World world, Profile profile, String instanceId) {
|
||||
this.world = world;
|
||||
@@ -86,7 +86,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
|
||||
worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this));
|
||||
worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this));
|
||||
dataPackTab.setNodeSupplier(() -> new DataPackListPage(this));
|
||||
datapackTab.setNodeSupplier(() -> new DatapackListPage(this));
|
||||
|
||||
this.state = new SimpleObjectProperty<>(new State(i18n("world.manage.title", StringUtils.parseColorEscapes(world.getWorldName())), null, true, true, true));
|
||||
|
||||
@@ -218,9 +218,9 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
tabBar.addNavigationDrawerTab(getSkinnable().header, getSkinnable().worldInfoTab, i18n("world.info"), SVG.INFO, SVG.INFO_FILL)
|
||||
.addNavigationDrawerTab(getSkinnable().header, getSkinnable().worldBackupsTab, i18n("world.backup"), SVG.ARCHIVE, SVG.ARCHIVE_FILL);
|
||||
|
||||
if (getSkinnable().world.supportDataPacks()) {
|
||||
getSkinnable().header.getTabs().add(getSkinnable().dataPackTab);
|
||||
tabBar.addNavigationDrawerTab(getSkinnable().header, getSkinnable().dataPackTab, i18n("world.datapack"), SVG.EXTENSION, SVG.EXTENSION_FILL);
|
||||
if (getSkinnable().world.supportDatapacks()) {
|
||||
getSkinnable().header.getTabs().add(getSkinnable().datapackTab);
|
||||
tabBar.addNavigationDrawerTab(getSkinnable().header, getSkinnable().datapackTab, i18n("world.datapack"), SVG.EXTENSION, SVG.EXTENSION_FILL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,27 +31,21 @@ public final class ChunkBaseApp {
|
||||
private static final GameVersionNumber MIN_END_CITY_VERSION = GameVersionNumber.asGameVersion("1.13");
|
||||
|
||||
private static final String[] SEED_MAP_GAME_VERSIONS = {
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13", "1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
"1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21", "1.20",
|
||||
"1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14", "1.13",
|
||||
"1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] STRONGHOLD_FINDER_GAME_VERSIONS = {
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13", "1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.16", "1.13", "1.9", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] NETHER_FORTRESS_GAME_VERSIONS = {
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13", "1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
|
||||
"1.18", "1.16", "1.7"
|
||||
};
|
||||
|
||||
public static final String[] END_CITY_GAME_VERSIONS = {
|
||||
"26.1", "1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21",
|
||||
"1.20", "1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14",
|
||||
"1.13"
|
||||
"1.19", "1.13"
|
||||
};
|
||||
|
||||
public static boolean isSupported(@NotNull World world) {
|
||||
|
||||
@@ -63,16 +63,6 @@ public final class NativePatcher {
|
||||
});
|
||||
}
|
||||
|
||||
// https://github.com/LWJGL/lwjgl3/issues/1111
|
||||
public static boolean needPatchMemoryUtil(Version version, int javaVersion) {
|
||||
return javaVersion >= 25 && javaVersion <= 26 && version.getLibraries().stream().anyMatch(library ->
|
||||
"org.lwjgl".equals(library.getGroupId())
|
||||
&& "lwjgl".equals(library.getArtifactId())
|
||||
&& "3.4.1".equals(library.getVersion())
|
||||
&& library.getClassifier() == null
|
||||
);
|
||||
}
|
||||
|
||||
public static Version patchNative(DefaultGameRepository repository,
|
||||
Version version, String gameVersion,
|
||||
JavaRuntime javaVersion,
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
"externalLink" : "https://github.com/jenkinsci/constant-pool-scanner"
|
||||
},
|
||||
{
|
||||
"title" : "HelloNBT",
|
||||
"subtitle" : "Copyright © 2026 Glavo.\nLicensed under the Apache 2.0 License.",
|
||||
"externalLink" : "https://github.com/Glavo/HelloNBT"
|
||||
"title" : "OpenNBT",
|
||||
"subtitle" : "Copyright © 2013-2021 Steveice10.\nLicensed under the MIT License.",
|
||||
"externalLink" : "https://github.com/GeyserMC/OpenNBT"
|
||||
},
|
||||
{
|
||||
"title" : "minecraft-jfx-skin",
|
||||
|
||||
@@ -113,26 +113,28 @@
|
||||
.memory-label {
|
||||
}
|
||||
|
||||
.memory-status-bar {
|
||||
-fx-opacity: 1;
|
||||
.memory-used {
|
||||
-fx-background-color: -monet-primary;
|
||||
}
|
||||
|
||||
.memory-status-bar:disabled {
|
||||
.memory-used:disabled {
|
||||
-fx-opacity: 0.4;
|
||||
}
|
||||
|
||||
.memory-status-bar .track {
|
||||
-fx-background-color: -monet-secondary-container;
|
||||
-fx-background-radius: 2;
|
||||
-fx-background-insets: 0;
|
||||
.memory-allocate {
|
||||
-fx-background-color: -monet-primary-transparent-50;
|
||||
}
|
||||
|
||||
.memory-status-bar .memory-used {
|
||||
-fx-fill: -monet-primary;
|
||||
.memory-allocate:disabled {
|
||||
-fx-opacity: 0.4;
|
||||
}
|
||||
|
||||
.memory-status-bar .memory-allocate {
|
||||
-fx-fill: -monet-primary-transparent-50;
|
||||
.memory-total {
|
||||
-fx-background-color: -monet-surface-container;
|
||||
}
|
||||
|
||||
.memory-total:disabled {
|
||||
-fx-opacity: 0.4;
|
||||
}
|
||||
|
||||
.update-label {
|
||||
@@ -525,8 +527,7 @@
|
||||
}
|
||||
|
||||
.jfx-dialog-layout {
|
||||
-fx-background-radius: 4px;
|
||||
-fx-padding: 24px 24px 16px 24px;
|
||||
-fx-padding: 24.0px 24.0px 16.0px 24.0px;
|
||||
-fx-background-color: -monet-surface-container-high;
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
}
|
||||
@@ -569,7 +570,6 @@
|
||||
}
|
||||
|
||||
.task-executor-dialog-layout {
|
||||
-fx-background-radius: 4px;
|
||||
-fx-background-color: -monet-surface-container-high;
|
||||
-fx-text-fill: -monet-on-surface;
|
||||
}
|
||||
@@ -1923,8 +1923,4 @@
|
||||
|
||||
.line-toggle-button .jfx-toggle-button {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.update-pane:active .subtitle-label {
|
||||
-fx-text-fill: -monet-tertiary;
|
||||
}
|
||||
}
|
||||
@@ -167,9 +167,6 @@ account.skin.upload.failed=Failed to upload skin.
|
||||
account.skin.invalid_skin=Invalid skin file.
|
||||
account.username=Username
|
||||
|
||||
addon.download.title.release=Minecraft %s
|
||||
addon.download.title.snapshot=Minecraft %s (Snapshots)
|
||||
|
||||
archive.author=Author(s)
|
||||
archive.date=Publish Date
|
||||
archive.file.name=File Name
|
||||
@@ -400,7 +397,7 @@ extension.ps1=Windows PowerShell Script
|
||||
extension.sh=Shell Script
|
||||
extension.command=macOS Shell Script
|
||||
|
||||
extension.datapack=Data Pack Archive
|
||||
extension.datapack=Datapack Archive
|
||||
extension.mod=Mod File
|
||||
extension.modloader.installer=Mod Loader Installer
|
||||
extension.resourcepack=Resource Pack Archive
|
||||
@@ -705,7 +702,7 @@ game.version=Game Instance
|
||||
|
||||
help=Help
|
||||
help.doc=Hello Minecraft! Launcher Documentation
|
||||
help.detail=For data pack and modpack makers.
|
||||
help.detail=For datapack and modpack makers.
|
||||
|
||||
input.email=The username must be an email address.
|
||||
input.number=The input must be numbers.
|
||||
@@ -811,8 +808,6 @@ launch.advice.forge37_0_60=Forge versions prior to 37.0.60 are not compatible wi
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later can only be run on Java 8 or later. Please use Java 8 or later versions.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 versions prior to 1.8.0_51. Please install the latest Java 8 version.
|
||||
launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later. Please use Java 8 instead.
|
||||
launch.advice.lwjgl_3_4_1=The current game version has a known issue that may cause stuttering in the game.\nHMCL can automatically optimize the game performance (using a Java Agent).\nDo you allow HMCL to automatically optimize the game?\nIf you encounter issues, you can disable the "Allow HMCL to modify the game" option in "Settings → General".
|
||||
launch.advice.lwjgl_3_4_1.title=Allow HMCL to automatically optimize the game?
|
||||
launch.advice.modded_java=Some mods may not be compatible with newer Java versions. It is recommended to use Java %1$s to launch Minecraft %2$s.
|
||||
launch.advice.modlauncher8=The Forge version you are using is not compatible with the current Java version. Please try updating Forge.
|
||||
launch.advice.newer_java=You are using an older Java version to launch the game. It is recommended to update to Java 8, otherwise some mods may cause the game to crash.
|
||||
@@ -995,7 +990,7 @@ modrinth.category.colored-lighting=Colored Lighting
|
||||
modrinth.category.combat=Combat
|
||||
modrinth.category.core-shaders=Core Shaders
|
||||
modrinth.category.cursed=Cursed
|
||||
modrinth.category.datapack=Data Pack
|
||||
modrinth.category.datapack=Datapack
|
||||
modrinth.category.decoration=Decoration
|
||||
modrinth.category.economy=Economy
|
||||
modrinth.category.entities=Entities
|
||||
@@ -1138,11 +1133,11 @@ nbt.open.failed=Failed to open file
|
||||
nbt.save.failed=Failed to save file
|
||||
nbt.title=View File - %s
|
||||
|
||||
datapack=Data Packs
|
||||
datapack=Datapacks
|
||||
datapack.add=Add
|
||||
datapack.add.title=Choose data pack archive you want to add
|
||||
datapack.reload.toast=Minecraft is running. Use the /reload command to reload data packs
|
||||
datapack.title=World [%s] - Data Packs
|
||||
datapack.add.title=Choose datapack archive you want to add
|
||||
datapack.reload.toast=Minecraft is running, please use the /reload command to reload the data pack
|
||||
datapack.title=World [%s] - Datapacks
|
||||
|
||||
web.failed=Failed to load page
|
||||
web.open_in_browser=Do you want to open this address in a browser:\n%s
|
||||
@@ -1172,7 +1167,7 @@ world.duplicate.failed.empty_name=Name cannot be empty
|
||||
world.duplicate.failed.invalid_name=Name contains invalid characters
|
||||
world.duplicate.failed=Failed to duplicate the world
|
||||
world.duplicate.success.toast=Successfully duplicated the world
|
||||
world.datapack=Data Packs
|
||||
world.datapack=Datapacks
|
||||
world.datetime=Last played on %s
|
||||
world.delete=Delete the World
|
||||
world.delete.failed=Failed to delete world.\n%s
|
||||
@@ -1386,9 +1381,8 @@ settings.advanced.renderer.zink.desc=Vulkan (Best performance, poor compatibilit
|
||||
settings.advanced.server_ip=Server Address
|
||||
settings.advanced.server_ip.prompt=Automatically join after launching the game
|
||||
settings.advanced.unsupported_system_options=Settings not applicable to the current system
|
||||
settings.advanced.use_native_glfw=Use System GLFW
|
||||
settings.advanced.use_native_openal=Use System OpenAL
|
||||
settings.advanced.linux_freebsd_only=Linux/FreeBSD Only
|
||||
settings.advanced.use_native_glfw=[Linux/FreeBSD Only] Use System GLFW
|
||||
settings.advanced.use_native_openal=[Linux/FreeBSD Only] Use System OpenAL
|
||||
settings.advanced.workaround=Workaround
|
||||
settings.advanced.workaround.warning=Workaround options are intended only for advanced users. Tweaking with these options may crash the game. Unless you know what you are doing, please do not edit these options.
|
||||
settings.advanced.wrapper_launcher=Wrapper Command
|
||||
@@ -1422,8 +1416,6 @@ settings.game.working_directory.hint=Enable the "Isolated" option in "Working Di
|
||||
settings.icon=Icon
|
||||
|
||||
settings.launcher=Launcher Settings
|
||||
settings.launcher.allow_auto_agent=Allow HMCL to modify the game
|
||||
settings.launcher.allow_auto_agent.subtitle=Allow HMCL to modify the game by attaching a Java Agent to improve the gaming experience.
|
||||
settings.launcher.appearance=Appearance
|
||||
settings.launcher.brightness=Theme Mode
|
||||
settings.launcher.brightness.auto=Follow System Settings
|
||||
@@ -1609,8 +1601,7 @@ update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
update.bubble.title=Update Available: %s
|
||||
update.bubble.subtitle=Click here to update
|
||||
update.note.stable=Suitable for users who prioritize software stability.\nNew features are added to the stable version only after thorough testing.
|
||||
update.note.dev=Suitable for users who want to experience new features first.\nThe development version includes the latest features and bug fixes, but may also have more issues due to insufficient testing.
|
||||
update.note=Beta and Nightly channels may have more features or fixes, but they also come with more potential problems.
|
||||
update.latest=This is the latest version
|
||||
update.no_browser=Cannot open in system browser. But we copied the link to your clipboard, and you can open it manually.
|
||||
update.tooltip=Update
|
||||
|
||||
@@ -1375,9 +1375,8 @@ settings.advanced.renderer.zink.desc=Vulkan (أفضل أداء، توافق ضع
|
||||
settings.advanced.server_ip=عنوان الخادم
|
||||
settings.advanced.server_ip.prompt=الانضمام تلقائياً بعد تشغيل اللعبة
|
||||
settings.advanced.unsupported_system_options=إعدادات غير مطبّقة على النظام الحالي
|
||||
settings.advanced.use_native_glfw=استخدام GLFW النظام
|
||||
settings.advanced.use_native_openal=استخدام OpenAL النظام
|
||||
settings.advanced.linux_freebsd_only=Linux/FreeBSD فقط
|
||||
settings.advanced.use_native_glfw=[Linux/FreeBSD فقط] استخدام GLFW النظام
|
||||
settings.advanced.use_native_openal=[Linux/FreeBSD فقط] استخدام OpenAL النظام
|
||||
settings.advanced.workaround=حلول بديلة
|
||||
settings.advanced.workaround.warning=خيارات الحلول البديلة مخصصة للمستخدمين المتقدمين فقط. قد يتسبب تعديل هذه الخيارات في تعطل اللعبة. يُرجى عدم تعديل هذه الخيارات إلا إذا كنت تعرف ما تفعله.
|
||||
settings.advanced.wrapper_launcher=أمر التغليف
|
||||
@@ -1593,6 +1592,7 @@ update.found=تحديث متاح!
|
||||
update.newest_version=أحدث إصدار: %s
|
||||
update.bubble.title=تحديث متاح: %s
|
||||
update.bubble.subtitle=انقر هنا للتحديث
|
||||
update.note=قد تحتوي القنوات التجريبية والليلية على مزيد من الميزات والإصلاحات، لكنها تأتي أيضاً بمزيد من المشاكل المحتملة.
|
||||
update.latest=هذا هو أحدث إصدار
|
||||
update.no_browser=لا يمكن الفتح في متصفح النظام. لكننا نسخنا الرابط إلى حافظتك، ويمكنك فتحه يدوياً.
|
||||
update.tooltip=تحديث
|
||||
|
||||
@@ -1279,9 +1279,8 @@ settings.advanced.renderer.zink.desc=Vulkan (Máximo rendimiento, baja compatibi
|
||||
settings.advanced.server_ip=Dirección del servidor
|
||||
settings.advanced.server_ip.prompt=Entrar automáticamente después de ejecutar el juego
|
||||
settings.advanced.unsupported_system_options=Configuración no aplicable al sistema actual
|
||||
settings.advanced.use_native_glfw=Utilizar GLFW nativo
|
||||
settings.advanced.use_native_openal=Utilizar OpenAL nativo
|
||||
settings.advanced.linux_freebsd_only=Sólo Linux/FreeBSD
|
||||
settings.advanced.use_native_glfw=[Sólo Linux/FreeBSD] Utilizar GLFW nativo
|
||||
settings.advanced.use_native_openal=[Sólo Linux/FreeBSD] Utilizar OpenAL nativo
|
||||
settings.advanced.workaround=Métodos alternativos
|
||||
settings.advanced.workaround.warning=Éstas opciones están pensadas sólo para usuarios expertos. Jugar con estas opciones puede romper el juego. A menos que sepas lo que estás haciendo, por favor no modifiques estas opciones.
|
||||
settings.advanced.wrapper_launcher=Comando Wrapper
|
||||
@@ -1402,6 +1401,7 @@ update.found=¡Actualización disponible!
|
||||
update.newest_version=Última versión: %s
|
||||
update.bubble.title=Actualización disponible: %s
|
||||
update.bubble.subtitle=Haga clic aquí para actualizar
|
||||
update.note=Advertencia: Las versiones beta y las versiones nocturnas pueden tener más funciones o correcciones, pero también tienen más problemas potenciales.
|
||||
update.latest=Esta es la última versión.
|
||||
update.no_browser=No se puede abrir en el navegador del sistema. Pero, hemos copiado el enlace a su portapapeles y puede abrirlo manualmente.
|
||||
update.tooltip=Actualización
|
||||
|
||||
@@ -802,9 +802,8 @@ settings.advanced.renderer.zink.desc=Vulkan (最高パフォーマンス、低
|
||||
settings.advanced.server_ip=サーバーアドレス
|
||||
settings.advanced.server_ip.prompt=ゲームの起動時にサーバーに参加する
|
||||
settings.advanced.unsupported_system_options=サポートされていないシステムオプション
|
||||
settings.advanced.use_native_glfw=システムGLFWを使用する
|
||||
settings.advanced.use_native_openal=システムOpenALを使用する
|
||||
settings.advanced.linux_freebsd_only=Linux/FreeBSDのみ
|
||||
settings.advanced.use_native_glfw=[Linux/FreeBSDのみ]システムGLFWを使用する
|
||||
settings.advanced.use_native_openal=[Linux/FreeBSDのみ]システムOpenALを使用する
|
||||
settings.advanced.workaround=デバッグ用オプション
|
||||
settings.advanced.workaround.warning=デバッグオプションはプロフェッショナルのみ使用可能です。デバッグオプションにより、ゲームが起動しない場合があります。これらのオプションは、ご自分が何をしているのかが分からない限り、変更しないでください。
|
||||
settings.advanced.wrapper_launcher=パッキングオーダー
|
||||
@@ -912,6 +911,7 @@ update.found=アップデートが利用可能です!
|
||||
update.newest_version=最新バージョン:%s
|
||||
update.bubble.title=利用可能な更新:%s
|
||||
update.bubble.subtitle=更新するにはここをクリックしてください
|
||||
update.note=警告:ベータ版には、より多くの機能とバグ修正が含まれている可能性がありますが、エラーが発生する可能性もあります。
|
||||
update.latest=これは最新バージョンです。
|
||||
update.no_browser=ブラウザを開くことができません。リンクがクリップボードにコピーされました。ブラウザのアドレスバーに貼り付けて更新します。
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -579,8 +579,6 @@ launch.advice.forge37_0_60=鍛三七點〇點五九與舊者弗兼爪哇十七
|
||||
launch.advice.java8_1_13=礦藝一點一三以晉但行於爪哇八與晉者。宜行以爪哇八與晉者。
|
||||
launch.advice.java8_51_1_13=爪哇之前一點八點〇之五一者或崩礦藝一點一三。宜新爪哇於一點八點〇之五一以晉,然後復啟。
|
||||
launch.advice.java9=礦藝之前一點一三而有改囊者弗見兼於爪哇九與晉者。宜啟以爪哇八。
|
||||
launch.advice.lwjgl_3_4_1=戲之是版有已知之謬,行戲之時或致頓滯。\nHMCL 可假爪哇 Agent 自優其戲,以弭是患。君其許 HMCL 自優其戲乎?\n誠有謬,可至於「置設 → 貫用」,廢「許改戲」之項。
|
||||
launch.advice.lwjgl_3_4_1.title=君其許 HMCL 自優其戲乎?
|
||||
launch.advice.modded_java=改囊或弗見兼於爪哇之新者。宜以爪哇 %s 啟礦藝 %s。
|
||||
launch.advice.modlauncher8=鍛版之所行弗兼於爪哇之所行。宜新鍛。
|
||||
launch.advice.newer_java=余識君之啟戲以舊爪哇也,蓋改囊或是以崩戲也。宜新爪哇於八,然後復啟。
|
||||
@@ -1095,9 +1093,8 @@ settings.advanced.renderer.zink.desc=Vulkan (效至,所適差)
|
||||
settings.advanced.server_ip=伺服器之址
|
||||
settings.advanced.server_ip.prompt=本之址,啟訖徑入是伺服器
|
||||
settings.advanced.unsupported_system_options=不逮今之械網所選
|
||||
settings.advanced.use_native_glfw=用 GLFW
|
||||
settings.advanced.use_native_openal=用開聲庫
|
||||
settings.advanced.linux_freebsd_only=惟磷若/FreeBSD
|
||||
settings.advanced.use_native_glfw=[惟磷若/FreeBSD] 用 GLFW
|
||||
settings.advanced.use_native_openal=[惟磷若/FreeBSD] 用開聲庫
|
||||
settings.advanced.workaround=勘誤置設
|
||||
settings.advanced.workaround.warning=勘誤置設者,蓋專者之所行也。苟妄改之,戲或弗啟。微其之知,無復改焉!
|
||||
settings.advanced.wrapper_launcher=包裝之令
|
||||
@@ -1129,8 +1126,6 @@ settings.game.working_directory.hint=夫于「運徑」擇「例獨」者,可
|
||||
settings.icon=戲徵
|
||||
|
||||
settings.launcher=啟者置設
|
||||
settings.launcher.allow_auto_agent=許改戲
|
||||
settings.launcher.allow_auto_agent.subtitle=許 HMCL 假附加爪哇 Agent 以改戲,而優其戲感。
|
||||
settings.launcher.appearance=外觀
|
||||
settings.launcher.common_path.tooltip=啟者置戲資與府庫於是。倘戲有成案,則弗取公庫之案。
|
||||
settings.launcher.debug=勘誤
|
||||
@@ -1227,6 +1222,7 @@ update.found=尋得新者
|
||||
update.newest_version=至新:%s
|
||||
update.bubble.title=尋得更新:%s
|
||||
update.bubble.subtitle=是擊以晉
|
||||
update.note=开發版與預版,虽增功、修阙,然亦恐生他患。
|
||||
update.latest=當版至新
|
||||
update.no_browser=無啟瀏覽器。址固鈔訖,君可自粘之以啟頁。
|
||||
update.tooltip=迭更
|
||||
|
||||
@@ -1270,9 +1270,8 @@ settings.advanced.renderer.zink.desc=Vulkan (Лучшая производите
|
||||
settings.advanced.server_ip=Адрес сервера
|
||||
settings.advanced.server_ip.prompt=Присоединяться к серверу при запуске игры
|
||||
settings.advanced.unsupported_system_options=Настройки, не применимые к текущей системе
|
||||
settings.advanced.use_native_glfw=Использовать системный GLFW
|
||||
settings.advanced.use_native_openal=Использовать системный OpenAL
|
||||
settings.advanced.linux_freebsd_only=Только для Linux/FreeBSD
|
||||
settings.advanced.use_native_glfw=[Только для Linux/FreeBSD] Использовать системный GLFW
|
||||
settings.advanced.use_native_openal=[Только для Linux/FreeBSD] Использовать системный OpenAL
|
||||
settings.advanced.workaround=Обходные пути
|
||||
settings.advanced.workaround.warning=Варианты обхода предназначены только для опытных пользователей. Изменение этих параметров может привести к вылету игры. Если не знаете, что делаете, то не изменяйте эти параметры.
|
||||
settings.advanced.wrapper_launcher=Команда-оболочка
|
||||
@@ -1392,6 +1391,7 @@ update.found=Доступно обновление!
|
||||
update.newest_version=Последняя версия: %s
|
||||
update.bubble.title=Доступно обновление: %s
|
||||
update.bubble.subtitle=Кликните здесь для обновления
|
||||
update.note=Предупреждение: Бета-версии могут иметь больше функциональных возможностей и исправлений ошибок, но также и больше возможных ошибок.
|
||||
update.latest=Это последняя версия.
|
||||
update.no_browser=Не удалось открыть браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.
|
||||
update.tooltip=Обновить
|
||||
|
||||
@@ -1217,9 +1217,8 @@ settings.advanced.renderer.zink.desc=Vulkan (Найкраща продуктив
|
||||
settings.advanced.server_ip=Адреса сервера
|
||||
settings.advanced.server_ip.prompt=Автоматично приєднатися після запуску гри
|
||||
settings.advanced.unsupported_system_options=Налаштування, що не застосовуються до поточної системи
|
||||
settings.advanced.use_native_glfw=Використовувати системний GLFW
|
||||
settings.advanced.use_native_openal=Використовувати системний OpenAL
|
||||
settings.advanced.linux_freebsd_only=Лише Linux/FreeBSD
|
||||
settings.advanced.use_native_glfw=[Лише Linux/FreeBSD] Використовувати системний GLFW
|
||||
settings.advanced.use_native_openal=[Лише Linux/FreeBSD] Використовувати системний OpenAL
|
||||
settings.advanced.workaround=Обхідні шляхи
|
||||
settings.advanced.workaround.warning=Опції обхідних шляхів призначені лише для досвідчених користувачів. Налаштування цих опцій може призвести до збою гри. Якщо ви не знаєте, що робите, не редагуйте ці опції.
|
||||
settings.advanced.wrapper_launcher=Команда обгортки
|
||||
@@ -1343,6 +1342,7 @@ update.found=Доступне оновлення!
|
||||
update.newest_version=Остання версія: %s
|
||||
update.bubble.title=Доступне оновлення: %s
|
||||
update.bubble.subtitle=Натисніть тут, щоб оновити
|
||||
update.note=Канали Бета та Нічний можуть мати більше функцій або виправлень, але вони також мають більше потенційних проблем.
|
||||
update.latest=Це остання версія
|
||||
update.no_browser=Не вдається відкрити в системному браузері. Але ми скопіювали посилання до вашого буфера обміну, і ви можете відкрити його вручну.
|
||||
update.tooltip=Оновити
|
||||
|
||||
@@ -164,9 +164,6 @@ account.skin.upload.failed=外觀上傳失敗
|
||||
account.skin.invalid_skin=無法識別的外觀檔案
|
||||
account.username=使用者名稱
|
||||
|
||||
addon.download.title.release=Minecraft %s
|
||||
addon.download.title.snapshot=Minecraft %s (快照)
|
||||
|
||||
archive.author=作者
|
||||
archive.date=發布日期
|
||||
archive.file.name=檔案名稱
|
||||
@@ -615,8 +612,6 @@ launch.advice.forge37_0_60=Forge 37.0.59 及更低版本與 Java 17 不相容。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 及更高版本僅支援 Java 8 或更高版本。請使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議你到 https://java.com 安裝最新版的 Java 8。
|
||||
launch.advice.java9=低於 (包含) 1.13 的有安裝模組的 Minecraft 版本不支援 Java 9 或更高版本。請使用 Java 8。
|
||||
launch.advice.lwjgl_3_4_1=當前遊戲版本存在已知問題,可能在遊戲中發生卡頓。\nHMCL 可以 (透過 Java Agent) 自動最佳化遊戲解決此問題。是否允許 HMCL 自動最佳化遊戲?\n如遇到問題,你可以在「設定 → 一般」中關閉「允許 HMCL 修改遊戲」選項。
|
||||
launch.advice.lwjgl_3_4_1.title=是否允許 HMCL 自動最佳化遊戲?
|
||||
launch.advice.modded_java=部分模組可能與高版本 Java 不相容。建議使用 Java %s 啟動 Minecraft %s。
|
||||
launch.advice.modlauncher8=你所使用的 Forge 版本與目前使用的 Java 不相容。請更新 Forge。
|
||||
launch.advice.newer_java=偵測到你正在使用舊版本 Java 啟動遊戲,這可能導致部分模組引發遊戲崩潰。建議更新至 Java 8 後再次啟動。
|
||||
@@ -1173,9 +1168,8 @@ settings.advanced.renderer.zink.desc=Vulkan (效能最好,相容性較差)
|
||||
settings.advanced.server_ip=伺服器位址
|
||||
settings.advanced.server_ip.prompt=預設,啟動遊戲後直接進入對應伺服器
|
||||
settings.advanced.unsupported_system_options=不適用於目前系統的選項
|
||||
settings.advanced.use_native_glfw=使用系統 GLFW
|
||||
settings.advanced.use_native_openal=使用系統 OpenAL
|
||||
settings.advanced.linux_freebsd_only=僅限 Linux/FreeBSD
|
||||
settings.advanced.use_native_glfw=[僅限 Linux/FreeBSD] 使用系統 GLFW
|
||||
settings.advanced.use_native_openal=[僅限 Linux/FreeBSD] 使用系統 OpenAL
|
||||
settings.advanced.workaround=除錯選項
|
||||
settings.advanced.workaround.warning=除錯選項僅提供給專業玩家使用。修改除錯選項可能會導致遊戲無法啟動。除非你知道你在做什麼,否則請不要修改這些選項。
|
||||
settings.advanced.wrapper_launcher=前置指令
|
||||
@@ -1207,8 +1201,6 @@ settings.game.working_directory.hint=在「執行路徑」選項中選取「各
|
||||
settings.icon=遊戲圖示
|
||||
|
||||
settings.launcher=啟動器設定
|
||||
settings.launcher.allow_auto_agent=允許 HMCL 修改遊戲
|
||||
settings.launcher.allow_auto_agent.subtitle=允許 HMCL 透過附加 Java Agent 修改遊戲以改善遊戲體驗。
|
||||
settings.launcher.appearance=外觀
|
||||
settings.launcher.brightness=主題模式
|
||||
settings.launcher.brightness.auto=跟隨系統設定
|
||||
@@ -1390,8 +1382,7 @@ update.found=發現到更新
|
||||
update.newest_version=最新版本為:%s
|
||||
update.bubble.title=發現更新:%s
|
||||
update.bubble.subtitle=點選此處進行升級
|
||||
update.note.stable=適合優先追求軟體穩定性的使用者使用。\n新功能在經過充分測試後才會被添加到穩定版中。
|
||||
update.note.dev=適合希望優先體驗新功能的使用者使用。\n開發版會包含最新功能和錯誤修復,但未經充分測試,可能會存在更多問題。
|
||||
update.note=開發版與預覽版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
|
||||
update.latest=目前版本為最新版本
|
||||
update.no_browser=無法開啟瀏覽器。網址已經複製到剪貼簿了,你可以手動複製網址開啟頁面。
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -166,9 +166,6 @@ account.skin.upload.failed=皮肤上传失败
|
||||
account.skin.invalid_skin=无法识别的皮肤文件
|
||||
account.username=用户名
|
||||
|
||||
addon.download.title.release=Minecraft %s
|
||||
addon.download.title.snapshot=Minecraft %s (快照)
|
||||
|
||||
archive.author=作者
|
||||
archive.date=发布日期
|
||||
archive.file.name=文件名
|
||||
@@ -620,8 +617,6 @@ launch.advice.forge37_0_60=Forge 37.0.59 及更低版本与 Java 17 不兼容。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 及更高版本只能在 Java 8 或更高版本上运行。请使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低于 1.8.0_51 的 Java 版本可能会导致 Minecraft 1.13 崩溃。建议更新 Java 至 1.8.0_51 或更高版本后再次启动。
|
||||
launch.advice.java9=低于 1.13 的有安装模组的 Minecraft 版本与 Java 9 或更高版本不兼容。请使用 Java 8。
|
||||
launch.advice.lwjgl_3_4_1=当前游戏版本存在已知问题,可能在游戏中发生卡顿。\nHMCL 可以 (通过 Java Agent) 自动优化游戏解决此问题。是否允许 HMCL 自动优化游戏?\n如遇到问题,你可以在“设置 → 通用”中关闭“允许 HMCL 修改游戏”选项。
|
||||
launch.advice.lwjgl_3_4_1.title=是否允许 HMCL 自动优化游戏?
|
||||
launch.advice.modded_java=部分模组可能与高版本 Java 不兼容。建议使用 Java %s 启动 Minecraft %s。
|
||||
launch.advice.modlauncher8=你所使用的 Forge 版本与当前使用的 Java 不兼容。请更新 Forge。
|
||||
launch.advice.newer_java=检测到你正在使用旧版本 Java 启动游戏,这可能导致部分模组引发游戏崩溃。建议更新至 Java 8 后再次启动。
|
||||
@@ -1178,9 +1173,8 @@ settings.advanced.renderer.zink.desc=Vulkan (性能最好,兼容性较差)
|
||||
settings.advanced.server_ip=服务器地址
|
||||
settings.advanced.server_ip.prompt=默认,启动游戏后可以直接进入对应服务器
|
||||
settings.advanced.unsupported_system_options=不适用于当前系统的选项
|
||||
settings.advanced.use_native_glfw=使用系统 GLFW
|
||||
settings.advanced.use_native_openal=使用系统 OpenAL
|
||||
settings.advanced.linux_freebsd_only=仅 Linux/FreeBSD
|
||||
settings.advanced.use_native_glfw=[仅 Linux/FreeBSD] 使用系统 GLFW
|
||||
settings.advanced.use_native_openal=[仅 Linux/FreeBSD] 使用系统 OpenAL
|
||||
settings.advanced.workaround=调试选项
|
||||
settings.advanced.workaround.warning=调试选项仅提供给专业玩家使用。调试选项可能会导致游戏无法启动。除非你知道你在做什么,否则请不要修改这些选项!
|
||||
settings.advanced.wrapper_launcher=包装命令
|
||||
@@ -1212,8 +1206,6 @@ settings.game.working_directory.hint=在“版本隔离”中选择“各实例
|
||||
settings.icon=游戏图标
|
||||
|
||||
settings.launcher=启动器设置
|
||||
settings.launcher.allow_auto_agent=允许 HMCL 修改游戏
|
||||
settings.launcher.allow_auto_agent.subtitle=允许 HMCL 通过附加 Java Agent 修改游戏以改善游戏体验。
|
||||
settings.launcher.appearance=外观
|
||||
settings.launcher.brightness=主题模式
|
||||
settings.launcher.brightness.auto=跟随系统设置
|
||||
@@ -1395,8 +1387,7 @@ update.found=发现更新
|
||||
update.newest_version=最新版本为:%s
|
||||
update.bubble.title=发现更新:%s
|
||||
update.bubble.subtitle=点击此处进行升级
|
||||
update.note.stable=适合优先追求软件稳定性的用户使用。\n新功能在经过充分测试后才会被添加到稳定版中。
|
||||
update.note.dev=适合希望优先体验新功能的用户使用。\n包含最新功能和错误修复,但未经充分测试,可能会存在更多问题。
|
||||
update.note=开发版与预览版包含更多的功能以及错误修复,但也可能会包含其他的问题。
|
||||
update.latest=当前版本为最新版本
|
||||
update.no_browser=无法打开浏览器。网址已经复制到剪贴板,你可以手动粘贴网址打开页面。
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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 javafx.collections.FXCollections;
|
||||
|
||||
@@ -18,15 +18,14 @@ dependencies {
|
||||
api(libs.gson)
|
||||
api(libs.toml)
|
||||
api(libs.xz)
|
||||
api(libs.lz4)
|
||||
api(libs.fx.gson)
|
||||
api(libs.constant.pool.scanner)
|
||||
api(libs.opennbt)
|
||||
api(libs.nanohttpd)
|
||||
api(libs.jsoup)
|
||||
api(libs.chardet)
|
||||
api(libs.jna)
|
||||
api(libs.pci.ids)
|
||||
api(libs.hello.nbt)
|
||||
|
||||
compileOnlyApi(libs.jetbrains.annotations)
|
||||
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.download.neoforge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
|
||||
-17
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.download.neoforge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
|
||||
-17
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.download.neoforge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* 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.game;
|
||||
|
||||
import com.sun.tools.attach.AttachNotSupportedException;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.game;
|
||||
|
||||
public enum NativesDirectoryType {
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.game;
|
||||
|
||||
public enum Renderer {
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.github.steveice10.opennbt.NBTIO;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import javafx.scene.image.Image;
|
||||
import org.glavo.nbt.io.NBTCodec;
|
||||
import org.glavo.nbt.tag.*;
|
||||
import org.jackhuang.hmcl.util.io.*;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -34,7 +34,9 @@ import java.nio.channels.OverlappingFileLockException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -46,7 +48,6 @@ public final class World {
|
||||
private Image icon;
|
||||
|
||||
private CompoundTag levelData;
|
||||
private CompoundTag dataTag;
|
||||
private Path levelDataPath;
|
||||
|
||||
private CompoundTag worldGenSettingsDataBackingTag; // Use for writing back to the file
|
||||
@@ -59,64 +60,10 @@ public final class World {
|
||||
public World(Path file) throws IOException {
|
||||
this.file = file;
|
||||
|
||||
if (Files.isDirectory(file)) {
|
||||
fileName = FileUtils.getName(this.file);
|
||||
Path levelDatPath = this.file.resolve("level.dat");
|
||||
if (!Files.exists(levelDatPath)) { // version 20w14infinite
|
||||
levelDatPath = this.file.resolve("special_level.dat");
|
||||
}
|
||||
if (!Files.exists(levelDatPath)) {
|
||||
throw new IOException("Not a valid world directory since level.dat or special_level.dat cannot be found.");
|
||||
}
|
||||
this.levelDataPath = levelDatPath;
|
||||
loadAndCheckWorldData();
|
||||
|
||||
Path iconFile = this.file.resolve("icon.png");
|
||||
if (Files.isRegularFile(iconFile)) {
|
||||
try (InputStream inputStream = Files.newInputStream(iconFile)) {
|
||||
icon = new Image(inputStream, 64, 64, true, false);
|
||||
if (icon.isError())
|
||||
throw icon.getException();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load world icon", e);
|
||||
}
|
||||
}
|
||||
} else if (Files.isRegularFile(file))
|
||||
try (FileSystem fs = CompressingUtils.readonly(this.file).setAutoDetectEncoding(true).build()) {
|
||||
Path root;
|
||||
if (Files.isRegularFile(fs.getPath("/level.dat"))) {
|
||||
root = fs.getPath("/");
|
||||
fileName = FileUtils.getName(this.file);
|
||||
} else {
|
||||
List<Path> files = Files.list(fs.getPath("/")).toList();
|
||||
if (files.size() != 1 || !Files.isDirectory(files.get(0))) {
|
||||
throw new IOException("Not a valid world zip file");
|
||||
}
|
||||
|
||||
root = files.get(0);
|
||||
fileName = FileUtils.getName(root);
|
||||
}
|
||||
|
||||
Path levelDat = root.resolve("level.dat");
|
||||
if (!Files.exists(levelDat)) { //version 20w14infinite
|
||||
levelDat = root.resolve("special_level.dat");
|
||||
}
|
||||
if (!Files.exists(levelDat)) {
|
||||
throw new IOException("Not a valid world zip file since level.dat or special_level.dat cannot be found.");
|
||||
}
|
||||
loadAndCheckLevelData(levelDat);
|
||||
|
||||
Path iconFile = root.resolve("icon.png");
|
||||
if (Files.isRegularFile(iconFile)) {
|
||||
try (InputStream inputStream = Files.newInputStream(iconFile)) {
|
||||
icon = new Image(inputStream, 64, 64, true, false);
|
||||
if (icon.isError())
|
||||
throw icon.getException();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load world icon", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Files.isDirectory(file))
|
||||
loadFromDirectory();
|
||||
else if (Files.isRegularFile(file))
|
||||
loadFromZip();
|
||||
else
|
||||
throw new IOException("Path " + file + " cannot be recognized as a Minecraft world");
|
||||
}
|
||||
@@ -130,11 +77,9 @@ public final class World {
|
||||
}
|
||||
|
||||
public String getWorldName() {
|
||||
if (levelData.get("Data") instanceof CompoundTag data
|
||||
&& data.get("LevelName") instanceof StringTag levelNameTag)
|
||||
return levelNameTag.get();
|
||||
else
|
||||
return "";
|
||||
CompoundTag data = levelData.get("Data");
|
||||
StringTag levelNameTag = data.get("LevelName");
|
||||
return levelNameTag.getValue();
|
||||
}
|
||||
|
||||
public void setWorldName(String worldName) throws IOException {
|
||||
@@ -161,11 +106,9 @@ public final class World {
|
||||
}
|
||||
|
||||
public long getLastPlayed() {
|
||||
if (dataTag.get("LastPlayed") instanceof LongTag lastPlayedTag) {
|
||||
return lastPlayedTag.get();
|
||||
} else {
|
||||
return 0L;
|
||||
}
|
||||
CompoundTag data = levelData.get("Data");
|
||||
LongTag lastPlayedTag = data.get("LastPlayed");
|
||||
return lastPlayedTag.getValue();
|
||||
}
|
||||
|
||||
public @Nullable GameVersionNumber getGameVersion() {
|
||||
@@ -179,20 +122,21 @@ public final class World {
|
||||
|
||||
public @Nullable Long getSeed() {
|
||||
// Valid after 1.16(20w20a)
|
||||
if (normalizedWorldGenSettingsData != null
|
||||
&& normalizedWorldGenSettingsData.get("seed") instanceof LongTag seedTag) {
|
||||
if (normalizedWorldGenSettingsData != null && normalizedWorldGenSettingsData.get("seed") instanceof LongTag seedTag) {
|
||||
return seedTag.getValue();
|
||||
}
|
||||
// Valid before 1.16(20w20a)
|
||||
if (dataTag.get("RandomSeed") instanceof LongTag seedTag) {
|
||||
if (levelData.get("Data") instanceof CompoundTag data && data.get("RandomSeed") instanceof LongTag seedTag) {
|
||||
return seedTag.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isLargeBiomes() {
|
||||
CompoundTag data = levelData.get("Data");
|
||||
|
||||
// Valid before 1.16(20w20a)
|
||||
if (dataTag.get("generatorName") instanceof StringTag generatorNameTag) {
|
||||
if (data.get("generatorName") instanceof StringTag generatorNameTag) {
|
||||
return "largeBiomes".equals(generatorNameTag.getValue());
|
||||
}
|
||||
// Unified handling of logic after version 1.16
|
||||
@@ -203,11 +147,11 @@ public final class World {
|
||||
// Valid between 1.16(20w20a) and 1.18(21w37a)
|
||||
if (generatorTag.get("biome_source") instanceof CompoundTag biomeSourceTag
|
||||
&& biomeSourceTag.get("large_biomes") instanceof ByteTag largeBiomesTag) {
|
||||
return largeBiomesTag.get() == (byte) 1;
|
||||
return largeBiomesTag.getValue() == (byte) 1;
|
||||
}
|
||||
// Valid after 1.18(21w37a)
|
||||
else if (generatorTag.get("settings") instanceof StringTag settingsTag) {
|
||||
return "minecraft:large_biomes".equals(settingsTag.get());
|
||||
return "minecraft:large_biomes".equals(settingsTag.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +166,7 @@ public final class World {
|
||||
return isLocked(getSessionLockFile());
|
||||
}
|
||||
|
||||
public boolean supportDataPacks() {
|
||||
public boolean supportDatapacks() {
|
||||
return getGameVersion() != null && getGameVersion().isAtLeast("1.13", "17w43a");
|
||||
}
|
||||
|
||||
@@ -234,14 +178,79 @@ public final class World {
|
||||
return gameVersionNumber != null && gameVersionNumber.isAtLeast("1.20", "23w14a");
|
||||
}
|
||||
|
||||
private void loadFromDirectory() throws IOException {
|
||||
fileName = FileUtils.getName(file);
|
||||
Path levelDat = file.resolve("level.dat");
|
||||
if (!Files.exists(levelDat)) { // version 20w14infinite
|
||||
levelDat = file.resolve("special_level.dat");
|
||||
}
|
||||
if (!Files.exists(levelDat)) {
|
||||
throw new IOException("Not a valid world directory since level.dat or special_level.dat cannot be found.");
|
||||
}
|
||||
this.levelDataPath = levelDat;
|
||||
loadAndCheckWorldData();
|
||||
|
||||
Path iconFile = file.resolve("icon.png");
|
||||
if (Files.isRegularFile(iconFile)) {
|
||||
try (InputStream inputStream = Files.newInputStream(iconFile)) {
|
||||
icon = new Image(inputStream, 64, 64, true, false);
|
||||
if (icon.isError())
|
||||
throw icon.getException();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load world icon", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFromZipImpl(Path root) throws IOException {
|
||||
Path levelDat = root.resolve("level.dat");
|
||||
if (!Files.exists(levelDat)) { //version 20w14infinite
|
||||
levelDat = root.resolve("special_level.dat");
|
||||
}
|
||||
if (!Files.exists(levelDat)) {
|
||||
throw new IOException("Not a valid world zip file since level.dat or special_level.dat cannot be found.");
|
||||
}
|
||||
loadAndCheckLevelData(levelDat);
|
||||
|
||||
Path iconFile = root.resolve("icon.png");
|
||||
if (Files.isRegularFile(iconFile)) {
|
||||
try (InputStream inputStream = Files.newInputStream(iconFile)) {
|
||||
icon = new Image(inputStream, 64, 64, true, false);
|
||||
if (icon.isError())
|
||||
throw icon.getException();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load world icon", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadFromZip() throws IOException {
|
||||
try (FileSystem fs = CompressingUtils.readonly(file).setAutoDetectEncoding(true).build()) {
|
||||
Path levelDatPath = fs.getPath("/level.dat");
|
||||
if (Files.isRegularFile(levelDatPath)) {
|
||||
fileName = FileUtils.getName(file);
|
||||
loadFromZipImpl(fs.getPath("/"));
|
||||
return;
|
||||
}
|
||||
|
||||
try (Stream<Path> stream = Files.list(fs.getPath("/"))) {
|
||||
Path root = stream.filter(Files::isDirectory).findAny()
|
||||
.orElseThrow(() -> new IOException("Not a valid world zip file"));
|
||||
fileName = FileUtils.getName(root);
|
||||
loadFromZipImpl(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAndCheckWorldData() throws IOException {
|
||||
loadAndCheckLevelData(levelDataPath);
|
||||
loadOtherData();
|
||||
}
|
||||
|
||||
private void loadAndCheckLevelData(Path levelDat) throws IOException {
|
||||
this.levelData = NBTCodec.of().readTag(levelDat, TagType.COMPOUND);
|
||||
if (!(levelData.get("Data") instanceof CompoundTag data))
|
||||
this.levelData = readTag(levelDat);
|
||||
CompoundTag data = levelData.get("Data");
|
||||
if (data == null)
|
||||
throw new IOException("level.dat missing Data");
|
||||
|
||||
if (!(data.get("LevelName") instanceof StringTag))
|
||||
@@ -249,7 +258,6 @@ public final class World {
|
||||
|
||||
if (!(data.get("LastPlayed") instanceof LongTag))
|
||||
throw new IOException("level.dat missing LastPlayed");
|
||||
this.dataTag = data;
|
||||
}
|
||||
|
||||
private void loadOtherData() throws IOException {
|
||||
@@ -259,7 +267,7 @@ public final class World {
|
||||
if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag) {
|
||||
setWorldGenSettingsData(null, worldGenSettingsTag, worldGenSettingsTag);
|
||||
} else if (Files.isRegularFile(worldGenSettingsDatPath)) {
|
||||
CompoundTag raw = NBTCodec.of().readTag(worldGenSettingsDatPath, TagType.COMPOUND);
|
||||
CompoundTag raw = readTag(worldGenSettingsDatPath);
|
||||
if (raw.get("data") instanceof CompoundTag compoundTag) {
|
||||
setWorldGenSettingsData(worldGenSettingsDatPath, raw, compoundTag);
|
||||
} else {
|
||||
@@ -271,11 +279,14 @@ public final class World {
|
||||
|
||||
if (data.get("Player") instanceof CompoundTag playerTag) {
|
||||
setPlayerData(null, playerTag);
|
||||
} else if (data.get("singleplayer_uuid") instanceof IntArrayTag uuidTag && uuidTag.isUUID()) {
|
||||
String playerUUID = uuidTag.getUUID().toString();
|
||||
} else if (data.get("singleplayer_uuid") instanceof IntArrayTag uuidTag && uuidTag.getValue().length == 4) {
|
||||
int[] uuidValue = uuidTag.getValue();
|
||||
long mostSigBits = ((long) uuidValue[0] << 32) | (uuidValue[1] & 0xFFFFFFFFL);
|
||||
long leastSigBits = ((long) uuidValue[2] << 32) | (uuidValue[3] & 0xFFFFFFFFL);
|
||||
String playerUUID = new UUID(mostSigBits, leastSigBits).toString();
|
||||
Path playerDatPath = file.resolve("players/data/" + playerUUID + ".dat");
|
||||
if (Files.exists(playerDatPath)) {
|
||||
setPlayerData(playerDatPath, NBTCodec.of().readTag(playerDatPath, TagType.COMPOUND));
|
||||
setPlayerData(playerDatPath, readTag(playerDatPath));
|
||||
} else {
|
||||
setPlayerData(null, null);
|
||||
}
|
||||
@@ -306,7 +317,8 @@ public final class World {
|
||||
throw new IOException("Not a valid world directory");
|
||||
|
||||
// Change the name recorded in level.dat
|
||||
dataTag.setString("LevelName", newName);
|
||||
CompoundTag data = levelData.get("Data");
|
||||
data.put(new StringTag("LevelName", newName));
|
||||
writeLevelData();
|
||||
|
||||
// then change the folder's name
|
||||
@@ -421,11 +433,19 @@ public final class World {
|
||||
writeTag(levelData, levelDataPath);
|
||||
}
|
||||
|
||||
private CompoundTag readTag(Path path) throws IOException {
|
||||
try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) {
|
||||
Tag tag = NBTIO.readTag(is);
|
||||
if (tag instanceof CompoundTag compoundTag) return compoundTag;
|
||||
throw new IOException("NBT file malformed: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTag(CompoundTag nbt, Path path) throws IOException {
|
||||
if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory");
|
||||
FileUtils.saveSafely(path, os -> {
|
||||
try (OutputStream gos = new GZIPOutputStream(os)) {
|
||||
NBTCodec.of().writeTag(gos, nbt);
|
||||
NBTIO.writeTag(gos, nbt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.launch;
|
||||
|
||||
public class CommandTooLongException extends RuntimeException {
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.launch;
|
||||
|
||||
public final class ExecutionPolicyLimitException extends RuntimeException {
|
||||
|
||||
+24
-24
@@ -39,14 +39,14 @@ import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class DataPack {
|
||||
public class Datapack {
|
||||
private static final String DISABLED_EXT = "disabled";
|
||||
private static final String ZIP_EXT = "zip";
|
||||
|
||||
private final Path path;
|
||||
private final ObservableList<Pack> packs = FXCollections.observableArrayList();
|
||||
|
||||
public DataPack(Path path) {
|
||||
public Datapack(Path path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ public class DataPack {
|
||||
return packs;
|
||||
}
|
||||
|
||||
public static void installPack(Path sourceDataPackPath, Path targetDataPackDirectory, GameVersionNumber gameVersionNumber) throws IOException {
|
||||
public static void installPack(Path sourceDatapackPath, Path targetDatapackDirectory, GameVersionNumber gameVersionNumber) throws IOException {
|
||||
boolean containsMultiplePacks;
|
||||
Set<String> packs = new HashSet<>();
|
||||
try (FileSystem fs = CompressingUtils.readonly(sourceDataPackPath).setAutoDetectEncoding(true).build()) {
|
||||
Path dataPacks = fs.getPath("datapacks");
|
||||
try (FileSystem fs = CompressingUtils.readonly(sourceDatapackPath).setAutoDetectEncoding(true).build()) {
|
||||
Path datapacks = fs.getPath("datapacks");
|
||||
Path mcmeta = fs.getPath("pack.mcmeta");
|
||||
|
||||
if (Files.exists(dataPacks)) {
|
||||
if (Files.exists(datapacks)) {
|
||||
containsMultiplePacks = true;
|
||||
} else if (Files.exists(mcmeta)) {
|
||||
containsMultiplePacks = false;
|
||||
@@ -74,14 +74,14 @@ public class DataPack {
|
||||
}
|
||||
|
||||
if (containsMultiplePacks) {
|
||||
try (Stream<Path> s = Files.list(dataPacks)) {
|
||||
try (Stream<Path> s = Files.list(datapacks)) {
|
||||
packs = s.map(FileUtils::getNameWithoutExtension).collect(Collectors.toSet());
|
||||
}
|
||||
} else {
|
||||
packs.add(FileUtils.getNameWithoutExtension(sourceDataPackPath));
|
||||
packs.add(FileUtils.getNameWithoutExtension(sourceDatapackPath));
|
||||
}
|
||||
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDataPackDirectory)) {
|
||||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(targetDatapackDirectory)) {
|
||||
for (Path dir : stream) {
|
||||
String packName = FileUtils.getName(dir);
|
||||
if (FileUtils.getExtension(dir).equals(DISABLED_EXT)) {
|
||||
@@ -100,9 +100,9 @@ public class DataPack {
|
||||
}
|
||||
|
||||
if (!containsMultiplePacks) {
|
||||
FileUtils.copyFile(sourceDataPackPath, targetDataPackDirectory.resolve(FileUtils.getName(sourceDataPackPath)));
|
||||
FileUtils.copyFile(sourceDatapackPath, targetDatapackDirectory.resolve(FileUtils.getName(sourceDatapackPath)));
|
||||
} else {
|
||||
new Unzipper(sourceDataPackPath, targetDataPackDirectory)
|
||||
new Unzipper(sourceDatapackPath, targetDatapackDirectory)
|
||||
.setReplaceExistentFile(true)
|
||||
.setSubDirectory("/datapacks/")
|
||||
.unzip();
|
||||
@@ -113,14 +113,14 @@ public class DataPack {
|
||||
&& gameVersionNumber.compareTo("26.1-snapshot-6") >= 0;
|
||||
|
||||
if (useNewResourcePath) {
|
||||
Files.createDirectories(targetDataPackDirectory.getParent().resolve("resourcepacks"));
|
||||
targetResourceZipPath = targetDataPackDirectory.getParent().resolve("resourcepacks/resources.zip");
|
||||
Files.createDirectories(targetDatapackDirectory.getParent().resolve("resourcepacks"));
|
||||
targetResourceZipPath = targetDatapackDirectory.getParent().resolve("resourcepacks/resources.zip");
|
||||
} else {
|
||||
targetResourceZipPath = targetDataPackDirectory.getParent().resolve("resources.zip");
|
||||
targetResourceZipPath = targetDatapackDirectory.getParent().resolve("resources.zip");
|
||||
}
|
||||
|
||||
try (FileSystem outputResourcesZipFS = CompressingUtils.createWritableZipFileSystem(targetResourceZipPath);
|
||||
FileSystem inputPackZipFS = CompressingUtils.createReadOnlyZipFileSystem(sourceDataPackPath)) {
|
||||
FileSystem inputPackZipFS = CompressingUtils.createReadOnlyZipFileSystem(sourceDatapackPath)) {
|
||||
Path resourcesZip = inputPackZipFS.getPath("resources.zip");
|
||||
if (Files.isRegularFile(resourcesZip)) {
|
||||
Path tempResourcesFile = Files.createTempFile("hmcl", ".zip");
|
||||
@@ -229,14 +229,14 @@ public class DataPack {
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Pack> parsePack(Path dataPackPath, boolean isDirectory, String name, Path mcmetaPath) {
|
||||
private Optional<Pack> parsePack(Path datapackPath, boolean isDirectory, String name, Path mcmetaPath) {
|
||||
try {
|
||||
PackMcMeta mcMeta = JsonUtils.fromNonNullJson(Files.readString(mcmetaPath), PackMcMeta.class);
|
||||
return Optional.of(new Pack(dataPackPath, isDirectory, name, mcMeta.pack().description(), this));
|
||||
return Optional.of(new Pack(datapackPath, isDirectory, name, mcMeta.pack().description(), this));
|
||||
} catch (JsonParseException e) {
|
||||
LOG.warning("Invalid pack.mcmeta format in " + dataPackPath, e);
|
||||
LOG.warning("Invalid pack.mcmeta format in " + datapackPath, e);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("IO error reading " + dataPackPath, e);
|
||||
LOG.warning("IO error reading " + datapackPath, e);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -248,14 +248,14 @@ public class DataPack {
|
||||
private final BooleanProperty activeProperty;
|
||||
private final String id;
|
||||
private final LocalModFile.Description description;
|
||||
private final DataPack parentDataPack;
|
||||
private final Datapack parentDatapack;
|
||||
|
||||
public Pack(Path path, boolean isDirectory, String id, LocalModFile.Description description, DataPack parentDataPack) {
|
||||
public Pack(Path path, boolean isDirectory, String id, LocalModFile.Description description, Datapack parentDatapack) {
|
||||
this.path = path;
|
||||
this.isDirectory = isDirectory;
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
this.parentDataPack = parentDataPack;
|
||||
this.parentDatapack = parentDatapack;
|
||||
|
||||
this.statusFile = initializeStatusFile(path, isDirectory);
|
||||
this.activeProperty = initializeActiveProperty();
|
||||
@@ -313,8 +313,8 @@ public class DataPack {
|
||||
return description;
|
||||
}
|
||||
|
||||
public DataPack getParentDataPack() {
|
||||
return parentDataPack;
|
||||
public Datapack getParentDatapack() {
|
||||
return parentDatapack;
|
||||
}
|
||||
|
||||
public BooleanProperty activeProperty() {
|
||||
@@ -23,17 +23,12 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public abstract class Modpack {
|
||||
|
||||
public static final Set<String> SUPPORTED_ICON_EXTS = Set.of("png", "jpg", "jpeg", "bmp", "gif", "webp", "apng");
|
||||
public static final Set<String> SUPPORTED_ICON_NAMES = Set.of("icon.png", "icon.jpg", "icon.jpeg", "icon.bmp", "icon.gif", "icon.webp", "icon.apng");
|
||||
|
||||
private String name;
|
||||
private String author;
|
||||
private String version;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.mod;
|
||||
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;
|
||||
|
||||
@@ -22,20 +22,17 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.task.CacheFileTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Install a downloaded CurseForge modpack.
|
||||
@@ -50,11 +47,8 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
private final Modpack modpack;
|
||||
private final CurseManifest manifest;
|
||||
private final String name;
|
||||
private final String iconUrl;
|
||||
private final Path run;
|
||||
private final ModpackConfiguration<CurseManifest> config;
|
||||
private String iconExt;
|
||||
private Task<Path> downloadIconTask;
|
||||
private final List<Task<?>> dependents = new ArrayList<>(4);
|
||||
private final List<Task<?>> dependencies = new ArrayList<>(1);
|
||||
|
||||
@@ -66,13 +60,12 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
* @param manifest The manifest content of given CurseForge modpack.
|
||||
* @param name the new version name
|
||||
*/
|
||||
public CurseInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, Modpack modpack, CurseManifest manifest, String name, String iconUrl) {
|
||||
public CurseInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, Modpack modpack, CurseManifest manifest, String name) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.zipFile = zipFile;
|
||||
this.modpack = modpack;
|
||||
this.manifest = manifest;
|
||||
this.name = name;
|
||||
this.iconUrl = iconUrl;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
this.run = repository.getRunDirectory(name);
|
||||
|
||||
@@ -115,14 +108,6 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(manifest.overrides()), any -> true, config).withStage("hmcl.modpack"));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(manifest.overrides()), manifest, CurseModpackProvider.INSTANCE, manifest.name(), manifest.version(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
|
||||
URI iconUri = NetworkUtils.toURIOrNull(iconUrl);
|
||||
if (iconUri != null) {
|
||||
String ext = FileUtils.getExtension(StringUtils.substringAfter(iconUri.getPath(), '/')).toLowerCase(Locale.ROOT);
|
||||
if (Modpack.SUPPORTED_ICON_EXTS.contains(ext)) {
|
||||
iconExt = ext;
|
||||
dependents.add(downloadIconTask = new CacheFileTask(dependencyManager.getDownloadProvider().injectURLWithCandidates(iconUrl)));
|
||||
}
|
||||
}
|
||||
dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest));
|
||||
}
|
||||
|
||||
@@ -152,13 +137,5 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
Path root = repository.getVersionRoot(name);
|
||||
Files.createDirectories(root);
|
||||
JsonUtils.writeToJsonFile(root.resolve("manifest.json"), manifest);
|
||||
|
||||
if (iconExt != null && Modpack.SUPPORTED_ICON_NAMES.stream().map(root::resolve).allMatch(Files::notExists)) {
|
||||
try {
|
||||
Files.copy(downloadIconTask.getResult(), root.resolve("icon." + iconExt));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to copy modpack icon", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ public final class CurseModpackProvider implements ModpackProvider {
|
||||
if (!(modpack.getManifest() instanceof CurseManifest curseManifest))
|
||||
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new CurseInstallTask(dependencyManager, zipFile, modpack, curseManifest, name, null));
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new CurseInstallTask(dependencyManager, zipFile, modpack, curseManifest, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,7 +69,7 @@ public final class CurseModpackProvider implements ModpackProvider {
|
||||
return new Modpack(manifest.name(), manifest.author(), manifest.version(), manifest.minecraft().gameVersion(), description, encoding, manifest) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {
|
||||
return new CurseInstallTask(dependencyManager, zipFile, this, manifest, name, iconUrl);
|
||||
return new CurseInstallTask(dependencyManager, zipFile, this, manifest, name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.mod.modinfo;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.mod.modinfo;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
@@ -38,6 +38,7 @@ import java.util.*;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class ModrinthInstallTask extends Task<Void> {
|
||||
private static final Set<String> SUPPORTED_ICON_EXTS = Set.of("png", "jpg", "jpeg", "bmp", "gif", "webp", "apng");
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final DefaultGameRepository repository;
|
||||
@@ -120,7 +121,7 @@ public class ModrinthInstallTask extends Task<Void> {
|
||||
URI iconUri = NetworkUtils.toURIOrNull(iconUrl);
|
||||
if (iconUri != null) {
|
||||
String ext = FileUtils.getExtension(StringUtils.substringAfter(iconUri.getPath(), '/')).toLowerCase(Locale.ROOT);
|
||||
if (Modpack.SUPPORTED_ICON_EXTS.contains(ext)) {
|
||||
if (SUPPORTED_ICON_EXTS.contains(ext)) {
|
||||
iconExt = ext;
|
||||
|
||||
dependents.add(downloadIconTask = new CacheFileTask(dependencyManager.getDownloadProvider().injectURLWithCandidates(iconUrl)));
|
||||
@@ -156,7 +157,7 @@ public class ModrinthInstallTask extends Task<Void> {
|
||||
Files.createDirectories(root);
|
||||
JsonUtils.writeToJsonFile(root.resolve("modrinth.index.json"), manifest);
|
||||
|
||||
if (iconExt != null && Modpack.SUPPORTED_ICON_NAMES.stream().map(root::resolve).allMatch(Files::notExists)) {
|
||||
if (iconExt != null) {
|
||||
try {
|
||||
Files.copy(downloadIconTask.getResult(), root.resolve("icon." + iconExt));
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.mod.modrinth;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.mod.multimc;
|
||||
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.resourcepack;
|
||||
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.schematic;
|
||||
|
||||
import com.github.steveice10.opennbt.NBTIO;
|
||||
import com.github.steveice10.opennbt.tag.builtin.*;
|
||||
import javafx.geometry.Point3D;
|
||||
import org.glavo.nbt.io.NBTCodec;
|
||||
import org.glavo.nbt.tag.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -30,19 +30,32 @@ import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/// @author Glavo
|
||||
/// @see <a href="https://litemapy.readthedocs.io/en/v0.9.0b0/litematics.html">The Litematic file format</a>
|
||||
/**
|
||||
* @author Glavo
|
||||
* @see <a href="https://litemapy.readthedocs.io/en/v0.9.0b0/litematics.html">The Litematic file format</a>
|
||||
*/
|
||||
public final class LitematicFile {
|
||||
|
||||
private static int tryGetInt(Tag tag) {
|
||||
return tag instanceof IntTag ? ((IntTag) tag).getValue() : 0;
|
||||
}
|
||||
|
||||
private static @Nullable Instant tryGetLongTimestamp(Tag tag) {
|
||||
if (tag instanceof LongTag) {
|
||||
return Instant.ofEpochMilli(((LongTag) tag).getValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable String tryGetString(Tag tag) {
|
||||
return tag instanceof StringTag stringTag ? stringTag.get() : null;
|
||||
return tag instanceof StringTag ? ((StringTag) tag).getValue() : null;
|
||||
}
|
||||
|
||||
public static LitematicFile load(Path file) throws IOException {
|
||||
|
||||
CompoundTag root;
|
||||
try (InputStream in = new GZIPInputStream(Files.newInputStream(file))) {
|
||||
root = NBTCodec.of().readTag(in, TagType.COMPOUND);
|
||||
root = (CompoundTag) NBTIO.readTag(in);
|
||||
}
|
||||
|
||||
Tag versionTag = root.get("Version");
|
||||
@@ -58,13 +71,14 @@ public final class LitematicFile {
|
||||
throw new IOException("Metadata tag is not a compound tag");
|
||||
|
||||
int regions = 0;
|
||||
if (root.get("Regions") instanceof CompoundTag regionsTag)
|
||||
regions = regionsTag.size();
|
||||
Tag regionsTag = root.get("Regions");
|
||||
if (regionsTag instanceof CompoundTag)
|
||||
regions = ((CompoundTag) regionsTag).size();
|
||||
|
||||
return new LitematicFile(file, (CompoundTag) metadataTag,
|
||||
((IntTag) versionTag).getValue(),
|
||||
root.getIntOrZero("SubVersion"),
|
||||
root.getIntOrZero("MinecraftDataVersion"),
|
||||
tryGetInt(root.get("SubVersion")),
|
||||
tryGetInt(root.get("MinecraftDataVersion")),
|
||||
regions
|
||||
);
|
||||
}
|
||||
@@ -94,23 +108,26 @@ public final class LitematicFile {
|
||||
this.regionCount = regionCount;
|
||||
|
||||
Tag previewImageData = metadata.get("PreviewImageData");
|
||||
this.previewImageData = previewImageData instanceof IntArrayTag intArrayTag
|
||||
? intArrayTag.getArray()
|
||||
this.previewImageData = previewImageData instanceof IntArrayTag
|
||||
? ((IntArrayTag) previewImageData).getValue()
|
||||
: null;
|
||||
|
||||
this.name = tryGetString(metadata.get("Name"));
|
||||
this.author = tryGetString(metadata.get("Author"));
|
||||
this.description = tryGetString(metadata.get("Description"));
|
||||
this.timeCreated = metadata.get("TimeCreated") instanceof LongTag time ? Instant.ofEpochMilli(time.getValue()) : null;
|
||||
this.timeModified = metadata.get("TimeModified") instanceof LongTag time ? Instant.ofEpochMilli(time.getValue()) : null;
|
||||
this.totalBlocks = metadata.getIntOrZero("TotalBlocks");
|
||||
this.totalVolume = metadata.getIntOrZero("TotalVolume");
|
||||
this.timeCreated = tryGetLongTimestamp(metadata.get("TimeCreated"));
|
||||
this.timeModified = tryGetLongTimestamp(metadata.get("TimeModified"));
|
||||
this.totalBlocks = tryGetInt(metadata.get("TotalBlocks"));
|
||||
this.totalVolume = tryGetInt(metadata.get("TotalVolume"));
|
||||
|
||||
|
||||
Point3D enclosingSize = null;
|
||||
if (metadata.get("EnclosingSize") instanceof CompoundTag list) {
|
||||
int x = list.getIntOrZero("x");
|
||||
int y = list.getIntOrZero("y");
|
||||
int z = list.getIntOrZero("z");
|
||||
Tag enclosingSizeTag = metadata.get("EnclosingSize");
|
||||
if (enclosingSizeTag instanceof CompoundTag) {
|
||||
CompoundTag list = (CompoundTag) enclosingSizeTag;
|
||||
int x = tryGetInt(list.get("x"));
|
||||
int y = tryGetInt(list.get("y"));
|
||||
int z = tryGetInt(list.get("z"));
|
||||
|
||||
if (x >= 0 && y >= 0 && z >= 0)
|
||||
enclosingSize = new Point3D(x, y, z);
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.task;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.Contract;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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 javafx.beans.InvalidationListener;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* 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.NotNull;
|
||||
|
||||
@@ -79,6 +79,20 @@ public final class Lang {
|
||||
return Collections.unmodifiableList(Arrays.asList(elements));
|
||||
}
|
||||
|
||||
public static <T extends Comparable<T>> T clamp(T min, T val, T max) {
|
||||
if (val.compareTo(min) < 0) return min;
|
||||
else if (val.compareTo(max) > 0) return max;
|
||||
else return val;
|
||||
}
|
||||
|
||||
public static double clamp(double min, double val, double max) {
|
||||
return Math.max(min, Math.min(val, max));
|
||||
}
|
||||
|
||||
public static int clamp(int min, int val, int max) {
|
||||
return Math.max(min, Math.min(val, max));
|
||||
}
|
||||
|
||||
public static boolean test(ExceptionalRunnable<?> r) {
|
||||
try {
|
||||
r.run();
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/// Utility class for mathematical operations.
|
||||
///
|
||||
/// @author Glavo
|
||||
public final class MathUtils {
|
||||
|
||||
/// Clamp value to fit between min and max.
|
||||
public static double clamp(double value, double min, double max) {
|
||||
assert min <= max;
|
||||
return Math.min(max, Math.max(value, min));
|
||||
}
|
||||
|
||||
/// Clamp value to fit between min and max.
|
||||
public static int clamp(int value, int min, int max) {
|
||||
assert min <= max;
|
||||
return Math.min(max, Math.max(value, min));
|
||||
}
|
||||
|
||||
private MathUtils() {
|
||||
}
|
||||
}
|
||||
@@ -86,9 +86,4 @@ public final class SettingsMap {
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SettingsMap" + map;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.gson;
|
||||
|
||||
public @interface JsonSubtype {
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.gson;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.gson;
|
||||
|
||||
import com.google.gson.*;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* 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.io;
|
||||
|
||||
import org.jackhuang.hmcl.util.InfiniteSizeList;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.io;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* 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.platform.hardware;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* 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.tree;
|
||||
|
||||
import kala.compress.archivers.tar.TarArchiveEntry;
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.util.versioning;
|
||||
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -190,32 +189,6 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
return buildDebugString().toString();
|
||||
}
|
||||
|
||||
public static @Nullable GameVersionNumber getReleaseOfSnapshot(GameVersionNumber gameVersion) {
|
||||
if (gameVersion instanceof Release release) {
|
||||
if (release.getEaType() == Release.ReleaseType.GA) {
|
||||
return null;
|
||||
}
|
||||
if (release.getPatch() > 0) {
|
||||
return asGameVersion(release.getMajor() + "." + release.getMinor() + "." + release.getPatch());
|
||||
} else {
|
||||
return asGameVersion(release.getMajor() + "." + release.getMinor());
|
||||
}
|
||||
}
|
||||
|
||||
if (gameVersion instanceof LegacySnapshot snapshot) {
|
||||
String[] defaultVersions = Versions.DEFAULT_GAME_VERSIONS;
|
||||
for (int i = defaultVersions.length - 1; i >= 0; i--) {
|
||||
Release gaRelease = (Release) asGameVersion(defaultVersions[i]);
|
||||
|
||||
if (gaRelease.compareToSnapshot(snapshot) > 0) {
|
||||
return gaRelease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final class Old extends GameVersionNumber {
|
||||
static Old parse(String value) {
|
||||
if (value.isEmpty())
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.versioning;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.gson;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.io;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.platform;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.skin;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.asGameVersion;
|
||||
import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.getReleaseOfSnapshot;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
@@ -508,14 +507,4 @@ public final class GameVersionNumberTest {
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", true));
|
||||
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetReleaseOfSnapshot() {
|
||||
assertEquals(asGameVersion("1.21.11"), getReleaseOfSnapshot(asGameVersion("25w45a")));
|
||||
assertEquals(asGameVersion("1.21.11"), getReleaseOfSnapshot(asGameVersion("25w45a_unobfuscated")));
|
||||
assertEquals(asGameVersion("1.21.11"), getReleaseOfSnapshot(asGameVersion("1.21.11-pre3")));
|
||||
assertEquals(asGameVersion("26.1"), getReleaseOfSnapshot(asGameVersion("26.1-snapshot-9")));
|
||||
assertNull(getReleaseOfSnapshot(asGameVersion("26.1")));
|
||||
assertNull(getReleaseOfSnapshot(asGameVersion("20w14infinite")));
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.versioning;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -41,8 +41,6 @@ subprojects {
|
||||
@Suppress("UnstableApiUsage")
|
||||
tasks.withType<Checkstyle> {
|
||||
maxHeapSize.set("2g")
|
||||
|
||||
setConfigProperties("licenseHeaderFile" to rootProject.rootDir.resolve("config/checkstyle/license-header.txt"))
|
||||
}
|
||||
|
||||
configure<CheckstyleExtension> {
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
/*
|
||||
* 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.gradle;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user