Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3a5b2a99d | ||
|
|
eac2740ea8 | ||
|
|
a06da53ec9 | ||
|
|
765ea24d22 | ||
|
|
bd24ab37f5 | ||
|
|
c689272c42 | ||
|
|
952cc5dd37 | ||
|
|
356df5d630 | ||
|
|
0e433c5f4b | ||
|
|
a28509de85 | ||
|
|
e45601ecda | ||
|
|
5bbf5cbfc8 | ||
|
|
afb98c20ce | ||
|
|
6b2bc4606b | ||
|
|
6b57cec576 |
No files matched your search
@@ -57,6 +57,7 @@ dependencies {
|
||||
implementation(project(":HMCLBoot"))
|
||||
implementation("libs:JFoenix")
|
||||
implementation(libs.twelvemonkeys.imageio.webp)
|
||||
implementation(libs.fxsvgimage)
|
||||
implementation(libs.java.info)
|
||||
implementation(libs.monet.fx)
|
||||
implementation(libs.nayuki.qrcodegen)
|
||||
|
||||
@@ -23,21 +23,13 @@ import com.jfoenix.controls.events.JFXDialogEvent;
|
||||
import com.jfoenix.converters.DialogTransitionConverter;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import com.jfoenix.transitions.CachedTransition;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.animation.Transition;
|
||||
import javafx.animation.*;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ObjectPropertyBase;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.css.CssMetaData;
|
||||
import javafx.css.SimpleStyleableObjectProperty;
|
||||
import javafx.css.Styleable;
|
||||
import javafx.css.StyleableObjectProperty;
|
||||
import javafx.css.StyleableProperty;
|
||||
import javafx.css.*;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -379,6 +371,14 @@ public class JFXDialog extends StackPane {
|
||||
setCycleDuration(Duration.seconds(0.4));
|
||||
setDelay(Duration.ZERO);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void starting() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopping() {
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
|
||||
@@ -40,12 +40,16 @@ import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.*;
|
||||
import javafx.util.Duration;
|
||||
import org.glavo.monetfx.ColorScheme;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author Shadi Shaheen
|
||||
*/
|
||||
@@ -78,7 +82,7 @@ public class JFXCustomColorPickerDialog extends StackPane {
|
||||
|
||||
// create JFX Decorator
|
||||
pickerDecorator = new JFXDecorator(dialog, this, false, false, false);
|
||||
pickerDecorator.setOnCloseButtonAction(this::updateColor);
|
||||
pickerDecorator.setOnCloseButtonAction(this::close);
|
||||
pickerDecorator.setPickOnBounds(false);
|
||||
customScene = new Scene(pickerDecorator, Color.TRANSPARENT);
|
||||
StyleSheets.init(customScene);
|
||||
@@ -153,6 +157,22 @@ public class JFXCustomColorPickerDialog extends StackPane {
|
||||
paraTransition.play();
|
||||
});
|
||||
|
||||
container.getChildren().add(tabs);
|
||||
|
||||
HBox actionsHBox = new HBox();
|
||||
actionsHBox.getStyleClass().add("jfx-color-dialog-actions");
|
||||
|
||||
JFXButton acceptButton = new JFXButton(i18n("button.ok"));
|
||||
acceptButton.setOnAction(event -> updateColor());
|
||||
acceptButton.getStyleClass().add("jfx-color-dialog-accept");
|
||||
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
cancelButton.setOnAction(event -> close());
|
||||
cancelButton.getStyleClass().add("jfx-color-dialog-cancel");
|
||||
|
||||
actionsHBox.getChildren().addAll(acceptButton, cancelButton);
|
||||
container.getChildren().add(actionsHBox);
|
||||
|
||||
initRun = () -> {
|
||||
// change tabs labels font color according to the selected color
|
||||
pane.backgroundProperty().addListener((o, oldVal, newVal) -> {
|
||||
@@ -232,7 +252,13 @@ public class JFXCustomColorPickerDialog extends StackPane {
|
||||
hexField.focusColorProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
return pane.getBackground().getFills().get(0).getFill();
|
||||
}, pane.backgroundProperty()));
|
||||
|
||||
acceptButton.textFillProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
Color fill = (Color) pane.getBackground().getFills().get(0).getFill();
|
||||
return ColorScheme.newBuilder(Themes.getColorScheme())
|
||||
.setPrimaryColorSeed(fill)
|
||||
.build()
|
||||
.getPrimary();
|
||||
}, pane.backgroundProperty()));
|
||||
|
||||
((Pane) pickerDecorator.lookup(".jfx-decorator-buttons-container")).backgroundProperty()
|
||||
.bind(Bindings.createObjectBinding(() -> {
|
||||
@@ -262,9 +288,6 @@ public class JFXCustomColorPickerDialog extends StackPane {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
container.getChildren().add(tabs);
|
||||
|
||||
this.getChildren().add(container);
|
||||
this.setPadding(new Insets(0));
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
@@ -49,6 +50,8 @@ public final class EntryPoint {
|
||||
createHMCLDirectories();
|
||||
LOG.start(Metadata.HMCL_CURRENT_DIRECTORY.resolve("logs"));
|
||||
|
||||
checkWine();
|
||||
|
||||
setupJavaFXVMOptions();
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
@@ -220,6 +223,20 @@ public final class EntryPoint {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkWine() {
|
||||
if (OperatingSystem.isRunningUnderWine()) {
|
||||
SwingUtils.initLookAndFeel();
|
||||
LOG.warning("HMCL is running under Wine or its distributions!");
|
||||
|
||||
int result = JOptionPane.showOptionDialog(null, i18n("fatal.wine_warning"), i18n("message.warning"), JOptionPane.OK_CANCEL_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE, null, null, null);
|
||||
|
||||
if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addEnableNativeAccess() {
|
||||
if (JavaRuntime.CURRENT_VERSION > 21) {
|
||||
try {
|
||||
|
||||
@@ -17,35 +17,36 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableBooleanValue;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.DataFormat;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.SambaException;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.FileSaver;
|
||||
import org.jackhuang.hmcl.task.AsyncTaskExecutor;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.CrashReporter;
|
||||
import org.jackhuang.hmcl.util.FileSaver;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -65,8 +66,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class Launcher extends Application {
|
||||
public static final CookieManager COOKIE_MANAGER = new CookieManager();
|
||||
@@ -111,7 +112,7 @@ public final class Launcher extends Application {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS
|
||||
&& ConfigHolder.isNewlyCreated()
|
||||
&& System.getProperty("user.dir").startsWith("/private/var/folders/")) {
|
||||
if (showAlert(AlertType.WARNING, i18n("fatal.mac_app_translocation"), ButtonType.YES, ButtonType.NO) == ButtonType.NO)
|
||||
if (!confirmWithCountdown(AlertType.WARNING, i18n("fatal.mac_app_translocation"), 5))
|
||||
return;
|
||||
} else {
|
||||
checkConfigInTempDir();
|
||||
@@ -182,6 +183,27 @@ public final class Launcher extends Application {
|
||||
return new Alert(alertType, contentText, buttons).showAndWait().orElse(null);
|
||||
}
|
||||
|
||||
private static boolean confirmWithCountdown(Alert.AlertType alertType, String contentText, int seconds) {
|
||||
Alert alert = new Alert(alertType, contentText, ButtonType.YES, ButtonType.NO);
|
||||
Button okButton = (Button) alert.getDialogPane().lookupButton(ButtonType.YES);
|
||||
|
||||
okButton.setDisable(true);
|
||||
|
||||
KeyFrame[] keyFrames = new KeyFrame[seconds + 1];
|
||||
for (int i = 0; i < seconds; i++) {
|
||||
keyFrames[i] = new KeyFrame(Duration.seconds(i),
|
||||
new KeyValue(okButton.textProperty(), i18n("button.ok.countdown", seconds - i)));
|
||||
}
|
||||
keyFrames[seconds] = new KeyFrame(Duration.seconds(seconds),
|
||||
new KeyValue(okButton.textProperty(), i18n("button.ok")),
|
||||
new KeyValue(okButton.disableProperty(), false));
|
||||
|
||||
Timeline timeline = new Timeline(keyFrames);
|
||||
alert.setOnShown(e -> timeline.play());
|
||||
alert.setOnCloseRequest(e -> timeline.stop());
|
||||
return alert.showAndWait().orElse(null) == ButtonType.YES;
|
||||
}
|
||||
|
||||
private static boolean isConfigInTempDir() {
|
||||
String configPath = ConfigHolder.configLocation().toString();
|
||||
|
||||
@@ -221,7 +243,7 @@ public final class Launcher extends Application {
|
||||
|
||||
private static void checkConfigInTempDir() {
|
||||
if (ConfigHolder.isNewlyCreated() && isConfigInTempDir()
|
||||
&& showAlert(AlertType.WARNING, i18n("fatal.config_in_temp_dir"), ButtonType.YES, ButtonType.NO) == ButtonType.NO) {
|
||||
&& !confirmWithCountdown(AlertType.WARNING, i18n("fatal.config_in_temp_dir"), 5)) {
|
||||
EntryPoint.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ public final class Metadata {
|
||||
public static final String ABOUT_URL = PUBLISH_URL + "/about";
|
||||
public static final String DOWNLOAD_URL = PUBLISH_URL + "/download";
|
||||
public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.update_source.override", PUBLISH_URL + "/api/update_link");
|
||||
public static final String MANUAL_UPDATE_URL = "https://github.com/HMCL-dev/HMCL/releases";
|
||||
|
||||
public static final String DOCS_URL = "https://docs.hmcl.net";
|
||||
public static final String CONTACT_URL = DOCS_URL + "/help.html";
|
||||
|
||||
@@ -310,7 +310,7 @@ public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
Optional<Path> iconFile = getVersionIconFile(id);
|
||||
if (iconFile.isPresent()) {
|
||||
try {
|
||||
return FXUtils.loadImage(iconFile.get());
|
||||
return FXUtils.loadImage(iconFile.get(), 64, 64, true, true);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load version icon of " + id, e);
|
||||
}
|
||||
|
||||
@@ -567,6 +567,21 @@ public final class Config extends ObservableSetting {
|
||||
this.versionListSource.set(versionListSource);
|
||||
}
|
||||
|
||||
@SerializedName("defaultAddonSource")
|
||||
private final StringProperty defaultAddonSource = new SimpleStringProperty("modrinth");
|
||||
|
||||
public StringProperty defaultAddonSourceProperty() {
|
||||
return defaultAddonSource;
|
||||
}
|
||||
|
||||
public String getDefaultAddonSource() {
|
||||
return defaultAddonSource.get();
|
||||
}
|
||||
|
||||
public void setDefaultAddonSource(String defaultAddonSource) {
|
||||
this.defaultAddonSource.set(defaultAddonSource);
|
||||
}
|
||||
|
||||
@SerializedName("hasProxy")
|
||||
private final BooleanProperty hasProxy = new SimpleBooleanProperty();
|
||||
|
||||
|
||||
@@ -235,8 +235,8 @@ public final class FXUtils {
|
||||
|
||||
public static final String DEFAULT_MONOSPACE_FONT = OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "Consolas" : "Monospace";
|
||||
|
||||
public static final List<String> IMAGE_EXTENSIONS = Lang.immutableListOf(
|
||||
"png", "jpg", "jpeg", "bmp", "gif", "webp", "apng"
|
||||
public static final List<String> IMAGE_EXTENSIONS = List.of(
|
||||
"png", "jpg", "jpeg", "bmp", "gif", "webp", "svg", "apng"
|
||||
);
|
||||
|
||||
private static final Map<String, Image> builtinImageCache = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -19,9 +19,17 @@ package org.jackhuang.hmcl.ui.image;
|
||||
|
||||
import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.SnapshotParameters;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.PixelFormat;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.girod.javafx.svgimage.LoaderParameters;
|
||||
import org.girod.javafx.svgimage.SVGImage;
|
||||
import org.girod.javafx.svgimage.SVGLoader;
|
||||
import org.girod.javafx.svgimage.ScaleQuality;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.image.apng.Png;
|
||||
import org.jackhuang.hmcl.ui.image.apng.argb8888.Argb8888Bitmap;
|
||||
import org.jackhuang.hmcl.ui.image.apng.argb8888.Argb8888BitmapSequence;
|
||||
@@ -39,7 +47,9 @@ import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
@@ -73,6 +83,46 @@ public final class ImageUtils {
|
||||
return SwingFXUtils.toFXImage(bufferedImage, requestedWidth, requestedHeight, preserveRatio, smooth);
|
||||
};
|
||||
|
||||
public static final ImageLoader SVG = (input, requestedWidth, requestedHeight, preserveRatio, smooth) -> {
|
||||
String content = new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
LoaderParameters parameters = new LoaderParameters();
|
||||
parameters.autoStartAnimations = false;
|
||||
|
||||
SVGImage image;
|
||||
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
image = SVGLoader.load(content, parameters);
|
||||
} else {
|
||||
// TODO: Currently, SVGLoader.load(...) requires the javafx.swing module if it operates on a non-JavaFX thread.
|
||||
image = CompletableFuture.supplyAsync(
|
||||
() -> SVGLoader.load(content, parameters),
|
||||
Schedulers.javafx()
|
||||
).get();
|
||||
}
|
||||
|
||||
if (image == null)
|
||||
throw new IOException("Failed to load SVG image");
|
||||
|
||||
var snapshotParameters = new SnapshotParameters();
|
||||
snapshotParameters.setFill(Color.TRANSPARENT);
|
||||
|
||||
if (requestedWidth <= 0. || requestedHeight <= 0.) {
|
||||
return image.toImage(snapshotParameters);
|
||||
}
|
||||
|
||||
double scaleX = requestedWidth / image.getScaledWidth();
|
||||
double scaleY = requestedHeight / image.getScaledHeight();
|
||||
|
||||
if (preserveRatio || scaleX == scaleY) {
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
return image.scale(scale).toImage(snapshotParameters);
|
||||
} else {
|
||||
// FIXME: Use DEFAULT_SVG_SNAPSHOT_PARAMS
|
||||
return image.toImageScaled(ScaleQuality.RENDER_QUALITY, scaleX, scaleY);
|
||||
}
|
||||
};
|
||||
|
||||
public static final ImageLoader APNG = (input, requestedWidth, requestedHeight, preserveRatio, smooth) -> {
|
||||
if (!"true".equals(System.getProperty("hmcl.experimental.apng", "true")))
|
||||
return DEFAULT.load(input, requestedWidth, requestedHeight, preserveRatio, smooth);
|
||||
@@ -136,11 +186,13 @@ public final class ImageUtils {
|
||||
|
||||
public static final Map<String, ImageLoader> EXT_TO_LOADER = Map.of(
|
||||
"webp", WEBP,
|
||||
"svg", SVG,
|
||||
"apng", APNG
|
||||
);
|
||||
|
||||
public static final Map<String, ImageLoader> CONTENT_TYPE_TO_LOADER = Map.of(
|
||||
"image/webp", WEBP,
|
||||
"image/svg+xml", SVG,
|
||||
"image/apng", APNG
|
||||
);
|
||||
|
||||
@@ -165,6 +217,14 @@ public final class ImageUtils {
|
||||
&& Arrays.equals(headerBuffer, 8, 12, WEBP_HEADER, 0, 4);
|
||||
}
|
||||
|
||||
private static final byte[] SVG_HEADER = "<svg".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
// This is currently a simple check, more complex checks can be considered in the future
|
||||
public static boolean isSVG(byte[] headerBuffer) {
|
||||
return headerBuffer.length > SVG_HEADER.length
|
||||
&& Arrays.equals(headerBuffer, 0, SVG_HEADER.length, SVG_HEADER, 0, SVG_HEADER.length);
|
||||
}
|
||||
|
||||
private static final byte[] PNG_HEADER = {
|
||||
(byte) 0x89, (byte) 0x50, (byte) 0x4e, (byte) 0x47,
|
||||
(byte) 0x0d, (byte) 0x0a, (byte) 0x1a, (byte) 0x0a,
|
||||
@@ -232,6 +292,8 @@ public final class ImageUtils {
|
||||
return WEBP;
|
||||
if (isApng(headerBuffer))
|
||||
return APNG;
|
||||
if (isSVG(headerBuffer))
|
||||
return SVG;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.google.gson.*;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
@@ -30,6 +28,7 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.LineButton;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -38,11 +37,18 @@ import java.io.InputStream;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class AboutPage extends StackPane {
|
||||
public final class AboutPage extends SpinnerPane {
|
||||
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
|
||||
public AboutPage() {
|
||||
VBox content = new VBox();
|
||||
content.getStyleClass().add("spinner-pane-content");
|
||||
ScrollPane scrollPane = new ScrollPane(content);
|
||||
scrollPane.setFitToWidth(true);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
setContent(scrollPane);
|
||||
|
||||
ComponentList about = new ComponentList();
|
||||
{
|
||||
var launcher = LineButton.createExternalLinkButton(Metadata.PUBLISH_URL);
|
||||
@@ -84,27 +90,16 @@ public final class AboutPage extends StackPane {
|
||||
legal.getContent().setAll(copyright, claim, openSource);
|
||||
}
|
||||
|
||||
VBox content = new VBox(16);
|
||||
content.setPadding(new Insets(10));
|
||||
content.getChildren().setAll(
|
||||
ComponentList.createComponentListTitle(i18n("about")),
|
||||
about,
|
||||
|
||||
ComponentList.createComponentListTitle(i18n("about.thanks_to")),
|
||||
thanks,
|
||||
|
||||
ComponentList.createComponentListTitle(i18n("about.dependency")),
|
||||
deps,
|
||||
|
||||
ComponentList.createComponentListTitle(i18n("about.legal")),
|
||||
legal
|
||||
);
|
||||
|
||||
|
||||
ScrollPane scrollPane = new ScrollPane(content);
|
||||
scrollPane.setFitToWidth(true);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
getChildren().setAll(scrollPane);
|
||||
}
|
||||
|
||||
private static Image loadImage(String url) {
|
||||
|
||||
@@ -85,7 +85,13 @@ public class DownloadSettingsPage extends StackPane {
|
||||
downloadSourcePane.setItems(DownloadProviders.DIRECT_PROVIDERS.keySet());
|
||||
downloadSourcePane.valueProperty().bindBidirectional(config().downloadTypeProperty());
|
||||
|
||||
downloadSource.getContent().setAll(autoChooseDownloadSource, versionListSourcePane, downloadSourcePane);
|
||||
var defaultAddonSourcePane = new LineSelectButton<String>();
|
||||
defaultAddonSourcePane.setTitle(i18n("settings.launcher.default_addon_source"));
|
||||
defaultAddonSourcePane.setConverter(key -> I18n.i18n("mods." + key));
|
||||
defaultAddonSourcePane.setItems("modrinth", "curseforge");
|
||||
defaultAddonSourcePane.valueProperty().bindBidirectional(config().defaultAddonSourceProperty());
|
||||
|
||||
downloadSource.getContent().setAll(autoChooseDownloadSource, versionListSourcePane, downloadSourcePane, defaultAddonSourcePane);
|
||||
}
|
||||
|
||||
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.download_source")), downloadSource);
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
@@ -37,9 +36,7 @@ public class FeedbackPage extends SpinnerPane {
|
||||
|
||||
public FeedbackPage() {
|
||||
VBox content = new VBox();
|
||||
content.setPadding(new Insets(10));
|
||||
content.setSpacing(10);
|
||||
content.setFillWidth(true);
|
||||
content.getStyleClass().add("spinner-pane-content");
|
||||
ScrollPane scrollPane = new ScrollPane(content);
|
||||
scrollPane.setFitToWidth(true);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
@@ -84,8 +81,5 @@ public class FeedbackPage extends SpinnerPane {
|
||||
ComponentList.createComponentListTitle(i18n("contact.feedback")),
|
||||
feedback
|
||||
);
|
||||
|
||||
this.setContent(content);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
@@ -42,9 +41,7 @@ public class HelpPage extends SpinnerPane {
|
||||
|
||||
public HelpPage() {
|
||||
content = new VBox();
|
||||
content.setPadding(new Insets(10));
|
||||
content.setSpacing(10);
|
||||
content.setFillWidth(true);
|
||||
content.getStyleClass().add("spinner-pane-content");
|
||||
ScrollPane scrollPane = new ScrollPane(content);
|
||||
scrollPane.setFitToWidth(true);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
|
||||
+19
-6
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.util.MissingResourceException;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@@ -62,17 +63,29 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
|
||||
|
||||
supportChinese.set(true);
|
||||
|
||||
boolean supportedCurseForge = CurseForgeRemoteModRepository.isAvailable() && curseForge != null;
|
||||
|
||||
downloadSources.setAll("mods.modrinth");
|
||||
if (CurseForgeRemoteModRepository.isAvailable()) {
|
||||
if (supportedCurseForge) {
|
||||
downloadSources.add("mods.curseforge");
|
||||
}
|
||||
|
||||
if (modrinth != null) {
|
||||
downloadSource.set("mods.modrinth");
|
||||
} else if (curseForge != null) {
|
||||
downloadSource.set("mods.curseforge");
|
||||
if ("curseforge".equalsIgnoreCase(config().getDefaultAddonSource())) {
|
||||
if (supportedCurseForge) {
|
||||
downloadSource.set("mods.curseforge");
|
||||
} else if (modrinth != null) {
|
||||
downloadSource.set("mods.modrinth");
|
||||
} else {
|
||||
throw new AssertionError("Should not be here.");
|
||||
}
|
||||
} else {
|
||||
throw new AssertionError("Should not be here.");
|
||||
if (modrinth != null) {
|
||||
downloadSource.set("mods.modrinth");
|
||||
} else if (supportedCurseForge) {
|
||||
downloadSource.set("mods.curseforge");
|
||||
} else {
|
||||
throw new AssertionError("Should not be here.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,10 @@ import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.EventPriority;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
@@ -39,8 +41,10 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -186,15 +190,34 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
private void clearLibraries() {
|
||||
FileUtils.deleteDirectoryQuietly(getProfile().getRepository().getBaseDirectory().resolve("libraries"));
|
||||
var libraries = getProfile().getRepository().getBaseDirectory().resolve("libraries");
|
||||
Task.runAsync(Schedulers.io(), () -> {
|
||||
FileUtils.deleteDirectoryQuietly(libraries);
|
||||
}).whenComplete(Schedulers.javafx(), (exception) -> {
|
||||
if (exception != null) {
|
||||
Controllers.dialog(i18n("message.failed") + "\n" + StringUtils.getStackTrace(exception), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void clearAssets() {
|
||||
HMCLGameRepository baseDirectory = getProfile().getRepository();
|
||||
FileUtils.deleteDirectoryQuietly(baseDirectory.getBaseDirectory().resolve("assets"));
|
||||
if (version.get() != null) {
|
||||
FileUtils.deleteDirectoryQuietly(baseDirectory.getRunDirectory(version.get().getVersion()).resolve("resources"));
|
||||
}
|
||||
Path assetsDir = getProfile().getRepository().getBaseDirectory().resolve("assets");
|
||||
|
||||
Profile.ProfileVersion currentVersion = version.get();
|
||||
Path resourcesDir = currentVersion != null
|
||||
? getProfile().getRepository().getRunDirectory(currentVersion.getVersion()).resolve("resources")
|
||||
: null;
|
||||
|
||||
Task.runAsync(Schedulers.io(), () -> {
|
||||
FileUtils.deleteDirectoryQuietly(assetsDir);
|
||||
if (resourcesDir != null) {
|
||||
FileUtils.deleteDirectoryQuietly(resourcesDir);
|
||||
}
|
||||
}).whenComplete(Schedulers.javafx(), (exception) -> {
|
||||
if (exception != null) {
|
||||
Controllers.dialog(i18n("message.failed") + "\n" + StringUtils.getStackTrace(exception), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void clearJunkFiles() {
|
||||
|
||||
@@ -67,7 +67,7 @@ public final class UpdateHandler {
|
||||
performMigration();
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to perform migration", e);
|
||||
SwingUtils.showErrorDialog(i18n("fatal.apply_update_failure", Metadata.PUBLISH_URL) + "\n" + StringUtils.getStackTrace(e));
|
||||
SwingUtils.showErrorDialog(i18n("fatal.apply_update_failure", Metadata.MANUAL_UPDATE_URL) + "\n" + StringUtils.getStackTrace(e));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ public final class UpdateHandler {
|
||||
applyUpdate(Paths.get(args[1]));
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to apply update", e);
|
||||
SwingUtils.showErrorDialog(i18n("fatal.apply_update_failure", Metadata.PUBLISH_URL) + "\n" + StringUtils.getStackTrace(e));
|
||||
SwingUtils.showErrorDialog(i18n("fatal.apply_update_failure", Metadata.MANUAL_UPDATE_URL) + "\n" + StringUtils.getStackTrace(e));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,32 @@ public class Translator_lzh extends Translator {
|
||||
case "23w13a_or_b" -> "二三週一三暨";
|
||||
case "24w14potato" -> "二四週一四芋";
|
||||
case "25w14craftmine" -> "二五週一四礦";
|
||||
default -> version;
|
||||
default -> {
|
||||
if (version.length() >= 6 && version.charAt(2) == 'w') {
|
||||
Matcher matcher = Pattern.compile("(?<year>[0-9]{2})w(?<week>[0-9]{2})(?<suffix>.+)").matcher(version);
|
||||
if (matcher.matches()) {
|
||||
int year = Integer.parseInt(matcher.group("year"));
|
||||
int week = Integer.parseInt(matcher.group("week"));
|
||||
String suffix = matcher.group("suffix");
|
||||
char suffixFirstChar = suffix.charAt(0);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
appendDigitByDigit(builder, String.valueOf(year));
|
||||
builder.append('週');
|
||||
appendDigitByDigit(builder, String.valueOf(week));
|
||||
|
||||
if (suffix.length() == 1
|
||||
&& suffixFirstChar >= 'a' && (suffixFirstChar - 'a') < TIAN_GAN.length)
|
||||
builder.append(TIAN_GAN[suffixFirstChar - 'a']);
|
||||
else
|
||||
builder.append(suffix);
|
||||
|
||||
yield builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
yield version;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return gameVersion.toString();
|
||||
|
||||
@@ -69,6 +69,11 @@
|
||||
"subtitle" : "Copyright © 2015 Andrew Ellerton.\nLicensed under the Apache 2.0 License.",
|
||||
"externalLink" : "https://github.com/aellerton/japng"
|
||||
},
|
||||
{
|
||||
"title" : "fxsvgimage",
|
||||
"subtitle" : "Copyright (c) 2021, 2022, 2025 Hervé Girod.\nLicensed under the BSD 3-clause License.",
|
||||
"externalLink" : "https://github.com/hervegirod/fxsvgimage"
|
||||
},
|
||||
{
|
||||
"title": "Terracotta",
|
||||
"subtitle": "Copyright © 2025 Burning_TNT.\nLicensed under the AGPL 3.0 License.",
|
||||
|
||||
@@ -1463,6 +1463,11 @@
|
||||
-jfx-radius: 9;
|
||||
}
|
||||
|
||||
.spinner-pane .spinner-pane-content {
|
||||
-fx-padding: 10px;
|
||||
-fx-spacing: 10px;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* JFX Combo Box *
|
||||
@@ -1565,6 +1570,21 @@
|
||||
-fx-background-color: -monet-on-surface;
|
||||
}
|
||||
|
||||
.custom-color-dialog .jfx-color-dialog-actions {
|
||||
-fx-background-color: -monet-surface;
|
||||
-fx-padding: 0.0 10.0 5.0 0.0;
|
||||
-fx-alignment: center-right;
|
||||
}
|
||||
|
||||
.custom-color-dialog .jfx-color-dialog-accept {
|
||||
-fx-padding: 0.7em 0.8em;
|
||||
}
|
||||
|
||||
.custom-color-dialog .jfx-color-dialog-cancel {
|
||||
-fx-text-fill: -monet-on-surface-variant;
|
||||
-fx-padding: 0.7em 0.8em;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* *
|
||||
* Table View *
|
||||
|
||||
@@ -458,6 +458,8 @@ fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher has provided
|
||||
\n\
|
||||
If you are using the <b>Qualcomm</b> platform, you may need to install the <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL Compatibility Pack</a> before playing games.\n\
|
||||
Click the link to navigate to the Microsoft Store and install the compatibility pack.
|
||||
# When translating the following string into non-Latin languages, please retain the English translation
|
||||
fatal.wine_warning=HMCL is running under Wine or one of its distributions.\nAs a cross-platform application, HMCL is best experienced natively on your host OS.\nPress "OK" to continue, but please note that some issues may arise.
|
||||
|
||||
file=File
|
||||
|
||||
@@ -1439,6 +1441,7 @@ settings.launcher.download.threads.auto=Automatically Determine
|
||||
settings.launcher.download.threads.hint=Too many threads may cause your system to freeze, and your download speed may be affected by your ISP and download servers. It is not always the case that more threads increase your download speed.
|
||||
settings.launcher.download_source=Download Source
|
||||
settings.launcher.download_source.auto=Automatically Choose Download Sources
|
||||
settings.launcher.default_addon_source=Default Addon Source
|
||||
settings.launcher.enable_game_list=Show instance list in homepage
|
||||
settings.launcher.font=Font
|
||||
settings.launcher.font.anti_aliasing=Anti-aliasing
|
||||
@@ -1587,7 +1590,7 @@ update=Update
|
||||
update.accept=Update
|
||||
update.changelog=Changelog
|
||||
update.channel.dev=Beta
|
||||
update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Release channel builds.\n\
|
||||
update.channel.dev.hint=You are currently using a Beta channel build of the launcher. While it may include some extra features, it is also sometimes less stable than the Stable channel builds.\n\
|
||||
\n\
|
||||
If you encounter any bugs or problems, please submit feedback via the channels provided on the <a href="hmcl://settings/feedback">Feedback</a> page.\n\
|
||||
\n\
|
||||
@@ -1600,7 +1603,7 @@ update.channel.nightly.hint=You are currently using a Nightly channel build of t
|
||||
\n\
|
||||
Follow <a href="https://space.bilibili.com/1445341">@huanghongxun</a> on Bilibili to stay up to date on important HMCL news, or <a href="https://space.bilibili.com/20314891">@Glavo</a> to learn about HMCL development progress.
|
||||
update.channel.nightly.title=Nightly Channel Notice
|
||||
update.channel.stable=Release
|
||||
update.channel.stable=Stable
|
||||
update.checking=Checking for updates
|
||||
update.disable_auto_show_update_dialog=Do not show update dialog automatically
|
||||
update.disable_auto_show_update_dialog.subtitle=Enable this option to prevent HMCL from automatically showing the update dialog.
|
||||
@@ -1610,7 +1613,7 @@ 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.dev=Suitable for users who want to experience new features first.\nThe development version includes the latest features and bug fixes,\nbut may also have more issues due to insufficient testing.
|
||||
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
|
||||
|
||||
@@ -421,6 +421,8 @@ fatal.unsupported_platform=Minecraft 尚未對您的平臺提供完善支援,
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已為龍芯提供支援。\n如果遇到問題,你可以點擊右上角幫助按鈕進行求助。
|
||||
fatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher 已為 Apple Silicon 平臺提供支援。使用 ARM 原生 Java 啟動遊戲以獲得更流暢的遊戲體驗。\n如果你在遊戲中遭遇問題,使用 x86-64 架構的 Java 啟動遊戲可能有更好的相容性。
|
||||
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已為 Windows on Arm 平臺提供原生支援。如果你在遊戲中遭遇問題,請嘗試使用 x86 架構的 Java 啟動遊戲。\n\n如果你正在使用<b>高通</b>平臺,你可能需要安裝 <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL 相容包</a>後才能進行遊戲。點擊連結前往 Microsoft Store 安裝相容包。
|
||||
# When translating the following string into non-Latin languages, please retain the English translation
|
||||
fatal.wine_warning=HMCL is running under Wine or one of its distributions.\nAs a cross-platform application, HMCL is best experienced natively on your host OS.\nPress "OK" to continue, but please note that some issues may arise.\n\n您目前正在使用 Wine 或其衍生版本執行 HMCL。\nHMCL 是跨平臺的,我們建議您直接在主機系統上執行。\n按下「確定」以繼續,但請注意,此操作可能會引發一些問題。
|
||||
|
||||
file=檔案
|
||||
|
||||
@@ -1224,6 +1226,7 @@ settings.launcher.download.threads.auto=自動選取執行緒數
|
||||
settings.launcher.download.threads.hint=執行緒數過高可能導致系統卡頓。你的下載速度會受到網際網路運營商、下載來源伺服器等方面的影響。調高下載執行緒數不一定能大幅提升總下載速度。
|
||||
settings.launcher.download_source=下載來源
|
||||
settings.launcher.download_source.auto=自動選取下載來源
|
||||
settings.launcher.default_addon_source=遊戲內容預設下載來源
|
||||
settings.launcher.enable_game_list=在首頁內顯示遊戲清單
|
||||
settings.launcher.font=字體
|
||||
settings.launcher.font.anti_aliasing=反鋸齒
|
||||
|
||||
@@ -425,6 +425,8 @@ fatal.unsupported_platform=Minecraft 尚未对你的平台提供完善支持,
|
||||
fatal.unsupported_platform.loongarch=Hello Minecraft! Launcher 已为龙芯提供支持。\n如果遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
fatal.unsupported_platform.macos_arm64=Hello Minecraft! Launcher 已为 Apple Silicon 平台提供支持。使用 ARM 原生 Java 启动游戏以获得更流畅的游戏体验。\n如果你在游戏中遇到问题,使用 x86-64 架构的 Java 启动游戏可能有更好的兼容性。\n如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
fatal.unsupported_platform.windows_arm64=Hello Minecraft! Launcher 已为 Windows on Arm 平台提供原生支持。如果你在游戏中遇到问题,请尝试使用 x86 架构的 Java 启动游戏。\n如果你正在使用 <b>高通</b> 平台,你可能需要安装 <a href="ms-windows-store://pdp/?productid=9NQPSL29BFFF">OpenGL 兼容包</a> 后才能进行游戏。点击链接前往 Microsoft Store 安装兼容包。\n如遇到问题,你可以点击右上角帮助按钮进行求助。
|
||||
# When translating the following string into non-Latin languages, please retain the English translation
|
||||
fatal.wine_warning=HMCL is running under Wine or one of its distributions.\nAs a cross-platform application, HMCL is best experienced natively on your host OS.\nPress "OK" to continue, but please note that some issues may arise.\n\n您目前正在使用 Wine 或其发行版运行 HMCL。\nHMCL 是跨平台的,我们建议您直接在宿主系统上运行。\n按下“确定”以继续,但请注意,此操作可能会引发一些问题。
|
||||
|
||||
file=文件
|
||||
|
||||
@@ -1229,6 +1231,7 @@ settings.launcher.download.threads.auto=自动选择线程数
|
||||
settings.launcher.download.threads.hint=线程数过高可能导致系统卡顿。你的下载速度会受到互联网运营商、下载源服务器等方面的影响。调高下载线程数不一定能大幅提升总下载速度。
|
||||
settings.launcher.download_source=下载源
|
||||
settings.launcher.download_source.auto=自动选择下载源
|
||||
settings.launcher.default_addon_source=游戏内容默认下载源
|
||||
settings.launcher.enable_game_list=在主页内显示版本列表
|
||||
settings.launcher.font=字体
|
||||
settings.launcher.font.anti_aliasing=抗锯齿
|
||||
|
||||
@@ -282,6 +282,25 @@ public enum OperatingSystem {
|
||||
return SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_7);
|
||||
}
|
||||
|
||||
public static boolean isRunningUnderWine() {
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] wineEnvVars = {
|
||||
"WINEPREFIX",
|
||||
"WINELOADER",
|
||||
"WINEDLLPATH"
|
||||
};
|
||||
|
||||
for (String var : wineEnvVars) {
|
||||
if (System.getenv(var) != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Path getWorkingDirectory(String folder) {
|
||||
String home = System.getProperty("user.home", ".");
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
|
||||
@@ -654,6 +654,10 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
|
||||
throw new IllegalArgumentException(value);
|
||||
}
|
||||
|
||||
if (year >= Release.MINIMUM_YEAR_MAJOR_VERSION) {
|
||||
throw new IllegalArgumentException(value);
|
||||
}
|
||||
|
||||
char suffix = value.charAt(5);
|
||||
if (suffix < 'a' || suffix > 'z')
|
||||
throw new IllegalArgumentException(value);
|
||||
|
||||
@@ -912,3 +912,6 @@
|
||||
26.1-rc-1
|
||||
26.1-rc-2
|
||||
26.1
|
||||
26.1.1-rc-1
|
||||
26.1.1
|
||||
26w14a
|
||||
@@ -1,4 +1,4 @@
|
||||
# https://gitlab.freedesktop.org/mesa/libdrm/-/blob/35a21916c8f621b35a67f3c5994280ae59a4981a/data/amdgpu.ids
|
||||
# https://gitlab.freedesktop.org/mesa/libdrm/-/blob/15cdcfe9e1c4b00f2a819bc1f4b3efa1326ac6bc/data/amdgpu.ids
|
||||
#
|
||||
# List of AMDGPU IDs
|
||||
#
|
||||
@@ -10,6 +10,19 @@
|
||||
1114, C3, AMD Radeon 840M Graphics
|
||||
1114, D2, AMD Radeon 860M Graphics
|
||||
1114, D3, AMD Radeon 840M Graphics
|
||||
1114, E2, AMD Radeon 860M Graphics
|
||||
1114, E4, AMD Radeon 860M Graphics
|
||||
1114, E5, AMD Radeon 840M Graphics
|
||||
1114, E9, AMD Radeon 860M Graphics
|
||||
1114, EA, AMD Radeon 840M Graphics
|
||||
1114, ED, AMD Radeon 860M Graphics
|
||||
1114, EE, AMD Radeon 840M Graphics
|
||||
1114, F2, AMD Radeon 860M Graphics
|
||||
1114, F3, AMD Radeon 840M Graphics
|
||||
1114, F9, AMD Radeon 860M Graphics
|
||||
1114, FA, AMD Radeon 840M Graphics
|
||||
1114, FC, AMD Radeon 860M Graphics
|
||||
1114, FD, AMD Radeon 840M Graphics
|
||||
1309, 00, AMD Radeon R7 Graphics
|
||||
130A, 00, AMD Radeon R6 Graphics
|
||||
130B, 00, AMD Radeon R4 Graphics
|
||||
@@ -41,23 +54,35 @@
|
||||
1506, C3, AMD Radeon 610M
|
||||
1506, C4, AMD Radeon 610M
|
||||
150E, C1, AMD Radeon 890M Graphics
|
||||
150E, C4, AMD Radeon 890M Graphics
|
||||
150E, C4, AMD Radeon 880M Graphics
|
||||
150E, C5, AMD Radeon 890M Graphics
|
||||
150E, C6, AMD Radeon 890M Graphics
|
||||
150E, C7, AMD Radeon 890M Graphics
|
||||
150E, D1, AMD Radeon 890M Graphics
|
||||
150E, D2, AMD Radeon 890M Graphics
|
||||
150E, D2, AMD Radeon 880M Graphics
|
||||
150E, D3, AMD Radeon 890M Graphics
|
||||
1586, C1, Radeon 8060S Graphics
|
||||
1586, C2, Radeon 8050S Graphics
|
||||
1586, C4, Radeon 8050S Graphics
|
||||
1586, D1, Radeon 8060S Graphics
|
||||
1586, D2, Radeon 8050S Graphics
|
||||
1586, D4, Radeon 8050S Graphics
|
||||
1586, D5, Radeon 8040S Graphics
|
||||
150E, E1, AMD Radeon 890M Graphics
|
||||
150E, E3, AMD Radeon 890M Graphics
|
||||
150E, E4, AMD Radeon 890M Graphics
|
||||
150E, F1, AMD Radeon 890M Graphics
|
||||
150E, F3, AMD Radeon 890M Graphics
|
||||
1586, C1, AMD Radeon 8060S Graphics
|
||||
1586, C2, AMD Radeon 8050S Graphics
|
||||
1586, C3, AMD Radeon 8060S Graphics
|
||||
1586, C4, AMD Radeon 8050S Graphics
|
||||
1586, C6, AMD Radeon 8060S Graphics
|
||||
1586, D1, AMD Radeon 8060S Graphics
|
||||
1586, D2, AMD Radeon 8050S Graphics
|
||||
1586, D4, AMD Radeon 8050S Graphics
|
||||
1586, D5, AMD Radeon 8040S Graphics
|
||||
15BF, 00, AMD Radeon 780M Graphics
|
||||
15BF, 01, AMD Radeon 760M Graphics
|
||||
15BF, 02, AMD Radeon 780M Graphics
|
||||
15BF, 03, AMD Radeon 760M Graphics
|
||||
15BF, 05, AMD Radeon 760M Graphics
|
||||
15BF, 06, AMD Radeon 780M Graphics
|
||||
15BF, 07, AMD Radeon 740M Graphics
|
||||
15BF, 08, AMD Radeon 740M Graphics
|
||||
15BF, C1, AMD Radeon 780M Graphics
|
||||
15BF, C2, AMD Radeon 780M Graphics
|
||||
15BF, C3, AMD Radeon 760M Graphics
|
||||
@@ -71,6 +96,7 @@
|
||||
15BF, CB, AMD Radeon 760M Graphics
|
||||
15BF, CC, AMD Radeon 740M Graphics
|
||||
15BF, CD, AMD Radeon 760M Graphics
|
||||
15BF, CE, AMD Radeon 740M Graphics
|
||||
15BF, CF, AMD Radeon 780M Graphics
|
||||
15BF, D0, AMD Radeon 780M Graphics
|
||||
15BF, D1, AMD Radeon 780M Graphics
|
||||
@@ -93,10 +119,18 @@
|
||||
15C8, C2, AMD Radeon 740M Graphics
|
||||
15C8, C3, AMD Radeon 740M Graphics
|
||||
15C8, C4, AMD Radeon 740M Graphics
|
||||
15C8, C5, AMD Radeon 740M Graphics
|
||||
15C8, C6, AMD Radeon 740M Graphics
|
||||
15C8, C7, AMD Radeon 740M Graphics
|
||||
15C8, C8, AMD Radeon 740M Graphics
|
||||
15C8, D1, AMD Radeon 740M Graphics
|
||||
15C8, D2, AMD Radeon 740M Graphics
|
||||
15C8, D3, AMD Radeon 740M Graphics
|
||||
15C8, D4, AMD Radeon 740M Graphics
|
||||
15C8, D5, AMD Radeon 740M Graphics
|
||||
15C8, D6, AMD Radeon 740M Graphics
|
||||
15C8, D7, AMD Radeon 740M Graphics
|
||||
15C8, D8, AMD Radeon 740M Graphics
|
||||
15D8, 00, AMD Radeon RX Vega 8 Graphics WS
|
||||
15D8, 91, AMD Radeon Vega 3 Graphics
|
||||
15D8, 91, AMD Ryzen Embedded R1606G with Radeon Vega Gfx
|
||||
@@ -252,6 +286,19 @@
|
||||
1901, D6, AMD Radeon 740M Graphics
|
||||
1901, D7, AMD Radeon 740M Graphics
|
||||
1901, D8, AMD Radeon 740M Graphics
|
||||
1902, C0, AMD Radeon 840M Graphics
|
||||
1902, C1, AMD Radeon 840M Graphics
|
||||
1902, C2, AMD Radeon 820M Graphics
|
||||
1902, C3, AMD Radeon 840M Graphics
|
||||
1902, C6, AMD Radeon 820M Graphics
|
||||
1902, C7, AMD Radeon 840M Graphics
|
||||
1902, C8, AMD Radeon 840M Graphics
|
||||
1902, C9, AMD Radeon 820M Graphics
|
||||
1902, CA, AMD Radeon 840M Graphics
|
||||
1902, D1, AMD Radeon 840M Graphics
|
||||
1902, D3, AMD Radeon 840M Graphics
|
||||
1902, D7, AMD Radeon 840M Graphics
|
||||
1902, D8, AMD Radeon 840M Graphics
|
||||
6600, 00, AMD Radeon HD 8600 / 8700M
|
||||
6600, 81, AMD Radeon R7 M370
|
||||
6601, 00, AMD Radeon HD 8500M / 8700M
|
||||
@@ -579,6 +626,7 @@
|
||||
7480, C3, AMD Radeon RX 7600S
|
||||
7480, C7, AMD Radeon RX 7600M XT
|
||||
7480, CF, AMD Radeon RX 7600
|
||||
7481, C7, AMD Steam Machine
|
||||
7483, CF, AMD Radeon RX 7600M
|
||||
7489, 00, AMD Radeon Pro W7500
|
||||
7499, 00, AMD Radeon Pro W7400
|
||||
@@ -597,12 +645,14 @@
|
||||
7550, C2, AMD Radeon RX 9070 GRE
|
||||
7550, C3, AMD Radeon RX 9070
|
||||
7551, C0, AMD Radeon AI PRO R9700
|
||||
7551, C8, AMD Radeon AI PRO R9600D
|
||||
7590, C0, AMD Radeon RX 9060 XT
|
||||
7590, C1, AMD Radeon RX 9060 XT LP
|
||||
7590, C7, AMD Radeon RX 9060
|
||||
75A0, C0, AMD Instinct MI350X
|
||||
75A3, C0, AMD Instinct MI355X
|
||||
75B0, C0, AMD Instinct MI350X VF
|
||||
75B3, C0, AMD Instinct MI355X VF
|
||||
75A0, 00, AMD Instinct MI350X
|
||||
75A3, 00, AMD Instinct MI355X
|
||||
75B0, 00, AMD Instinct MI350X VF
|
||||
75B3, 00, AMD Instinct MI355X VF
|
||||
9830, 00, AMD Radeon HD 8400 / R3 Series
|
||||
9831, 00, AMD Radeon HD 8400E
|
||||
9832, 00, AMD Radeon HD 8330
|
||||
|
||||
+2
-1
@@ -377,10 +377,11 @@ public final class GameVersionNumberTest {
|
||||
"1.21.11-pre1_unobfuscated",
|
||||
"1.21.11-pre2",
|
||||
"1.21.11-pre2_unobfuscated",
|
||||
"99w99a",
|
||||
"26.1-snapshot-1",
|
||||
"26.1-snapshot-2",
|
||||
"26.1",
|
||||
"26.1.1",
|
||||
"26w14a",
|
||||
"26.2-snapshot-1",
|
||||
"26.2-snapshot-2",
|
||||
"26.2",
|
||||
|
||||
+13
-13
@@ -17,7 +17,7 @@ while the minor version is incremented regularly according to the release schedu
|
||||
|
||||
### Version Types
|
||||
|
||||
HMCL has two types of versions: **Stable** and **Development**.
|
||||
HMCL has two types of versions: **Stable** and **Beta**.
|
||||
|
||||
#### Stable Version
|
||||
|
||||
@@ -27,19 +27,19 @@ New features are merged into the stable version only after thorough testing.
|
||||
The stable version number follows the naming rule `<version branch>.<build number>` (e.g., `3.7.1`).
|
||||
The build number is calculated independently for each version branch.
|
||||
|
||||
#### Development Version
|
||||
#### Beta Version
|
||||
|
||||
The HMCL development version is suitable for users who want to experience new features first.
|
||||
The development version includes the latest features and bug fixes,
|
||||
The HMCL beta version is suitable for users who want to experience new features first.
|
||||
The beta version includes the latest features and bug fixes,
|
||||
but may also have more issues due to insufficient testing.
|
||||
|
||||
The development version follows the naming rule `<version branch>.0.<build number>` (e.g., `3.7.0.300`).
|
||||
The beta version follows the naming rule `<version branch>.0.<build number>` (e.g., `3.7.0.300`).
|
||||
The build number is shared globally across all version branches.
|
||||
|
||||
## Release Channels
|
||||
|
||||
HMCL has two main release channels: **Stable Channel** and **Development Channel**.
|
||||
They are used to release the HMCL stable and development versions, respectively.
|
||||
HMCL has two main release channels: **Stable Channel** and **Beta Channel**.
|
||||
They are used to release the HMCL stable and beta versions, respectively.
|
||||
|
||||
To test HMCL versions before official release, we will push updates to some users in advance.
|
||||
Users can enable the "Preview HMCL releases early" option on the "Settings > General" page to receive preview updates from the corresponding channel.
|
||||
@@ -50,17 +50,17 @@ Users can enable the "Preview HMCL releases early" option on the "Settings > Gen
|
||||
gantt
|
||||
title HMCL Version Lifecycle (Example)
|
||||
section 3.9
|
||||
Development Phase: a1, 2025-11-15, 30d
|
||||
Beta Phase: a1, 2025-11-15, 30d
|
||||
Preview Phase: a2, after a1, 16d
|
||||
Stable Release: milestone, after a2, 0d
|
||||
Maintenance Phase: a3, after a2, 31d
|
||||
section 3.10
|
||||
Development Phase: b1, after a1, 31d
|
||||
Beta Phase: b1, after a1, 31d
|
||||
Preview Phase: b2, after b1, 16d
|
||||
Stable Release: milestone, after b2, 0d
|
||||
Maintenance Phase: b3, after b2, 31d
|
||||
section 3.11
|
||||
Development Phase: c1, after b1, 31d
|
||||
Beta Phase: c1, after b1, 31d
|
||||
Preview Phase: c2, after c1, 16d
|
||||
Stable Release: milestone, after c2, 0d
|
||||
Maintenance Phase: c3, after c2, 30d
|
||||
@@ -71,11 +71,11 @@ and each version branch `x.y` has a lifecycle of two and a half months.
|
||||
|
||||
The lifecycle of each version branch is divided into the following stages:
|
||||
|
||||
1. **Development Phase** (Mid-month M ~ Mid-month M+1)
|
||||
1. **Beta Phase** (Mid-month M ~ Mid-month M+1)
|
||||
|
||||
During this phase, the version is developed in the Git branch `main`.
|
||||
|
||||
Typically, we release a development version `x.y.0.<build number>` based on this branch every week,
|
||||
Typically, we release a beta version `x.y.0.<build number>` based on this branch every week,
|
||||
which includes all features and bug fixes merged during the week.
|
||||
|
||||
2. **Preview Phase** (Mid-month M+1 ~ End of month M+1)
|
||||
@@ -84,7 +84,7 @@ The lifecycle of each version branch is divided into the following stages:
|
||||
which corresponds to the fixed version branch `x.y`.
|
||||
|
||||
At the same time, the version branch corresponding to the `main` branch is incremented to `x.y♯`,
|
||||
entering the development phase of the next version branch.
|
||||
entering the beta phase of the next version branch.
|
||||
|
||||
After the fork, no new features will be added to this version branch;
|
||||
only bug fixes and data updates will be made.
|
||||
|
||||
@@ -5,7 +5,7 @@ kala-compress = "1.27.1-3"
|
||||
simple-png-javafx = "0.3.0"
|
||||
gson = "2.13.2"
|
||||
toml4j = "0.7.2"
|
||||
xz = "1.11"
|
||||
xz = "1.12"
|
||||
lz4 = "1.10.4.1"
|
||||
fx-gson = "5.0.0"
|
||||
constant-pool-scanner = "1.2"
|
||||
@@ -14,11 +14,12 @@ nanohttpd = "2.3.1"
|
||||
jsoup = "1.21.2"
|
||||
chardet = "2.5.0"
|
||||
twelvemonkeys = "3.13.1"
|
||||
fxsvgimage = "1.3"
|
||||
jna = "5.18.1"
|
||||
pci-ids = "0.4.0"
|
||||
java-info = "1.0"
|
||||
authlib-injector = "1.2.7"
|
||||
lwjgl-unsafe-agent = "1.0"
|
||||
lwjgl-unsafe-agent = "2.0"
|
||||
monet-fx = "0.4.0"
|
||||
terracotta = "0.4.2"
|
||||
nayuki-qrcodegen = "1.8.0"
|
||||
@@ -47,6 +48,7 @@ nanohttpd = { module = "org.nanohttpd:nanohttpd", version.ref = "nanohttpd" }
|
||||
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
|
||||
chardet = { module = "org.glavo:chardet", version.ref = "chardet" }
|
||||
twelvemonkeys-imageio-webp = { module = "com.twelvemonkeys.imageio:imageio-webp", version.ref = "twelvemonkeys" }
|
||||
fxsvgimage = { module = "com.github.hervegirod:fxsvgimage", version.ref = "fxsvgimage" }
|
||||
jna = { module = "net.java.dev.jna:jna", version.ref = "jna" }
|
||||
jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jna" }
|
||||
pci-ids = { module = "org.glavo:pci-ids", version.ref = "pci-ids" }
|
||||
|
||||
Reference in new issue
Block a user