Compare commits

..
40 Commits
Author SHA1 Message Date
3gf8jv4dvandGlavo dcfcd1d8b2 Revise zh_TW part 3 (#3358)
* Revise zh_TW part 3

* Update

* Update

* fix typo

* fix

* fix

* oops...

* fix

* fix main page hint

* Update HMCL/src/main/resources/assets/lang/I18N_zh.properties

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-25 02:25:26 +08:00
3gf8jv4dv b2dc4b45c2 Revise zh_TW part 2 (#3357)
* Revise zh_TW part 2

* Update

* fix typo

* fix

* fix

* fix

* fix

When choosing a download source, the commas in the BMCLAPI item are changed to half-width.
2024-10-25 01:47:22 +08:00
0314921728 Revise en_US (#3367)
* Revise en_US

* fix

* fix

* Revise

Co-authored-by: Zkitefly <2573874409@qq.com>

* fix some English grammar

* another English grammar fixes

* fix fix fix...

* Revise

Co-authored-by: Glavo <zjx001202@gmail.com>

* fix

* fix

* fix

* fix

* fix

---------

Co-authored-by: Zkitefly <2573874409@qq.com>
Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-25 01:28:02 +08:00
Glavo 7d339677e4 调整公告中超链接的风格 (#3377)
* 调整 MessageDialogPane 中超链接的风格

* Fix checkstyle

* fix
2024-10-24 21:50:05 +08:00
3gf8jv4dv c9d259d7f1 zh_CN post-fix (#3376)
- When choosing a download source, the commas in the BMCLAPI item are changed to half-width.
- Remove extra line breaks in the home page hint.
2024-10-24 21:32:37 +08:00
Glavo eae2670855 支持在启动器内上传微软账号皮肤 (#3375)
* update

* Fix clearCache
2024-10-24 20:03:21 +08:00
3gf8jv4dvandGlavo e857f19b18 Revise zh_CN part 4 (#3349)
* Revise zh_CN part 4

* Update

* fix some typo

* Revise

Co-authored-by: Glavo <zjx001202@gmail.com>

* Revise

Co-authored-by: Glavo <zjx001202@gmail.com>

* Revise

Co-authored-by: Glavo <zjx001202@gmail.com>

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-24 14:25:37 +08:00
Glavo a11816884c 创建 @FXThread 注解 (#3374) 2024-10-24 01:01:31 +08:00
3gf8jv4dv f686cb0ceb Update versions.txt (#3373) 2024-10-23 23:00:02 +08:00
Glavo 5aac4b82fc 优化 Java 下载功能 (#3371)
* update

* update

* update

* update
2024-10-23 21:49:35 +08:00
Glavo 99c3ef8a57 优化 Java 下载功能 (#3369)
* update

* update

* update
2024-10-20 22:26:38 +08:00
3gf8jv4dv 0a17aad9d6 Revise readme (#3362) 2024-10-20 21:36:01 +08:00
lijiaxuanand3gf8jv4dv 965995ad20 略微修改了一下英文版的readme.md (#3354)
* Updated English version of readme.md

修改了英文版的readme.md,并未增加或删除内容,只是让一些句子更加自然了一些

* Update README.md

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>

---------

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
2024-10-20 21:35:27 +08:00
Glavo a5067cb1dd Bump JavaFX to 17.0.13 (#3364) 2024-10-20 21:33:55 +08:00
3gf8jv4dv 1f9c0736aa Capitalize choose player dialog title text (#3368) 2024-10-20 21:33:35 +08:00
Glavo a27671560d 支持下载 OpenJ9 (#3363)
* 支持下载 OpenJ9

* update
2024-10-19 22:59:40 +08:00
Glavo 5c767deaf3 优化 WorldInfoPage (#3361)
* update

* update

* update
2024-10-19 22:25:47 +08:00
3gf8jv4dv c157d4e0d1 Revise zh_TW part 4 (#3359) 2024-10-19 21:48:30 +08:00
3gf8jv4dv 655f42734a Revise zh_TW part 1 (#3356)
* Revise zh_TW part 1

* Update

* Update

* Update
2024-10-19 21:41:54 +08:00
3gf8jv4dv 55b01ab101 Revise zh_CN part 3 (#3348)
* Revise zh_CN part 3

* Update
2024-10-19 21:35:57 +08:00
3gf8jv4dv c6ed42d3e6 Revise zh_CN part 2 (#3346)
* Revise zh_CN part 2

* Update

* Oops...
2024-10-19 21:35:22 +08:00
0562051989 Fix mod update channel i18n (#3331)
* Fix mod update channel i18n

* Update

Co-authored-by: Burning_TNT <pangyl08@163.com>

* update

---------

Co-authored-by: Burning_TNT <pangyl08@163.com>
Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-19 21:34:11 +08:00
3gf8jv4dvandBurning_TNT f68e056e47 Revise zh_CN part 1 (#3343)
* Revise zh_CN part 1

* Update

Co-authored-by: Burning_TNT <pangyl08@163.com>

---------

Co-authored-by: Burning_TNT <pangyl08@163.com>
2024-10-19 21:28:03 +08:00
Burning_TNTandGlavo 457490276c Update CallerFinder (#3350)
* Update CallerFinder

* Revert "Fix #2687 (#2688)"

This reverts commit 207b4d0033.

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-18 02:17:47 +08:00
Glavo 49ea9e5da0 fix checkstyle (#3353) 2024-10-17 12:48:17 +08:00
Glavo 9f3fb7aff6 修复无法安装 zip 格式的 Java 压缩包的问题 (#3352) 2024-10-17 04:20:09 +08:00
Glavo 1033389c38 Revert "Bump commons-compress to 1.27.1 (#3344)" (#3351)
This reverts commit fab32a6d51.
2024-10-16 22:15:13 +08:00
Glavo 58e32c4459 当整合包没有描述时,隐藏查看整合包描述按钮 (#3347) 2024-10-15 19:56:32 +08:00
Glavo 936285847c Bump Gson to 2.11.0 (#3345) 2024-10-15 19:10:02 +08:00
Glavo fab32a6d51 Bump commons-compress to 1.27.1 (#3344) 2024-10-15 19:05:22 +08:00
Glavo 5b345affec 优化 Java 下载功能 (#3342)
* update

* update
2024-10-15 18:47:57 +08:00
3gf8jv4dv 32c3b694b3 Revise readme and platform docs [skip ci] (#3054)
* Revise [skip ci]

Squash previous commits.

* Revise [skip ci]

Unify badge style.

* Revise [skip ci]

Change the badge style of QQ group

* Revise [skip ci]

- Add Chinese variants (Traditional, Taiwan)
- Compilation requirements for Java bump to 11+
- Various revisions

* Revise [skip ci]

Fix a link

* Revise [skip ci]

Revert a change

* Revise [skip ci]

* Revise [skip ci]

* Revise [skip ci]
2024-10-15 18:47:38 +08:00
3gf8jv4dv 4dce13855a Fix some i18n key mistake for es, ja & ru (#3341) 2024-10-15 18:26:12 +08:00
Burning_TNTandGlavo 1ce9858893 Support #2992: 检查模组更新界面加入全选/取消全选复选框 (#3032)
Support #2992

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-15 04:06:40 +08:00
Glavo 71c9632d61 GitHub Action 使用 Java 8 构建 HMCL (#3340)
* update

* update
2024-10-15 03:13:35 +08:00
3gf8jv4dv cce17edaa6 Capitalize text above the sidebar line (#3336) 2024-10-15 01:24:44 +08:00
3gf8jv4dvandGlavo deeef8f67a Download source and thread setting titles i18n key (#3338)
* Change the download source and thread setting titles to the appropriate i18n key

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-10-15 01:19:11 +08:00
3gf8jv4dv 25c76a748f Fix alignment of host and port in proxy settings (#3332) 2024-10-15 01:06:21 +08:00
3gf8jv4dv 2109df8145 Fix some link (#3334)
* Fix some link

* Revise
2024-10-15 00:58:40 +08:00
3gf8jv4dv ae01a49cc4 Fix some error message wrap (#3333)
* Fix some error message wrap

* Fix checkstyle
2024-10-15 00:57:23 +08:00
49 changed files with 2080 additions and 1718 deletions

No files matched your search

+2 -2
View File
@@ -14,11 +14,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up JDK 11
- name: Set up JDK 8
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '11'
java-version: 8
java-package: 'jdk+fx'
- name: Build with Gradle
run: ./gradlew build --no-daemon
@@ -244,9 +244,9 @@ public final class LauncherHelper {
} else if (ex instanceof PermissionException) {
message = i18n("launch.failed.executable_permission");
} else if (ex instanceof ProcessCreationException) {
message = i18n("launch.failed.creating_process") + ex.getLocalizedMessage();
message = i18n("launch.failed.creating_process") + "\n" + ex.getLocalizedMessage();
} else if (ex instanceof NotDecompressingNativesException) {
message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage();
message = i18n("launch.failed.decompressing_natives") + "\n" + ex.getLocalizedMessage();
} else if (ex instanceof LibraryDownloadException) {
message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n";
if (ex.getCause() instanceof ResponseCodeException) {
@@ -881,8 +881,8 @@ public final class LauncherHelper {
}
private static final String ORACLEJDK_DOWNLOAD_LINK = "https://www.java.com/download";
private static final String OPENJDK_DOWNLOAD_LINK = "https://docs.microsoft.com/java/openjdk/download";
private static final String ORACLEJDK_DOWNLOAD_LINK = "https://www.java.com/download/";
private static final String OPENJDK_DOWNLOAD_LINK = "https://learn.microsoft.com/java/openjdk/download";
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
@@ -23,13 +23,12 @@ import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.beans.value.WritableValue;
import javafx.beans.value.*;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
@@ -54,7 +53,6 @@ import org.glavo.png.PNGWriter;
import org.glavo.png.javafx.PNGJavaFXUtils;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.ResourceNotFoundError;
import org.jackhuang.hmcl.util.StringUtils;
@@ -657,6 +655,61 @@ public final class FXUtils {
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
}
public static void bindAllEnabled(BooleanProperty allEnabled, BooleanProperty... children) {
int itemCount = children.length;
int childSelectedCount = 0;
for (BooleanProperty child : children) {
if (child.get())
childSelectedCount++;
}
allEnabled.set(childSelectedCount == itemCount);
class Listener implements InvalidationListener {
private int childSelectedCount;
private boolean updating = false;
public Listener(int childSelectedCount) {
this.childSelectedCount = childSelectedCount;
}
@Override
public void invalidated(Observable observable) {
if (updating)
return;
updating = true;
try {
boolean value = ((BooleanProperty) observable).get();
if (observable == allEnabled) {
for (BooleanProperty child : children) {
child.setValue(value);
}
childSelectedCount = value ? itemCount : 0;
} else {
if (value)
childSelectedCount++;
else
childSelectedCount--;
allEnabled.set(childSelectedCount == itemCount);
}
} finally {
updating = false;
}
}
}
InvalidationListener listener = new Listener(childSelectedCount);
WeakInvalidationListener weakListener = new WeakInvalidationListener(listener);
allEnabled.addListener(listener);
for (BooleanProperty child : children) {
child.addListener(weakListener);
}
}
public static void setIcon(Stage stage) {
String icon;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
@@ -925,7 +978,7 @@ public final class FXUtils {
Element r = doc.getDocumentElement();
NodeList children = r.getChildNodes();
List<javafx.scene.Node> texts = new ArrayList<>();
List<Node> texts = new ArrayList<>();
for (int i = 0; i < children.getLength(); i++) {
org.w3c.dom.Node node = children.item(i);
@@ -933,8 +986,8 @@ public final class FXUtils {
Element element = (Element) node;
if ("a".equals(element.getTagName())) {
String href = element.getAttribute("href");
JFXHyperlink hyperlink = new JFXHyperlink(element.getTextContent());
hyperlink.setOnAction(e -> {
Text text = new Text(element.getTextContent());
text.setOnMouseClicked(e -> {
String link = href;
try {
link = new URI(href).toASCIIString();
@@ -942,7 +995,10 @@ public final class FXUtils {
}
hyperlinkAction.accept(link);
});
texts.add(hyperlink);
text.setCursor(Cursor.HAND);
text.setUnderline(true);
text.setFill(Color.web("#283593"));
texts.add(text);
} else if ("b".equals(element.getTagName())) {
Text text = new Text(element.getTextContent());
text.getStyleClass().add("bold");
@@ -32,17 +32,14 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CredentialExpiredException;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.util.skin.InvalidSkinException;
import org.jackhuang.hmcl.util.skin.NormalizedSkin;
@@ -128,7 +125,7 @@ public class AccountListItem extends RadioButton {
.orElse(emptySet());
return uploadableTextures.contains(TextureType.SKIN);
}, profile);
} else if (account instanceof OfflineAccount || account instanceof MicrosoftAccount) {
} else if (account instanceof OfflineAccount || account.canUploadSkin()) {
return createBooleanBinding(() -> true);
} else {
return createBooleanBinding(() -> false);
@@ -144,11 +141,7 @@ public class AccountListItem extends RadioButton {
Controllers.dialog(new OfflineAccountSkinPane((OfflineAccount) account));
return null;
}
if (account instanceof MicrosoftAccount) {
FXUtils.openLink("https://www.minecraft.net/msaprofile/mygames/editskin");
return null;
}
if (!(account instanceof YggdrasilAccount)) {
if (!account.canUploadSkin()) {
return null;
}
@@ -174,7 +167,7 @@ public class AccountListItem extends RadioButton {
NormalizedSkin skin = new NormalizedSkin(skinImg);
String model = skin.isSlim() ? "slim" : "";
LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]");
((YggdrasilAccount) account).uploadSkin(model, selectedFile.toPath());
account.uploadSkin(skin.isSlim(), selectedFile.toPath());
})
.thenComposeAsync(refreshAsync())
.whenComplete(Schedulers.javafx(), e -> {
@@ -44,6 +44,7 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import java.net.URI;
import java.util.Locale;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -95,7 +96,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
VBox boxMethods = new VBox();
{
boxMethods.getStyleClass().add("advanced-list-box-content");
boxMethods.getChildren().add(new ClassTitle(i18n("account.create")));
boxMethods.getChildren().add(new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT)));
FXUtils.setLimitWidth(boxMethods, 200);
AdvancedListItem offlineItem = new AdvancedListItem();
@@ -65,6 +65,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
@@ -637,7 +638,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT);
cancel.setOnAction(e -> latch.countDown());
listBox.startCategory(i18n("account.choose"));
listBox.startCategory(i18n("account.choose").toUpperCase(Locale.ROOT));
setCenter(listBox);
@@ -55,6 +55,7 @@ import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Locale;
import java.util.concurrent.CancellationException;
import java.util.function.Supplier;
@@ -101,7 +102,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
{
AdvancedListBox sideBar = new AdvancedListBox()
.startCategory(i18n("download.game"))
.startCategory(i18n("download.game").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("game"));
item.setLeftGraphic(wrap(SVG.GAMEPAD));
@@ -114,7 +115,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
settingsItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(modpackTab));
settingsItem.setOnAction(e -> tab.select(modpackTab));
})
.startCategory(i18n("download.content"))
.startCategory(i18n("download.content").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(item -> {
item.setTitle(i18n("mods"));
item.setLeftGraphic(wrap(SVG.PUZZLE));
@@ -36,6 +36,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -79,6 +80,8 @@ public final class LocalModpackPage extends ModpackPage {
});
}
btnDescription.setVisible(false);
File selectedFile;
Optional<File> filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class);
if (filePath.isPresent()) {
@@ -134,6 +137,8 @@ public final class LocalModpackPage extends ModpackPage {
// trim: https://github.com/HMCL-dev/HMCL/issues/962
txtModpackName.setText(manifest.getName().trim());
}
btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription()));
}
}).start();
}
@@ -24,6 +24,7 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage {
protected final Label lblAuthor;
protected final JFXTextField txtModpackName;
protected final JFXButton btnInstall;
protected final JFXButton btnDescription;
protected ModpackPage(WizardController controller) {
this.controller = controller;
@@ -77,7 +78,7 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage {
BorderPane descriptionPane = new BorderPane();
{
JFXButton btnDescription = new JFXButton(i18n("modpack.description"));
btnDescription = new JFXButton(i18n("modpack.description"));
btnDescription.getStyleClass().add("jfx-button-border");
btnDescription.setOnAction(e -> onDescribe());
descriptionPane.setLeft(btnDescription);
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.IOException;
import java.util.Map;
@@ -69,6 +70,8 @@ public final class RemoteModpackPage extends ModpackPage {
new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().versionIdConflicts(str)),
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId));
}
btnDescription.setVisible(StringUtils.isNotBlank(manifest.getDescription()));
}
@Override
@@ -18,7 +18,6 @@
package org.jackhuang.hmcl.ui.main;
import com.jfoenix.controls.*;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
@@ -100,7 +99,7 @@ public class DownloadSettingsPage extends StackPane {
downloadSource.getChildren().setAll(chooseWrapper, versionListSourcePane, downloadSourcePane);
}
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.version_list_source")), downloadSource);
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.download_source")), downloadSource);
}
{
@@ -204,7 +203,6 @@ public class DownloadSettingsPage extends StackPane {
Label host = new Label(i18n("settings.launcher.proxy.host"));
GridPane.setRowIndex(host, 1);
GridPane.setColumnIndex(host, 0);
GridPane.setHalignment(host, HPos.RIGHT);
gridPane.getChildren().add(host);
}
@@ -220,7 +218,6 @@ public class DownloadSettingsPage extends StackPane {
Label port = new Label(i18n("settings.launcher.proxy.port"));
GridPane.setRowIndex(port, 2);
GridPane.setColumnIndex(port, 0);
GridPane.setHalignment(port, HPos.RIGHT);
gridPane.getChildren().add(port);
}
@@ -21,7 +21,6 @@ import com.jfoenix.controls.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
@@ -46,7 +45,6 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.DialogPane;
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -56,6 +54,7 @@ import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.function.Consumer;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.Lang.resolveException;
@@ -118,6 +117,8 @@ public final class JavaDownloadDialog extends StackPane {
button.setUserData(version);
vbox.getChildren().add(button);
toggleGroup.getToggles().add(button);
if (JavaManager.REPOSITORY.isInstalled(platform, version))
button.setDisable(true);
}
setBody(vbox);
@@ -166,19 +167,6 @@ public final class JavaDownloadDialog extends StackPane {
}
private final class DownloadDiscoJava extends JFXDialogLayout {
private boolean isLTS(int major) {
if (major <= 8) {
return true;
}
if (major < 21) {
return major == 11 || major == 17;
}
return major % 4 == 1;
}
private final JFXComboBox<DiscoJavaDistribution> distributionBox;
private final JFXComboBox<DiscoJavaRemoteVersion> remoteVersionBox;
private final JFXComboBox<JavaPackageType> packageTypeBox;
@@ -189,11 +177,8 @@ public final class JavaDownloadDialog extends StackPane {
private final DownloadProvider downloadProvider = DownloadProviders.getDownloadProvider();
private final ObjectProperty<DiscoJavaVersionList> currentDiscoJavaVersionList = new SimpleObjectProperty<>();
private final Map<Pair<DiscoJavaDistribution, JavaPackageType>, DiscoJavaVersionList> javaVersionLists = new HashMap<>();
private boolean changingDistribution = false;
private final Map<DiscoJavaDistribution, DiscoJavaVersionList> javaVersionLists = new HashMap<>();
private final ObjectProperty<DiscoJavaVersionList> currentJavaVersionList = new SimpleObjectProperty<>();
DownloadDiscoJava() {
assert !distributions.isEmpty();
@@ -204,7 +189,7 @@ public final class JavaDownloadDialog extends StackPane {
this.remoteVersionBox = new JFXComboBox<>();
this.remoteVersionBox.setConverter(FXUtils.stringConverter(JavaRemoteVersion::getDistributionVersion));
this.packageTypeBox = new JFXComboBox<>();
this.packageTypeBox = new JFXComboBox<>(FXCollections.observableArrayList());
this.downloadButton = new JFXButton(i18n("download"));
downloadButton.setOnAction(e -> onDownload());
@@ -227,66 +212,91 @@ public final class JavaDownloadDialog extends StackPane {
body.addRow(2, new Label(i18n("java.download.packageType")), packageTypeBox);
distributionBox.setItems(FXCollections.observableList(new ArrayList<>(distributions)));
ChangeListener<DiscoJavaVersionList.Status> updateStatusListener = (observable, oldValue, newValue) -> updateStatus(newValue);
this.currentDiscoJavaVersionList.addListener((observable, oldValue, newValue) -> {
if (oldValue != null) {
oldValue.status.removeListener(updateStatusListener);
FXUtils.onChange(packageTypeBox.getSelectionModel().selectedItemProperty(), packageType -> {
ObservableList<DiscoJavaRemoteVersion> versions;
if (packageType == null
|| currentJavaVersionList.get() == null
|| (versions = currentJavaVersionList.get().versions.get(packageType)) == null) {
remoteVersionBox.setItems(null);
return;
}
remoteVersionBox.setItems(versions);
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (version.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
remoteVersionBox.getSelectionModel().select(i);
return;
}
}
for (int i = 0; i < versions.size(); i++) {
DiscoJavaRemoteVersion version = versions.get(i);
if (version.isLTS()) {
remoteVersionBox.getSelectionModel().select(i);
return;
}
}
remoteVersionBox.getSelectionModel().selectFirst();
});
Consumer<DiscoJavaVersionList> updateListStatus = list -> {
remoteVersionBox.setItems(null);
packageTypeBox.getItems().clear();
remoteVersionBox.setDisable(true);
packageTypeBox.setDisable(true);
warningLabel.setText(null);
if (list == null || (list.versions != null && list.versions.isEmpty()))
downloadButtonPane.getChildren().setAll(downloadButton);
else if (list.status == DiscoJavaVersionList.Status.LOADING)
downloadButtonPane.getChildren().setAll(new JFXSpinner());
else {
downloadButtonPane.getChildren().setAll(downloadButton);
if (list.status == DiscoJavaVersionList.Status.SUCCESS) {
packageTypeBox.getItems().setAll(list.versions.keySet());
packageTypeBox.getSelectionModel().selectFirst();
remoteVersionBox.setDisable(false);
packageTypeBox.setDisable(false);
} else
warningLabel.setText(i18n("java.download.load_list.failed"));
}
};
currentJavaVersionList.addListener((observable, oldValue, newValue) -> {
if (oldValue != null)
oldValue.listener = null;
if (newValue != null) {
newValue.status.addListener(updateStatusListener);
updateStatus(newValue.status.get());
updateListStatus.accept(newValue);
if (newValue.status == DiscoJavaVersionList.Status.LOADING)
newValue.listener = updateListStatus;
} else {
updateStatus(null);
currentJavaVersionList.set(null);
updateListStatus.accept(null);
}
});
packageTypeBox.getSelectionModel().selectedItemProperty().addListener(ignored -> updateVersions());
FXUtils.onChangeAndOperate(distributionBox.getSelectionModel().selectedItemProperty(), distribution -> {
if (distribution != null) {
changingDistribution = true;
packageTypeBox.setItems(FXCollections.observableList(new ArrayList<>(distribution.getSupportedPackageTypes())));
packageTypeBox.getSelectionModel().select(0);
changingDistribution = false;
updateVersions();
packageTypeBox.setDisable(false);
remoteVersionBox.setDisable(false);
} else {
packageTypeBox.setItems(null);
updateVersions();
remoteVersionBox.setItems(null);
packageTypeBox.setDisable(true);
remoteVersionBox.setDisable(true);
}
});
FXUtils.onChange(distributionBox.getSelectionModel().selectedItemProperty(),
it -> currentJavaVersionList.set(getJavaVersionList(it)));
setHeading(new Label(i18n("java.download")));
setBody(body);
setActions(warningLabel, downloadButtonPane, cancelButton);
}
private void updateStatus(DiscoJavaVersionList.Status status) {
if (status == DiscoJavaVersionList.Status.LOADING) {
downloadButtonPane.getChildren().setAll(new JFXSpinner());
remoteVersionBox.setDisable(true);
warningLabel.setText(null);
} else {
downloadButtonPane.getChildren().setAll(downloadButton);
if (status == DiscoJavaVersionList.Status.SUCCESS || status == null) {
remoteVersionBox.setDisable(false);
warningLabel.setText(null);
} else if (status == DiscoJavaVersionList.Status.FAILED) {
remoteVersionBox.setDisable(true);
warningLabel.setText(i18n("java.download.load_list.failed"));
}
}
}
private void onDownload() {
fireEvent(new DialogCloseEvent());
DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem();
DiscoJavaRemoteVersion version = remoteVersionBox.getSelectionModel().getSelectedItem();
JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem();
if (version == null)
return;
@@ -312,8 +322,15 @@ public final class JavaDownloadDialog extends StackPane {
getIntegrityCheck = Task.completed(new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), fileInfo.getChecksum()));
else if (StringUtils.isNotBlank(fileInfo.getChecksumUri()))
getIntegrityCheck = new GetTask(downloadProvider.injectURLWithCandidates(fileInfo.getChecksumUri()))
.thenApplyAsync(checksum ->
new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), checksum.trim()));
.thenApplyAsync(checksum -> {
checksum = checksum.trim();
int idx = checksum.indexOf(' ');
if (idx > 0)
checksum = checksum.substring(0, idx);
return new FileDownloadTask.IntegrityCheck(fileInfo.getChecksumType(), checksum);
});
else
throw new IOException("Unable to get checksum for file");
@@ -336,7 +353,7 @@ public final class JavaDownloadDialog extends StackPane {
if (idx > 0) {
javaVersion = javaVersion.substring(0, idx);
}
String defaultName = distribution.getApiParameter() + "-" + javaVersion;
String defaultName = distribution.getApiParameter() + "-" + javaVersion + "-" + packageType.name().toLowerCase(Locale.ROOT);
Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller ->
new JavaInstallPage(controller::onFinish, info, version, updateInfo, defaultName, result)));
} else {
@@ -350,65 +367,39 @@ public final class JavaDownloadDialog extends StackPane {
}
private void updateVersions() {
if (changingDistribution) return;
private DiscoJavaVersionList getJavaVersionList(DiscoJavaDistribution distribution) {
if (distribution == null)
return null;
return javaVersionLists.computeIfAbsent(distribution, it -> {
DiscoJavaVersionList versionList = new DiscoJavaVersionList(it);
new DiscoFetchJavaListTask(downloadProvider, it, platform).setExecutor(Schedulers.io()).thenApplyAsync(versions -> {
EnumMap<JavaPackageType, ObservableList<DiscoJavaRemoteVersion>> result = new EnumMap<>(JavaPackageType.class);
if (versions.isEmpty())
return result;
DiscoJavaDistribution distribution = distributionBox.getSelectionModel().getSelectedItem();
if (distribution == null) {
this.currentDiscoJavaVersionList.set(null);
return;
}
for (Map.Entry<JavaPackageType, TreeMap<Integer, DiscoJavaRemoteVersion>> entry : versions.entrySet())
for (DiscoJavaRemoteVersion version : entry.getValue().values())
if (version.isLTS()
|| version.getJdkVersion() == entry.getValue().lastKey() // latest version
|| version.getJdkVersion() == 16)
result.computeIfAbsent(entry.getKey(), ignored -> FXCollections.observableArrayList())
.add(version);
JavaPackageType packageType = packageTypeBox.getSelectionModel().getSelectedItem();
DiscoJavaVersionList list = javaVersionLists.computeIfAbsent(Pair.pair(distribution, packageType), pair -> {
DiscoJavaVersionList res = new DiscoJavaVersionList();
new DiscoFetchJavaListTask(downloadProvider, distribution, platform, packageType).setExecutor(Schedulers.io()).thenApplyAsync(versions -> {
if (versions.isEmpty()) return Collections.<DiscoJavaRemoteVersion>emptyList();
int lastLTS = -1;
for (int v : versions.keySet()) {
if (isLTS(v)) {
lastLTS = v;
}
}
ArrayList<DiscoJavaRemoteVersion> remoteVersions = new ArrayList<>();
for (Map.Entry<Integer, DiscoJavaRemoteVersion> entry : versions.entrySet()) {
int v = entry.getKey();
if (v >= lastLTS || isLTS(v) || v == 16) {
remoteVersions.add(entry.getValue());
}
}
Collections.reverse(remoteVersions);
return remoteVersions;
for (List<DiscoJavaRemoteVersion> l : result.values())
Collections.reverse(l);
return result;
}).whenComplete(Schedulers.javafx(), ((result, exception) -> {
if (exception == null) {
res.status.set(DiscoJavaVersionList.Status.SUCCESS);
res.versions.setAll(result);
selectLTS(res);
versionList.status = DiscoJavaVersionList.Status.SUCCESS;
versionList.versions = result;
} else {
LOG.warning("Failed to load java list", exception);
res.status.set(DiscoJavaVersionList.Status.FAILED);
versionList.status = DiscoJavaVersionList.Status.FAILED;
}
versionList.invalidate();
})).start();
return res;
return versionList;
});
this.currentDiscoJavaVersionList.set(list);
this.remoteVersionBox.setItems(list.versions);
selectLTS(list);
}
private void selectLTS(DiscoJavaVersionList list) {
if (remoteVersionBox.getItems() == list.versions) {
for (int i = 0; i < list.versions.size(); i++) {
JavaRemoteVersion item = list.versions.get(i);
if (item.getJdkVersion() == GameJavaVersion.LATEST.getMajorVersion()) {
remoteVersionBox.getSelectionModel().select(i);
break;
}
}
}
}
}
@@ -417,7 +408,19 @@ public final class JavaDownloadDialog extends StackPane {
LOADING, SUCCESS, FAILED
}
final ObservableList<DiscoJavaRemoteVersion> versions = FXCollections.observableArrayList();
final ObjectProperty<Status> status = new SimpleObjectProperty<>(Status.LOADING);
final DiscoJavaDistribution distribution;
Status status = Status.LOADING;
EnumMap<JavaPackageType, ObservableList<DiscoJavaRemoteVersion>> versions;
Consumer<DiscoJavaVersionList> listener;
DiscoJavaVersionList(DiscoJavaDistribution distribution) {
this.distribution = distribution;
}
void invalidate() {
if (listener != null)
listener.accept(this);
}
}
}
@@ -50,7 +50,6 @@ import org.jackhuang.hmcl.util.tree.ArchiveFileTree;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.Platform;
import org.jackhuang.hmcl.util.tree.TarFileTree;
import java.io.*;
import java.nio.file.Files;
@@ -151,7 +150,7 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
private void onInstallArchive(Path file) {
Task.supplyAsync(() -> {
try (ArchiveFileTree<?, ?> tree = TarFileTree.open(file)) {
try (ArchiveFileTree<?, ?> tree = ArchiveFileTree.open(file)) {
JavaInfo info = JavaInfo.fromArchive(tree);
if (!JavaManager.isCompatible(info.getPlatform()))
@@ -33,6 +33,8 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.versions.VersionSettingsPage;
import java.util.Locale;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -83,7 +85,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
javaItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(javaManagementTab));
javaItem.setOnAction(e -> tab.select(javaManagementTab));
})
.startCategory(i18n("launcher"))
.startCategory(i18n("launcher").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(settingsItem -> {
settingsItem.setTitle(i18n("settings.launcher.general"));
settingsItem.setLeftGraphic(wrap(SVG.APPLICATION_OUTLINE));
@@ -102,7 +104,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
downloadItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(downloadTab));
downloadItem.setOnAction(e -> tab.select(downloadTab));
})
.startCategory(i18n("help"))
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
.addNavigationDrawerItem(helpItem -> {
helpItem.setTitle(i18n("help"));
helpItem.setLeftGraphic(wrap(SVG.HELP_CIRCLE_OUTLINE));
@@ -388,15 +388,15 @@ public class DownloadPage extends Control implements DecoratorPage {
switch (dataItem.getVersionType()) {
case Alpha:
content.getTags().add(i18n("version.game.snapshot"));
content.getTags().add(i18n("mods.channel.alpha"));
graphicPane.getChildren().setAll(SVG.ALPHA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
break;
case Beta:
content.getTags().add(i18n("version.game.snapshot"));
content.getTags().add(i18n("mods.channel.beta"));
graphicPane.getChildren().setAll(SVG.BETA_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
break;
case Release:
content.getTags().add(i18n("version.game.release"));
content.getTags().add(i18n("mods.channel.release"));
graphicPane.getChildren().setAll(SVG.RELEASE_CIRCLE_OUTLINE.createIcon(Theme.blackFill(), 24, 24));
break;
}
@@ -24,6 +24,7 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
@@ -73,6 +74,8 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
getStyleClass().add("gray-background");
TableColumn<ModUpdateObject, Boolean> enabledColumn = new TableColumn<>();
CheckBox allEnabledBox = new CheckBox();
enabledColumn.setGraphic(allEnabledBox);
enabledColumn.setCellFactory(CheckBoxTableCell.forTableColumn(enabledColumn));
setupCellValueFactory(enabledColumn, ModUpdateObject::enabledProperty);
enabledColumn.setEditable(true);
@@ -95,6 +98,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
setupCellValueFactory(sourceColumn, ModUpdateObject::sourceProperty);
objects = FXCollections.observableList(updates.stream().map(ModUpdateObject::new).collect(Collectors.toList()));
FXUtils.bindAllEnabled(allEnabledBox.selectedProperty(), objects.stream().map(o -> o.enabled).toArray(BooleanProperty[]::new));
TableView<ModUpdateObject> table = new TableView<>(objects);
table.setEditable(true);
@@ -170,7 +174,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
csvTable.write(Files.newOutputStream(path));
FXUtils.showFileInExplorer(path);
}).whenComplete(Schedulers.javafx() ,exception -> {
}).whenComplete(Schedulers.javafx(), exception -> {
if (exception == null) {
Controllers.dialog(path.toString(), i18n("message.success"));
} else {
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2024 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.github.steveice10.opennbt.tag.builtin.*;
@@ -10,11 +27,14 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.DoubleValidator;
@@ -32,22 +52,36 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
* @author Glavo
*/
public final class WorldInfoPage extends StackPane implements DecoratorPage {
private final World world;
private final CompoundTag levelDat;
private final CompoundTag dataTag;
private CompoundTag levelDat;
private final ObjectProperty<State> stateProperty = new SimpleObjectProperty<>();
private final ObjectProperty<State> stateProperty;
public WorldInfoPage(World world) throws IOException {
public WorldInfoPage(World world) {
this.world = world;
this.levelDat = world.readLevelDat();
this.dataTag = levelDat.get("Data");
this.stateProperty = new SimpleObjectProperty<>(State.fromTitle(i18n("world.info.title", world.getWorldName())));
this.getChildren().add(new ProgressIndicator());
Task.supplyAsync(world::readLevelDat)
.whenComplete(Schedulers.javafx(), ((result, exception) -> {
if (exception == null) {
this.levelDat = result;
loadWorldInfo();
} else {
LOG.warning("Failed to load level.dat", exception);
this.getChildren().setAll(new Label(i18n("world.info.failed")));
}
})).start();
}
private void loadWorldInfo() {
CompoundTag dataTag = levelDat.get("Data");
CompoundTag worldGenSettings = dataTag.get("WorldGenSettings");
stateProperty.set(State.fromTitle(i18n("world.info.title", world.getWorldName())));
ScrollPane scrollPane = new ScrollPane();
scrollPane.setFitToHeight(true);
scrollPane.setFitToWidth(true);
@@ -483,7 +517,6 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
Tag z = listTag.get(2);
if (x instanceof DoubleTag && y instanceof DoubleTag && z instanceof DoubleTag) {
//noinspection MalformedFormatString
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());
@@ -102,10 +102,6 @@ public class WorldListItem extends Control {
}
public void showInfo() {
try {
Controllers.navigate(new WorldInfoPage(world));
} catch (Exception e) {
// TODO
}
Controllers.navigate(new WorldInfoPage(world));
}
}
@@ -9,13 +9,13 @@ import java.util.stream.Stream;
* @author Glavo
*/
final class CallerFinder {
private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
private static final String PACKAGE_PREFIX = CallerFinder.class.getPackageName() + ".";
private static final Predicate<StackWalker.StackFrame> PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX);
private static final Function<Stream<StackWalker.StackFrame>, Optional<StackWalker.StackFrame>> FUNCTION = stream -> stream.filter(PREDICATE).findFirst();
private static final Function<StackWalker.StackFrame, String> FRAME_MAPPING = frame -> frame.getClassName() + "." + frame.getMethodName();
static String getCaller() {
return WALKER.walk(FUNCTION).map(it -> it.getClassName() + "." + it.getMethodName()).orElse(null);
return StackWalker.getInstance().walk(FUNCTION).map(FRAME_MAPPING).orElse(null);
}
private CallerFinder() {
File diff suppressed because it is too large. Load diff
@@ -153,7 +153,7 @@ button.edit=Editar
button.install=Instalar
button.export=Exportar
button.no=No
botón.ok=Aceptar
button.ok=Aceptar
button.refresh=Refrescar
button.remove=Eliminar
button.remove.confirm=¿Está seguro de querer eliminarlo definitivamente? Esta acción no se puede deshacer.
@@ -517,12 +517,12 @@ game.crash.reason.shaders_mod=El juego actual no puede seguir ejecutándose porq
game.crash.reason.rtss_forest_sodium=El juego actual se bloquea debido a una incompatibilidad entre RivaTuner Statistics Server (RTSS) y Sodium, lo que provoca el fallo del juego. Haz clic <a href="https://github.com/CaffeineMC/sodium-fabric/wiki/Known-Issues#rtss-incompatible">aquí</a> para obtener más detalles.
game.crash.reason.stacktrace=El motivo del fallo es desconocido. Puede ver los detalles haciendo clic en el botón "Registros".\n\
\n\
Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo..\n\
Hay algunas palabras clave que pueden contener algunos Mod IDs. Puedes buscarlas en Internet para averiguar el problema por ti mismo.\n\
\n\
%s
game.crash.reason.too_old_java=El juego se ha bloqueado porque estás utilizando una versión histórica de Java VM..\n\
\n\
Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde <a href="https://docs.microsoft.com/java/openjdk/download">here</a>.
Tienes que cambiar a una versión más reciente (%1$s) de Java en la configuración del juego y luego volver a ejecutar el juego. Puedes descargar Java desde <a href="https://learn.microsoft.com/java/openjdk/download">here</a>.
game.crash.reason.unknown=No somos capaces de averiguar por qué el juego se estrelló, por favor refiérase a los registros del juego.\n\
\n\
<b>Cuando pidas ayuda a otra persona, por favor, comparte con ella el registro completo del juego y el archivo de informe de fallos relacionado.</b>\n\
@@ -128,9 +128,9 @@ archive.date=公開日
archive.file.name=名前
archive.version=バージョン
Assets.download=アセットのダウンロード
Assets.download_all=アセットの整合性チェック
Assets.index.malformed=アセットインデックスの形式が正しくありません。バージョン設定の[ゲームアセットファイルの更新]で再試行できます。
assets.download=アセットのダウンロード
assets.download_all=アセットの整合性チェック
assets.index.malformed=アセットインデックスの形式が正しくありません。バージョン設定の[ゲームアセットファイルの更新]で再試行できます。
button.cancel=キャンセル
button.change_source=ダウンロードソースの変更
@@ -152,8 +152,8 @@ button.yes=はい
color.recent=推奨
color.custom=カスタムカラー
crack.NoClassDefFound=「HelloMinecraft!Launcher」ソフトウェアが破損していないことを確認してください。
crack.user_fault=OSまたはJava環境が正しくインストールされていない可能性があり、クラッシュする可能性があります。Javaランタイム環境またはコンピュータを確認してください。
crash.NoClassDefFound=「HelloMinecraft!Launcher」ソフトウェアが破損していないことを確認してください。
crash.user_fault=OSまたはJava環境が正しくインストールされていない可能性があり、クラッシュする可能性があります。Javaランタイム環境またはコンピュータを確認してください。
curse.category.0=すべて
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -30,25 +30,25 @@
"module": "javafx.base",
"groupId": "org.openjfx",
"artifactId": "javafx-base",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "win",
"sha1": "f33d0776b2b027c2ea9267c184f9048ef94c4deb"
"sha1": "ae86377359860449040372d3693bccf046148513"
},
{
"module": "javafx.graphics",
"groupId": "org.openjfx",
"artifactId": "javafx-graphics",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "win",
"sha1": "951ca77bd50ef4fd44f4b7fb1e38301088fde4e2"
"sha1": "bc0055a059b201d6c0be2293ccf72b6d2874fd1c"
},
{
"module": "javafx.controls",
"groupId": "org.openjfx",
"artifactId": "javafx-controls",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "win",
"sha1": "78ac7de48d0aac48e7d5282efc81bab7f337b587"
"sha1": "bcd765dca36fa9e5fb4c997491a2d908947603f5"
}
],
"windows-arm64": [
@@ -82,25 +82,25 @@
"module": "javafx.base",
"groupId": "org.openjfx",
"artifactId": "javafx-base",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "mac",
"sha1": "73e422d8426aaa23e8c712b9f4d9bebf70d3bfb9"
"sha1": "e1744403a6691aaa6b65a7e9b4ff9aad98831bb1"
},
{
"module": "javafx.graphics",
"groupId": "org.openjfx",
"artifactId": "javafx-graphics",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "mac",
"sha1": "6ab6f3e23421fcfa04e692d9d26a8f55a830c114"
"sha1": "dbe733470094abee56560067d0fea3ad24991d2b"
},
{
"module": "javafx.controls",
"groupId": "org.openjfx",
"artifactId": "javafx-controls",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "mac",
"sha1": "b7786b1b63e741c0e234829825fae5fef9d96c31"
"sha1": "0d43a776d6c9e2622e3581ed04501e2cadcd6e65"
}
],
"osx-arm64": [
@@ -108,25 +108,25 @@
"module": "javafx.base",
"groupId": "org.openjfx",
"artifactId": "javafx-base",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "mac-aarch64",
"sha1": "1d0d887c492330ed527b0614d115a4f32d2d05a4"
"sha1": "d4dc7d2864c07b56a27e39fa5691856ddccf0ba4"
},
{
"module": "javafx.graphics",
"groupId": "org.openjfx",
"artifactId": "javafx-graphics",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "mac-aarch64",
"sha1": "64db28799e61e0f8f51e471c732599b2a6961803"
"sha1": "d6a49f19e90db56c134757d1bbab4935de075d45"
},
{
"module": "javafx.controls",
"groupId": "org.openjfx",
"artifactId": "javafx-controls",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "mac-aarch64",
"sha1": "3a14bd5f3ebe45d344c1f2bade0fe074e6ea2a83"
"sha1": "ad564497edc49f7a6135c3b52d360962e19a2539"
}
],
"linux-x86_64": [
@@ -134,25 +134,25 @@
"module": "javafx.base",
"groupId": "org.openjfx",
"artifactId": "javafx-base",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "linux",
"sha1": "9e4ca5ce2b87c479d2cc445ff411a48aeae76936"
"sha1": "875f02b498b00c9692f6ed0af3da373b1d07bf03"
},
{
"module": "javafx.graphics",
"groupId": "org.openjfx",
"artifactId": "javafx-graphics",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "linux",
"sha1": "27c64e4e1569ddf198f28eda84cf8d014ab80693"
"sha1": "103f3bba5a177ba8912a92ef2ee0855de7ba050c"
},
{
"module": "javafx.controls",
"groupId": "org.openjfx",
"artifactId": "javafx-controls",
"version": "19.0.2.1",
"version": "17.0.13",
"classifier": "linux",
"sha1": "b5b2a8ead40bd43476740aa51697bb1cac23de5c"
"sha1": "940e9e05a974a3e484d5ec241d88a0c77ad013f2"
}
],
"linux-arm32": [
+7 -1
View File
@@ -4,7 +4,7 @@ plugins {
dependencies {
api("org.glavo:simple-png-javafx:0.3.0")
api("com.google.code.gson:gson:2.10.1")
api("com.google.code.gson:gson:2.11.0")
api("com.moandjiezana.toml:toml4j:0.7.2")
api("org.tukaani:xz:1.9")
api("org.hildan.fxgson:fx-gson:5.0.0")
@@ -14,4 +14,10 @@ dependencies {
api("org.apache.commons:commons-compress:1.25.0")
api("org.jsoup:jsoup:1.18.1")
compileOnlyApi("org.jetbrains:annotations:24.1.0")
if (JavaVersion.current().isJava8) {
org.gradle.internal.jvm.Jvm.current().toolsJar?.let {
compileOnly(files(it))
}
}
}
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import java.nio.file.Path;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -69,6 +70,14 @@ public abstract class Account implements Observable {
*/
public abstract AuthInfo playOffline() throws AuthenticationException;
public boolean canUploadSkin() {
return false;
}
public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
throw new UnsupportedOperationException("Unsupported Operation");
}
public abstract Map<Object, Object> toStorage();
public void clearCache() {
@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import java.nio.file.Path;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -32,7 +33,7 @@ import java.util.UUID;
import static java.util.Objects.requireNonNull;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class MicrosoftAccount extends OAuthAccount {
public final class MicrosoftAccount extends OAuthAccount {
protected final MicrosoftService service;
protected UUID characterUUID;
@@ -125,6 +126,16 @@ public class MicrosoftAccount extends OAuthAccount {
return session.toAuthInfo();
}
@Override
public boolean canUploadSkin() {
return true;
}
@Override
public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
service.uploadSkin(session.getAccessToken(), isSlim, file);
}
@Override
public Map<Object, Object> toStorage() {
return session.toStorage();
@@ -150,6 +161,7 @@ public class MicrosoftAccount extends OAuthAccount {
@Override
public void clearCache() {
authenticated = false;
service.getProfileRepository().invalidate(characterUUID);
}
@Override
@@ -25,19 +25,18 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.OAuth;
import org.jackhuang.hmcl.auth.ServerDisconnectException;
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
import org.jackhuang.hmcl.auth.yggdrasil.*;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.*;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.io.ResponseCodeException;
import org.jackhuang.hmcl.util.io.*;
import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -261,6 +260,33 @@ public class MicrosoftService {
return Optional.ofNullable(GSON.fromJson(request(NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class));
}
public void uploadSkin(String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
try {
HttpURLConnection con = NetworkUtils.createHttpConnection(NetworkUtils.toURL("https://api.minecraftservices.com/minecraft/profile/skins"));
con.setRequestMethod("POST");
con.setRequestProperty("Authorization", "Bearer " + accessToken);
con.setDoOutput(true);
try (HttpMultipartRequest request = new HttpMultipartRequest(con)) {
request.param("variant", isSlim ? "slim" : "classic");
try (InputStream fis = Files.newInputStream(file)) {
request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis);
}
}
String response = NetworkUtils.readData(con);
if (StringUtils.isBlank(response)) {
if (con.getResponseCode() / 100 != 2)
throw new ResponseCodeException(con.getURL(), con.getResponseCode());
} else {
MinecraftErrorResponse profileResponse = GSON.fromJson(response, MinecraftErrorResponse.class);
if (StringUtils.isNotBlank(profileResponse.errorMessage) || con.getResponseCode() / 100 != 2)
throw new AuthenticationException("Failed to upload skin, response code: " + con.getResponseCode() + ", response: " + response);
}
} catch (IOException | JsonParseException e) {
throw new AuthenticationException(e);
}
}
private static String request(URL url, Object payload) throws AuthenticationException {
try {
if (payload == null)
@@ -272,14 +298,6 @@ public class MicrosoftService {
}
}
private static <T> T fromJson(String text, Class<T> typeOfT) throws ServerResponseMalformedException {
try {
return GSON.fromJson(text, typeOfT);
} catch (JsonParseException e) {
throw new ServerResponseMalformedException(text, e);
}
}
public static class XboxAuthorizationException extends AuthenticationException {
private final long errorCode;
private final String redirect;
@@ -203,8 +203,14 @@ public abstract class YggdrasilAccount extends ClassicAccount {
}
public void uploadSkin(String model, Path file) throws AuthenticationException, UnsupportedOperationException {
service.uploadSkin(characterUUID, session.getAccessToken(), model, file);
@Override
public boolean canUploadSkin() {
return true;
}
@Override
public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
service.uploadSkin(characterUUID, session.getAccessToken(), isSlim, file);
}
private static String randomClientToken() {
@@ -148,14 +148,14 @@ public class YggdrasilService {
requireEmpty(request(provider.getInvalidationURL(), createRequestWithCredentials(accessToken, clientToken)));
}
public void uploadSkin(UUID uuid, String accessToken, String model, Path file) throws AuthenticationException, UnsupportedOperationException {
public void uploadSkin(UUID uuid, String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
try {
HttpURLConnection con = NetworkUtils.createHttpConnection(provider.getSkinUploadURL(uuid));
con.setRequestMethod("PUT");
con.setRequestProperty("Authorization", "Bearer " + accessToken);
con.setDoOutput(true);
try (HttpMultipartRequest request = new HttpMultipartRequest(con)) {
request.param("model", model);
request.param("model", isSlim ? "slim" : "");
try (InputStream fis = Files.newInputStream(file)) {
request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis);
}
@@ -17,12 +17,7 @@
*/
package org.jackhuang.hmcl.download.java;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.platform.Platform;
import java.util.Set;
import java.util.TreeMap;
/**
* @author Glavo
@@ -31,6 +26,4 @@ public interface JavaDistribution<V extends JavaRemoteVersion> {
String getDisplayName();
Set<JavaPackageType> getSupportedPackageTypes();
Task<TreeMap<Integer, V>> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType);
}
@@ -21,10 +21,10 @@ package org.jackhuang.hmcl.download.java;
* @author Glavo
*/
public enum JavaPackageType {
JDK(true, false),
JRE(false, false),
JDKFX(true, true),
JREFX(false, true);
JDK(true, false),
JREFX(false, true),
JDKFX(true, true);
private final boolean jdk;
private final boolean javafx;
@@ -34,6 +34,13 @@ public enum JavaPackageType {
this.javafx = javafx;
}
public static JavaPackageType of(boolean jdk, boolean javafx) {
if (jdk)
return javafx ? JDKFX : JDK;
else
return javafx ? JREFX : JRE;
}
public boolean isJDK() {
return jdk;
}
@@ -33,7 +33,7 @@ import java.util.*;
/**
* @author Glavo
*/
public final class DiscoFetchJavaListTask extends Task<TreeMap<Integer, DiscoJavaRemoteVersion>> {
public final class DiscoFetchJavaListTask extends Task<EnumMap<JavaPackageType, TreeMap<Integer, DiscoJavaRemoteVersion>>> {
public static final String API_ROOT = System.getProperty("hmcl.discoapi.override", "https://api.foojay.io/disco/v3.0");
@@ -46,22 +46,21 @@ public final class DiscoFetchJavaListTask extends Task<TreeMap<Integer, DiscoJav
}
private final DiscoJavaDistribution distribution;
private final String archiveType;
private final Task<String> fetchPackagesTask;
public DiscoFetchJavaListTask(DownloadProvider downloadProvider, DiscoJavaDistribution distribution, Platform platform, JavaPackageType packageType) {
public DiscoFetchJavaListTask(DownloadProvider downloadProvider, DiscoJavaDistribution distribution, Platform platform) {
this.distribution = distribution;
this.archiveType = platform.getOperatingSystem() == OperatingSystem.WINDOWS ? "zip" : "tar.gz";
HashMap<String, String> params = new HashMap<>();
params.put("distribution", distribution.getApiParameter());
params.put("package", packageType.isJDK() ? "jdk" : "jre");
params.put("javafx_bundled", Boolean.toString(packageType.isJavaFXBundled()));
params.put("operating_system", getOperatingSystemName(platform.getOperatingSystem()));
params.put("architecture", getArchitectureName(platform.getArchitecture()));
params.put("archive_type", platform.getOperatingSystem() == OperatingSystem.WINDOWS ? "zip" : "tar.gz");
params.put("archive_type", archiveType);
params.put("directly_downloadable", "true");
if (platform.getOperatingSystem() == OperatingSystem.LINUX) {
if (platform.getOperatingSystem() == OperatingSystem.LINUX)
params.put("lib_c_type", "glibc");
}
this.fetchPackagesTask = new GetTask(downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(API_ROOT + "/packages", params)));
}
@@ -74,22 +73,28 @@ public final class DiscoFetchJavaListTask extends Task<TreeMap<Integer, DiscoJav
@Override
public void execute() throws Exception {
String json = fetchPackagesTask.getResult();
List<DiscoJavaRemoteVersion> result = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoJavaRemoteVersion.class)).getResult();
List<DiscoJavaRemoteVersion> list = JsonUtils.fromNonNullJson(json, DiscoResult.typeOf(DiscoJavaRemoteVersion.class)).getResult();
EnumMap<JavaPackageType, TreeMap<Integer, DiscoJavaRemoteVersion>> result = new EnumMap<>(JavaPackageType.class);
TreeMap<Integer, DiscoJavaRemoteVersion> map = new TreeMap<>();
for (DiscoJavaRemoteVersion version : result) {
if (!distribution.getApiParameter().equals(version.getDistribution()))
for (DiscoJavaRemoteVersion version : list) {
if (!distribution.getApiParameter().equals(version.getDistribution())
|| !version.isDirectlyDownloadable()
|| !archiveType.equals(version.getArchiveType()))
continue;
if (!distribution.testVersion(version))
continue;
JavaPackageType packageType = JavaPackageType.of("jdk".equals(version.getPackageType()), version.isJavaFXBundled());
TreeMap<Integer, DiscoJavaRemoteVersion> map = result.computeIfAbsent(packageType, ignored -> new TreeMap<>());
int jdkVersion = version.getJdkVersion();
DiscoJavaRemoteVersion oldVersion = map.get(jdkVersion);
if (oldVersion == null || VersionNumber.compare(version.getDistributionVersion(), oldVersion.getDistributionVersion()) > 0) {
if (oldVersion == null || VersionNumber.compare(version.getDistributionVersion(), oldVersion.getDistributionVersion()) > 0)
map.put(jdkVersion, version);
}
}
setResult(map);
setResult(result);
}
}
@@ -17,10 +17,8 @@
*/
package org.jackhuang.hmcl.download.java.disco;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.java.JavaDistribution;
import org.jackhuang.hmcl.download.java.JavaPackageType;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
@@ -35,6 +33,7 @@ import static org.jackhuang.hmcl.util.platform.OperatingSystem.*;
/**
* @author Glavo
* @see <a href="https://github.com/foojayio/discoapi">discoapi</a>
*/
public enum DiscoJavaDistribution implements JavaDistribution<DiscoJavaRemoteVersion> {
TEMURIN("Eclipse Temurin", "temurin", "Adoptium",
@@ -42,21 +41,42 @@ public enum DiscoJavaDistribution implements JavaDistribution<DiscoJavaRemoteVer
pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64, PPC64LE, S390X, SPARCV9)),
pair(OSX, EnumSet.of(X86_64, ARM64))),
LIBERICA("Liberica", "liberica", "BellSoft",
LIBERICA("BellSoft Liberica", "liberica", "BellSoft",
EnumSet.of(JDK, JRE, JDKFX, JREFX),
pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),
pair(OSX, EnumSet.of(X86_64, ARM64))) {
@Override
public boolean testVersion(DiscoJavaRemoteVersion version) {
if (!super.testVersion(version))
return false;
String fileName = version.getFileName();
return !fileName.endsWith("-lite.tar.gz") && !fileName.endsWith("-lite.zip");
}
},
ZULU("Azul Zulu", "zulu", "Azul",
EnumSet.of(JDK, JRE, JDKFX, JREFX),
pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),
pair(OSX, EnumSet.of(X86_64, ARM64))),
ZULU("Zulu", "zulu", "Azul",
EnumSet.of(JDK, JRE, JDKFX, JREFX),
pair(WINDOWS, EnumSet.of(X86_64, X86, ARM64)),
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),
pair(OSX, EnumSet.of(X86_64, ARM64))),
GRAALVM("GraalVM", "graalvm", "Oracle",
GRAALVM("Oracle GraalVM", "graalvm", "Oracle",
EnumSet.of(JDK),
pair(WINDOWS, EnumSet.of(X86_64, X86)),
pair(LINUX, EnumSet.of(X86_64, X86, ARM64, ARM32, RISCV64, PPC64LE)),
pair(OSX, EnumSet.of(X86_64, ARM64)));
pair(WINDOWS, EnumSet.of(X86_64)),
pair(LINUX, EnumSet.of(X86_64, ARM64)),
pair(OSX, EnumSet.of(X86_64, ARM64))),
SEMERU("IBM Semeru (OpenJ9)", "semeru", "IBM",
EnumSet.of(JDK, JRE),
pair(WINDOWS, EnumSet.of(X86_64)),
pair(LINUX, EnumSet.of(X86_64, ARM64, PPC64LE, S390X)),
pair(OSX, EnumSet.of(X86_64, ARM64))
),
CORRETTO("Amazon Corretto", "corretto", "Amazon",
EnumSet.of(JDK),
pair(WINDOWS, EnumSet.of(X86_64)),
pair(LINUX, EnumSet.of(X86_64, ARM64)),
pair(OSX, EnumSet.of(X86_64, ARM64))
);
public static DiscoJavaDistribution of(String name) {
for (DiscoJavaDistribution distribution : values()) {
@@ -109,8 +129,7 @@ public enum DiscoJavaDistribution implements JavaDistribution<DiscoJavaRemoteVer
return architectures != null && architectures.contains(platform.getArchitecture());
}
@Override
public Task<TreeMap<Integer, DiscoJavaRemoteVersion>> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType) {
return new DiscoFetchJavaListTask(provider, this, platform, packageType);
public boolean testVersion(DiscoJavaRemoteVersion version) {
return this.getApiParameter().equals(version.getDistribution());
}
}
@@ -171,6 +171,10 @@ public final class DiscoJavaRemoteVersion implements JavaRemoteVersion {
return termOfSupport;
}
public boolean isLTS() {
return "lts".equals(termOfSupport);
}
public String getOperatingSystem() {
return operatingSystem;
}
@@ -191,7 +195,7 @@ public final class DiscoJavaRemoteVersion implements JavaRemoteVersion {
return packageType;
}
public boolean isJavafxBundled() {
public boolean isJavaFXBundled() {
return javafxBundled;
}
@@ -17,16 +17,12 @@
*/
package org.jackhuang.hmcl.download.java.mojang;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.java.JavaDistribution;
import org.jackhuang.hmcl.download.java.JavaPackageType;
import org.jackhuang.hmcl.download.java.JavaRemoteVersion;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.platform.Platform;
import java.util.Collections;
import java.util.Set;
import java.util.TreeMap;
/**
* @author Glavo
@@ -47,9 +43,4 @@ public final class MojangJavaDistribution implements JavaDistribution<JavaRemote
public Set<JavaPackageType> getSupportedPackageTypes() {
return Collections.singleton(JavaPackageType.JRE);
}
@Override
public Task<TreeMap<Integer, JavaRemoteVersion>> getFetchJavaVersionsTask(DownloadProvider provider, Platform platform, JavaPackageType packageType) {
return null;
}
}
@@ -113,7 +113,7 @@ public final class CrashReportAnalyzer {
//https://github.com/HMCL-dev/HMCL/pull/2038
MODMIXIN_FAILURE(Pattern.compile("(MixinApplyError|Mixin prepare failed |Mixin apply failed |mixin\\.injection\\.throwables\\.|\\.mixins\\.json\\] FAILED during \\))")),//ModMixin失败
MIXIN_APPLY_MOD_FAILED(Pattern.compile("Mixin apply for mod (?<id>.*) failed"), "id"),//Mixin应用失败
FORGE_ERROR(Pattern.compile("An exception was thrown, the game will display an error screen and halt\\.(?<reason>(.*)[\\n\\r]*((.*)[\\n\\r]*)+)at "), "reason"),//Forge报错,Forge可能已经提供了错误信息
FORGE_ERROR(Pattern.compile("An exception was thrown, the game will display an error screen and halt\\.\\R*(?<reason>.*\\R*(\\s*at .*\\R)+)"), "reason"),//Forge报错,Forge可能已经提供了错误信息
MOD_RESOLUTION0(Pattern.compile("(\tMod File:|-- MOD |\tFailure message:)")),
FORGE_REPEAT_INSTALLATION(Pattern.compile("MultipleArgumentsForOptionException: Found multiple arguments for option (.*?), but you asked for only one")),//https://github.com/HMCL-dev/HMCL/issues/1880
OPTIFINE_REPEAT_INSTALLATION(Pattern.compile("ResolutionException: Module optifine reads another module named optifine")),//Optifine 重复安装(及Mod文件夹有,自动安装也有)
@@ -126,6 +126,7 @@ public final class JavaInfo {
return "Azul";
case "IBM Corporation":
case "International Business Machines Corporation":
case "Eclipse OpenJ9":
return "IBM";
case "Eclipse Adoptium":
return "Adoptium";
@@ -0,0 +1,32 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2024 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 java.lang.annotation.*;
/**
* Mark a method that can only be called on the JavaFX Application Thread,
* or mark a field that can only be read/modified on the JavaFX Application Thread.
*
* @author Glavo
*/
@Documented
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface FXThread {
}
@@ -767,4 +767,23 @@
1.21-pre3
1.21-pre4
1.21-rc1
1.21
1.21
1.21.1-rc1
1.21.1
24w33a
24w34a
24w35a
24w36a
24w37a
24w38a
24w39a
24w40a
1.21.2-pre1
1.21.2-pre2
1.21.2-pre3
1.21.2-pre4
1.21.2-pre5
1.21.2-rc1
1.21.2-rc2
1.21.2
1.21.3
@@ -140,7 +140,8 @@ public class CrashReportAnalyzerTest {
"\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_131]\n" +
"\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_131]\n" +
"\tat java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_131]\n" +
"\tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58) [JavaWrapper.jar:?]\n").replaceAll("\\s+", ""),
"\tat oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58) [JavaWrapper.jar:?]\n" +
"\tat oolloo.jlw.Wrapper.main(Wrapper.java:51) [JavaWrapper.jar:?]").replaceAll("\\s+", ""),
result.getMatcher().group("reason").replaceAll("\\s+", ""));
}
+19 -21
View File
@@ -1,19 +1,19 @@
# Platform Support Status
English | [简体中文](PLATFORM_cn.md)
**English** | [简体中文](PLATFORM_cn.md) | [繁體中文](PLATFORM_tw.md)
| | Windows | Linux | Mac OS | FreeBSD |
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
| | Windows | Linux | macOS | FreeBSD |
| -------------------------- | :----------------------------------------------- | :------------------------ | :--------------------------------------------------------------------- | :------------------------ |
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.1) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.20.6) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / |
| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / |
| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
Legend:
@@ -23,18 +23,16 @@ Legend:
* 👌: Supported platforms.
Support is provided by HMCL, tested to work, but may have more issues than a fully supported platform.
Support for versions below Minecraft 1.6 is not guaranteed.
Support is provided by HMCL, tested to work, but may have more issues than a fully supported platform.
Support for versions below Minecraft 1.6 is not guaranteed.
If you encounter a problem that does not exist on fully supported platforms, you can report it to HMCL.
* ❔: Low level supported platforms.
HMCL can run on this platform and has some basic support.
However, launching the game directly is not yet available.
If you want to start the game,
you'll need to get the native libraries needed by Minecraft in other way and specify the native path in the instance settings.
HMCL can run on this platform and has some basic support. However, launching the game directly is not yet available.
If you want to start the game, you will need to get the native libraries needed by Minecraft in other way and specify the native path in the instance settings.
* /: Not applicable.
* `/`: Not applicable.
We have no plans to support these platforms at this time, mainly because we don't have the equipment to test them.
If you can help us adapt, please file a support request via issue.
We have no plans to support these platforms at this time, mainly because we do not have the equipment to test them.
If you can help us adapt, please file a support request via GitHub Issue.
+26 -28
View File
@@ -1,40 +1,38 @@
# 平台支持状态
[English](PLATFORM.md) | 简体中文
[English](PLATFORM.md) | **简体中文** | [繁體中文](PLATFORM_tw.md)
| | Windows | Linux | Mac OS | FreeBSD |
|----------------------------|:--------------------------------------------------|:---------------------------|:-----------------------------------------------------------------------|:--------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.1) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.6~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / |
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
| | Windows | Linux | macOS | FreeBSD |
| -------------------------- | :----------------------------------------------- | :------------------------ | :---------------------------------------------------------------------- | :----------------------- |
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.1) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / |
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
图例:
图例:
* ✅: 官方支持的平台.
* ✅: 官方支持的平台
受 Mojang 官方支持. 在游戏中遇到的问题应该直接向 Mojang 反馈.
受 Mojang 官方支持。在游戏中遇到的问题应该直接向 Mojang 反馈。
* 👌: 支持的平台.
* 👌: 支持的平台
由 HMCL 提供支持, 经过测试可以正常运行, 但可能比得到全面支持的平台有更多问题.
不保证支持 Minecraft 1.6 以下的版本.
如果你遇到在得到全面支持的平台上不存在的问题, 可以向 HMCL 反馈.
由 HMCL 提供支持,经过测试可以正常运行,但可能比得到全面支持的平台有更多问题。
不保证支持 Minecraft 1.6 以下的版本。
如果你遇到在得到全面支持的平台上不存在的问题,可以向 HMCL 反馈。
* ❔: 低级别支持的平台.
* ❔: 低级别支持的平台
HMCL 可以在这个平台上运行, 并且有一些基本的支持.
但是, 还不能正常地启动游戏.
如果你想正常启动游戏,
则需要通过其他方式获得游戏所需的本地库(LWJGL), 并在版本设置中指定本地库路径.
HMCL 可以在这个平台上运行,并且有一些基本的支持。但是,还不能正常地启动游戏。
如果你想正常启动游戏,则需要通过其他方式获得游戏所需的本地库 (LWJGL),并在(全局)游戏设置中指定本地库路径。
* /: 不支持的平台.
* `/`: 不支持的平台
我们目前还没有打算支持这些平台, 主要是因为我们没有测试这些平台的设备.
如果你能帮助我们进行测试, 请通过提交 Issue 提出支持请求.
我们目前还没有打算支持这些平台,主要是因为我们没有测试这些平台的设备。
如果你能帮助我们进行测试,请通过提交 Issue 提出支持请求。
+38
View File
@@ -0,0 +1,38 @@
# 平臺支援狀態
[English](PLATFORM.md) | [简体中文](PLATFORM_cn.md) | **繁體中文**
| | Windows | Linux | macOS | FreeBSD |
| -------------------------- | :----------------------------------------------- | :------------------------ | :---------------------------------------------------------------------- | :----------------------- |
| x86-64 | ✅️ | ✅️ | ✅️ | 👌(Minecraft 1.13~1.21.1) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.1) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.1) | / | / |
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
圖例:
* ✅: 官方支援的平臺
受 Mojang 官方支援。在遊戲中遇到的問題應該直接向 Mojang 回報。
* 👌: 支援的平臺
由 HMCL 提供支援,經過測試可以正常執行,但可能比得到全面支援的平臺有更多問題。
不保證支援 Minecraft 1.6 以下的版本。
如果你遇到在得到全面支援的平臺上不存在的問題,可以向 HMCL 回報。
* ❔: 低級別支援的平臺
HMCL 可以在這個平臺上執行,並且有一些基本的支援。但是,還不能正常地啟動遊戲。
如果你想正常啟動遊戲,則需要透過其他方式獲得遊戲所需的本機庫 (LWJGL),並在(全域)遊戲設定中指定本機庫路徑。
* `/`: 不支援的平臺
我們目前還沒有打算支援這些平臺,主要是因為我們沒有測試這些平臺的裝置。
如果你能幫助我們進行測試,請透過提交 Issue 提出支援請求。
+25 -28
View File
@@ -1,21 +1,18 @@
# ⛏ Hello Minecraft! Launcher 💎
[![Build Status](https://ci.huangyuhui.net/job/HMCL/badge/icon?.svg)](https://ci.huangyuhui.net/job/HMCL)
![Downloads](https://img.shields.io/github/downloads/huanghongxun/HMCL/total)
![Stars](https://img.shields.io/github/stars/huanghongxun/HMCL)
![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-brightgreen)](https://docs.hmcl.net/groups.html)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=tencentqq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html)
English | [中文](README_cn.md)
**English** | [简体中文](README_cn.md) | [繁體中文](README_tw.md)
## Introduction
HMCL is a cross-platform Minecraft launcher which supports Mod Management, Game Customizing, Auto Installing (Forge, NeoForge, Fabric, Quilt, LiteLoader and OptiFine), Modpack Creating, UI Customization, and more.
HMCL is an open-source, cross-platform Minecraft launcher that supports Mod Management, Game Customizing, ModLoader Installing (Forge, NeoForge, Fabric, Quilt, LiteLoader and OptiFine), Modpack Creating, UI Customization, and more.
HMCL has amazing cross-platform capabilities.
It can not only run on different operating systems such as Windows, Linux, and macOS,
but also supports multiple CPU architectures such as x86, arm, mips, and loongarch.
You can easily play Minecraft on different platforms through HMCL.
HMCL has amazing cross-platform capabilities. Not only does it run on different operating systems like Windows, Linux, and macOS, but it also supports various CPU architectures such as x86, ARM, MIPS, and LoongArch. You can easily enjoy Minecraft across different platforms through HMCL.
For systems and CPU architectures supported by HMCL, see [this table](PLATFORM.md).
@@ -23,13 +20,13 @@ For systems and CPU architectures supported by HMCL, see [this table](PLATFORM.m
Download the latest version from [the official website](https://hmcl.huangyuhui.net/download).
You can also download the latest version of HMCL in [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases).
You can also find the latest version of HMCL in [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases).
Although not necessary, it is recommended to download the ones from the official website.
Although not necessary, it is recommended only to download releases from the official websites listed above.
## License
The software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) with additional terms.
The software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) license with the following additional terms:
### Additional terms under GPLv3 Section 7
@@ -41,9 +38,9 @@ The software is distributed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.h
## Contribution
If you want to submit a pull request, there are some requirements:
If you want to submit a pull request, here are some requirements:
* IDE: Intellij IDEA
* IDE: IntelliJ IDEA
* Compiler: Java 1.8 or Java 11+
* Do NOT modify `gradle` files
@@ -55,20 +52,20 @@ Simply execute the following command in project root directory:
./gradlew clean build
```
Make sure you have Java installed with JavaFX 8 at least. Liberica Full JDK 8 or later is recommended.
Make sure you have at least JavaFX 8 installed. Liberica Full JDK 8 or later is recommended.
## JVM Options (for debugging)
| Parameter | Description |
|----------------------------------------------|------------------------------------------------------------------------------------------------|
| `-Dhmcl.home=<path>` | Override HMCL directory. |
| `-Dhmcl.self_integrity_check.disable=true` | Bypass the self integrity check when checking for update. |
| `-Dhmcl.bmclapi.override=<url>` | Override API Root of BMCLAPI download provider, defaults to `https://bmclapi2.bangbang93.com`. |
| `-Dhmcl.font.override=<font family>` | Override font family. |
| `-Dhmcl.version.override=<version>` | Override the version number. |
| `-Dhmcl.update_source.override=<url>` | Override the update source for HMCL itself. |
| `-Dhmcl.authlibinjector.location=<path>` | Use specified authlib-injector (instead of downloading one). |
| `-Dhmcl.openjfx.repo=<maven repository url>` | Add custom Maven repository for download OpenJFX. |
| `-Dhmcl.native.encoding=<encoding>` | Override the native encoding. |
| `-Dhmcl.microsoft.auth.id=<App ID>` | Override Microsoft OAuth App ID. |
| `-Dhmcl.microsoft.auth.secret=<App Secret>` | Override Microsoft OAuth App secret. |
| Parameter | Description |
| -------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `-Dhmcl.home=<path>` | Override HMCL directory |
| `-Dhmcl.self_integrity_check.disable=true` | Bypass the self integrity check when checking for update |
| `-Dhmcl.bmclapi.override=<url>` | Override API Root of BMCLAPI download provider, defaults to `https://bmclapi2.bangbang93.com` |
| `-Dhmcl.font.override=<font family>` | Override font family |
| `-Dhmcl.version.override=<version>` | Override the version number |
| `-Dhmcl.update_source.override=<url>` | Override the update source for HMCL itself |
| `-Dhmcl.authlibinjector.location=<path>` | Use specified authlib-injector (instead of downloading one) |
| `-Dhmcl.openjfx.repo=<maven repository url>` | Add custom Maven repository for download OpenJFX |
| `-Dhmcl.native.encoding=<encoding>` | Override the native encoding |
| `-Dhmcl.microsoft.auth.id=<App ID>` | Override Microsoft OAuth App ID |
| `-Dhmcl.microsoft.auth.secret=<App Secret>` | Override Microsoft OAuth App secret |
+32 -33
View File
@@ -1,72 +1,71 @@
# ⛏ Hello Minecraft! Launcher 💎
[![Build Status](https://ci.huangyuhui.net/job/HMCL/badge/icon?.svg)](https://ci.huangyuhui.net/job/HMCL)
![Downloads](https://img.shields.io/github/downloads/huanghongxun/HMCL/total)
![Stars](https://img.shields.io/github/stars/huanghongxun/HMCL)
![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-brightgreen)](https://docs.hmcl.net/groups.html)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=tencentqq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html)
[English](README.md) | 中文
[English](README.md) | **简体中文** | [繁體中文](README_tw.md)
## 简介
HMCL 是一款跨平台 Minecraft 启动器, 支持 Mod 管理, 游戏自定义, 游戏自动安装 (Forge, NeoForge, Fabric, Quilt, LiteLoader 与 OptiFine), 整合包创建, 界面自定义等功能.
HMCL 是一款开源、跨平台的 Minecraft 启动器,支持模组管理、游戏自定义、游戏自动安装 (Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine)、整合包创建、界面自定义等功能。
HMCL 有着强大的跨平台能力. 它不仅支持 Windows、Linux、macOS 等常见的操作系统,同时也支持 x86、ARM、MIPS 和 LoongArch 等不同的 CPU 架构.
您可以使用 HMCL 在不同平台上轻松的游玩 Minecraft.
HMCL 有着强大的跨平台能力。它不仅支持 Windows、Linux、macOS 等常见的操作系统,同时也支持 x86、ARM、MIPS 和 LoongArch 等不同的 CPU 架构。你可以使用 HMCL 在不同平台上轻松地游玩 Minecraft。
如果您想要了解 HMCL 对不同平台的支持程度,请参见[此表格](PLATFORM_cn.md).
如果你想要了解 HMCL 对不同平台的支持程度,请参见 [此表格](PLATFORM_cn.md)。
## 下载
请从 [HMCL 官网](https://hmcl.huangyuhui.net/download) 下载最新版本的 HMCL.
请从 [HMCL 官网](https://hmcl.huangyuhui.net/download) 下载最新版本的 HMCL。
你也可以在 [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases) 中下载最新版本的 HMCL.
你也可以在 [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases) 中下载最新版本的 HMCL。
虽然并不强制, 但仍建议通过 HMCL 官网下载启动器.
虽然并不强制,但仍建议通过 HMCL 官网下载启动器。
## 开源协议
该程序在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 开源协议下发布, 同时附有附加条款.
该程序在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 开源协议下发布,同时附有以下附加条款。
### 附加条款 (依据 GPLv3 开源协议第七条)
1. 当您分发该程序的修改版本时, 您必须以一种合理的方式修改该程序的名称或版本号, 以示其与原始版本不同. (依据 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374))
1. 当你分发该程序的修改版本时,你必须以一种合理的方式修改该程序的名称或版本号,以示其与原始版本不同。(依据 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374))
该程序的名称及版本号可在[此处](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35)修改.
该程序的名称及版本号可在 [此处](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35) 修改。
2. 您不得移除该程序所显示的版权声明. (依据 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370))
2. 你不得移除该程序所显示的版权声明。(依据 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370))
## 贡献
如果您想提交一个 Pull Request, 必须遵守如下要求:
如果你想提交一个 Pull Request,必须遵守如下要求:
* IDE: Intellij IDEA
* 编译器: Java 1.8 或 Java 11+
* IDE:IntelliJ IDEA
* 编译器:Java 1.8 或 Java 11+
* **不要**修改 `gradle` 相关文件
### 编译
于项目根目录执行以下命令:
于项目根文件夹执行以下命令:
```bash
./gradlew clean build
```
请确保您至少安装了含有 JavaFX 8 的 Java. 建议使用 Liberica Full JDK 8 或更高版本.
请确保你至少安装了含有 JavaFX 8 的 Java。建议使用 Liberica Full JDK 8 或更高版本。
## JVM 选项 (用于调试)
| 参数 | 简介 |
|----------------------------------------------|----------------------------------------------------------------|
| `-Dhmcl.home=<path>` | 覆盖 HMCL 数据文件夹. |
| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时绕过本体完整性检查. |
| `-Dhmcl.bmclapi.override=<url>` | 覆盖 BMCLAPI 的 API Root, 默认值为 `https://bmclapi2.bangbang93.com`. |
| `-Dhmcl.font.override=<font family>` | 覆盖字族. |
| `-Dhmcl.version.override=<version>` | 覆盖版本号. |
| `-Dhmcl.update_source.override=<url>` | 覆盖 HMCL 更新源. |
| `-Dhmcl.authlibinjector.location=<path>` | 使用指定的 authlib-injector (而非下载一个). |
| `-Dhmcl.openjfx.repo=<maven repository url>` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 |
| `-Dhmcl.native.encoding=<encoding>` | 覆盖原生编码. |
| `-Dhmcl.microsoft.auth.id=<App ID>` | 覆盖 Microsoft OAuth App ID. |
| `-Dhmcl.microsoft.auth.secret=<App Secret>` | 覆盖 Microsoft OAuth App 密钥. |
| 参数 | 简介 |
| -------------------------------------------- | -------------------------------------------------------------------- |
| `-Dhmcl.home=<path>` | 覆盖 HMCL 数据文件夹 |
| `-Dhmcl.self_integrity_check.disable=true` | 检查更新时绕过本体完整性检查 |
| `-Dhmcl.bmclapi.override=<url>` | 覆盖 BMCLAPI 的 API Root,默认值为 `https://bmclapi2.bangbang93.com` |
| `-Dhmcl.font.override=<font family>` | 覆盖字族 |
| `-Dhmcl.version.override=<version>` | 覆盖版本号 |
| `-Dhmcl.update_source.override=<url>` | 覆盖 HMCL 更新源 |
| `-Dhmcl.authlibinjector.location=<path>` | 使用指定的 authlib-injector (而非下载一个) |
| `-Dhmcl.openjfx.repo=<maven repository url>` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 |
| `-Dhmcl.native.encoding=<encoding>` | 覆盖原生编码 |
| `-Dhmcl.microsoft.auth.id=<App ID>` | 覆盖 Microsoft OAuth App ID |
| `-Dhmcl.microsoft.auth.secret=<App Secret>` | 覆盖 Microsoft OAuth App 密钥 |
+71
View File
@@ -0,0 +1,71 @@
# ⛏ Hello Minecraft! Launcher 💎
[![Build Status](https://ci.huangyuhui.net/job/HMCL/badge/icon?.svg)](https://ci.huangyuhui.net/job/HMCL)
![Downloads](https://img.shields.io/github/downloads/HMCL-dev/HMCL/total?style=flat)
![Stars](https://img.shields.io/github/stars/HMCL-dev/HMCL?style=flat)
[![Discord](https://img.shields.io/discord/995291757799538688.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/jVvC7HfM6U)
[![QQ Group](https://img.shields.io/badge/QQ-HMCL-bright?label=&logo=tencentqq&logoColor=ffffff&color=1EBAFC&labelColor=1DB0EF&logoSize=auto)](https://docs.hmcl.net/groups.html)
[English](README.md) | [简体中文](README_cn.md) | **繁體中文**
## 簡介
HMCL 是一款開源、跨平臺的 Minecraft 啟動器,支援模組管理、遊戲自訂、遊戲自動安裝 (Forge、NeoForge、Fabric、Quilt、LiteLoader 和 OptiFine)、模組包建立、介面自訂等功能。
HMCL 有著強大的跨平臺能力。它不僅支援 Windows、Linux、macOS 等常見的作業系統,同時也支援 x86、ARM、MIPS 和 LoongArch 等不同的 CPU 架構。你可以使用 HMCL 在不同平臺上輕鬆地遊玩 Minecraft。
如果你想要了解 HMCL 對不同平臺的支援程度,請參見 [此表格](PLATFORM_tw.md)。
## 下載
請從 [HMCL 官網](https://hmcl.huangyuhui.net/download) 下載最新版本的 HMCL。
你也可以在 [GitHub Releases](https://github.com/HMCL-dev/HMCL/releases) 中下載最新版本的 HMCL。
雖然並不強制,但仍建議透過 HMCL 官網下載啟動器。
## 開源協議
該程式在 [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 開源協議下發布,同時附有以下附加條款。
### 附加條款 (依據 GPLv3 開源協議第七條)
1. 當你分發該程式的修改版本時,你必須以一種合理的方式修改該程式的名稱或版本號,以示其與原始版本不同。(依據 [GPLv3, 7(c)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L372-L374))
該程式的名稱及版本號可在 [此處](https://github.com/HMCL-dev/HMCL/blob/javafx/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java#L33-L35) 修改。
2. 你不得移除該程式所顯示的版權宣告。(依據 [GPLv3, 7(b)](https://github.com/HMCL-dev/HMCL/blob/11820e31a85d8989e41d97476712b07e7094b190/LICENSE#L368-L370))
## 貢獻
如果你想提交一個 Pull Request,必須遵守如下要求:
* IDE:IntelliJ IDEA
* 編譯器:Java 1.8 或 Java 11+
* **不要**修改 `gradle` 相關文件
### 編譯
於項目根目錄執行以下指令:
```bash
./gradlew clean build
```
請確保你至少安裝了含有 JavaFX 8 的 Java。建議使用 Liberica Full JDK 8 或更高版本。
## JVM 選項 (用於除錯)
| 參數 | 簡介 |
| -------------------------------------------- | -------------------------------------------------------------------- |
| `-Dhmcl.home=<path>` | 覆蓋 HMCL 使用者目錄 |
| `-Dhmcl.self_integrity_check.disable=true` | 檢查更新時繞過本體完整性檢查 |
| `-Dhmcl.bmclapi.override=<url>` | 覆蓋 BMCLAPI 的 API Root,預設值為 `https://bmclapi2.bangbang93.com` |
| `-Dhmcl.font.override=<font family>` | 覆蓋字族 |
| `-Dhmcl.version.override=<version>` | 覆蓋版本號 |
| `-Dhmcl.update_source.override=<url>` | 覆蓋 HMCL 更新來源 |
| `-Dhmcl.authlibinjector.location=<path>` | 使用指定的 authlib-injector (而非下載一個) |
| `-Dhmcl.openjfx.repo=<maven repository url>` | 添加用於下載 OpenJFX 的自訂 Maven 倉庫 |
| `-Dhmcl.native.encoding=<encoding>` | 覆蓋原生編碼 |
| `-Dhmcl.microsoft.auth.id=<App ID>` | 覆蓋 Microsoft OAuth App ID |
| `-Dhmcl.microsoft.auth.secret=<App Secret>` | 覆蓋 Microsoft OAuth App 金鑰 |
+6 -5
View File
@@ -4,11 +4,12 @@ buildscript {
}
dependencies {
classpath("com.google.code.gson:gson:2.10.1")
classpath("com.google.code.gson:gson:2.11.0")
}
}
val jfxVersion = "19.0.2.1"
val jfxVersion = "17.0.13"
val oldJfxVersion = "19.0.2.1"
data class Platform(
val name: String,
@@ -29,14 +30,14 @@ val jfxModules = listOf("base", "graphics", "controls")
val jfxMirrorRepos = listOf("https://mirrors.cloud.tencent.com/nexus/repository/maven-public")
val jfxDependenciesFile = project.file("HMCL/src/main/resources/assets/openjfx-dependencies.json")
val jfxPlatforms = listOf(
Platform("windows-x86", "win-x86"),
Platform("windows-x86", "win-x86", version = oldJfxVersion),
Platform("windows-x86_64", "win"),
Platform("windows-arm64", "win", groupId = "org.glavo.hmcl.openjfx", version = "18.0.2+1-arm64"),
Platform("osx-x86_64", "mac"),
Platform("osx-arm64", "mac-aarch64"),
Platform("linux-x86_64", "linux"),
Platform("linux-arm32", "linux-arm32-monocle"),
Platform("linux-arm64", "linux-aarch64"),
Platform("linux-arm32", "linux-arm32-monocle", version = oldJfxVersion),
Platform("linux-arm64", "linux-aarch64", version = oldJfxVersion),
Platform("linux-loongarch64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "17.0.8-loongarch64"),
Platform("linux-loongarch64_ow", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19-ea+10-loongson64"),
Platform("linux-riscv64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19.0.2.1-riscv64"),