Compare commits

...
Author SHA1 Message Date
Burning_TNTandGlavo 84b3312ebb Fix #3825: 修复 NeoForge 愚人节版本无法正常解析的问题 (#3826)
* Fix #2825.

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-04-19 11:49:42 +08:00
Burning_TNTandGlavo f351d1d133 Close #3829: 将模组/整合包/资源包的默认下载源调整为 Modrinth (#3830)
* Close #3829: Use Modrinth as default download source.

* Update HMCLLocalizedDownloadListPage.java

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2025-04-18 22:49:02 +08:00
Glavo 5ca1120223 Bump jsoup to 1.19.1 (#3835) 2025-04-18 22:42:10 +08:00
Glavo 6aa4019221 Bump Gson to 2.13.0 (#3834) 2025-04-18 22:35:05 +08:00
Glavo 73fb8bda10 优化默认 JVM 参数 (#3824) 2025-04-15 03:46:33 +08:00
Glavo cc16f84992 默认限制离线账户功能 (#3823) 2025-04-14 20:34:01 +08:00
辞庐 b4945a150e 统一导出和导入整合包页面样式 (#3822) 2025-04-13 22:34:22 +08:00
14 changed files with 185 additions and 41 deletions

No files matched your search

+5 -2
View File
@@ -190,9 +190,9 @@ tasks.build {
dependsOn(makeExecutables)
}
fun parseToolOptions(options: String?): List<String> {
fun parseToolOptions(options: String?): MutableList<String> {
if (options == null)
return listOf()
return mutableListOf()
val builder = StringBuilder()
val result = mutableListOf<String>()
@@ -249,6 +249,9 @@ tasks.create<JavaExec>("run") {
workingDir = rootProject.rootDir
val vmOptions = parseToolOptions(System.getenv("HMCL_JAVA_OPTS"))
if (vmOptions.none { it.startsWith("-Dhmcl.offline.auth.restricted=") })
vmOptions += "-Dhmcl.offline.auth.restricted=false"
jvmArgs(vmOptions)
val hmclJavaHome = System.getenv("HMCL_JAVA_HOME")
@@ -509,16 +509,16 @@ public class HMCLGameRepository extends DefaultGameRepository {
public static long getAllocatedMemory(long minimum, long available, boolean auto) {
if (auto) {
available -= 384 * 1024 * 1024; // Reserve 384MiB memory for off-heap memory and HMCL itself
available -= 512 * 1024 * 1024; // Reserve 512 MiB memory for off-heap memory and HMCL itself
if (available <= 0) {
return minimum;
}
final long threshold = 8L * 1024 * 1024 * 1024;
final long threshold = 8L * 1024 * 1024 * 1024; // 8 GiB
final long suggested = Math.min(available <= threshold
? (long) (available * 0.8)
: (long) (threshold * 0.8 + (available - threshold) * 0.2),
16384L * 1024 * 1024);
16L * 1024 * 1024 * 1024);
return Math.max(minimum, suggested);
} else {
return minimum;
@@ -22,6 +22,7 @@ import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.*;
@@ -50,6 +51,7 @@ import java.util.*;
import static java.util.stream.Collectors.toList;
import static javafx.collections.FXCollections.observableArrayList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.Lang.immutableListOf;
import static org.jackhuang.hmcl.util.Lang.mapOf;
@@ -277,6 +279,30 @@ public final class Accounts {
selected = accounts.get(0);
}
if (!globalConfig().isEnableOfflineAccount())
for (Account account : accounts) {
if (account instanceof MicrosoftAccount) {
globalConfig().setEnableOfflineAccount(true);
break;
}
}
if (!globalConfig().isEnableOfflineAccount())
accounts.addListener(new ListChangeListener<Account>() {
@Override
public void onChanged(Change<? extends Account> change) {
while (change.next()) {
for (Account account : change.getAddedSubList()) {
if (account instanceof MicrosoftAccount) {
accounts.removeListener(this);
globalConfig().setEnableOfflineAccount(true);
return;
}
}
}
}
});
selectedAccount.set(selected);
InvalidationListener listener = o -> {
@@ -21,7 +21,9 @@ import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
@@ -53,6 +55,8 @@ public final class GlobalConfig implements Observable {
private final IntegerProperty logRetention = new SimpleIntegerProperty();
private final BooleanProperty enableOfflineAccount = new SimpleBooleanProperty(false);
private final ObservableSet<String> userJava = FXCollections.observableSet(new LinkedHashSet<>());
private final ObservableSet<String> disabledJava = FXCollections.observableSet(new LinkedHashSet<>());
@@ -115,6 +119,18 @@ public final class GlobalConfig implements Observable {
this.logRetention.set(logRetention);
}
public boolean isEnableOfflineAccount() {
return enableOfflineAccount.get();
}
public BooleanProperty enableOfflineAccountProperty() {
return enableOfflineAccount;
}
public void setEnableOfflineAccount(boolean value) {
enableOfflineAccount.set(value);
}
public ObservableSet<String> getUserJava() {
return userJava;
}
@@ -129,7 +145,8 @@ public final class GlobalConfig implements Observable {
"platformPromptVersion",
"logRetention",
"userJava",
"disabledJava"
"disabledJava",
"enableOfflineAccount"
));
@Override
@@ -142,6 +159,9 @@ public final class GlobalConfig implements Observable {
jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion()));
jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion()));
jsonObject.add("logRetention", context.serialize(src.getLogRetention()));
if (src.enableOfflineAccount.get())
jsonObject.addProperty("enableOfflineAccount", true);
if (!src.getUserJava().isEmpty())
jsonObject.add("userJava", context.serialize(src.getUserJava()));
@@ -165,6 +185,7 @@ public final class GlobalConfig implements Observable {
config.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0));
config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).map(JsonElement::getAsInt).orElse(0));
config.setLogRetention(Optional.ofNullable(obj.get("logRetention")).map(JsonElement::getAsInt).orElse(20));
config.setEnableOfflineAccount(Optional.ofNullable(obj.get("enableOfflineAccount")).map(JsonElement::getAsBoolean).orElse(false));
JsonElement userJava = obj.get("userJava");
if (userJava != null && userJava.isJsonArray()) {
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.account;
import com.jfoenix.controls.JFXButton;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -44,14 +45,37 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import java.net.URI;
import java.time.ZoneId;
import java.util.Locale;
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
public class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
public final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(true);
static {
String property = System.getProperty("hmcl.offline.auth.restricted", "auto");
if ("false".equals(property)
|| "auto".equals(property) && "Asia/Shanghai".equals(ZoneId.systemDefault().getId())
|| globalConfig().isEnableOfflineAccount())
RESTRICTED.set(false);
else
globalConfig().enableOfflineAccountProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> o, Boolean oldValue, Boolean newValue) {
if (newValue) {
globalConfig().enableOfflineAccountProperty().removeListener(this);
RESTRICTED.set(false);
}
}
});
}
private final ObservableList<AccountListItem> items;
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("account.manage")));
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
@@ -88,6 +112,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
private static class AccountListPageSkin extends DecoratorAnimatedPageSkin<AccountListPage> {
private final ObservableList<AdvancedListItem> authServerItems;
private ChangeListener<Boolean> holder;
public AccountListPageSkin(AccountListPage skinnable) {
super(skinnable);
@@ -96,24 +121,21 @@ 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").toUpperCase(Locale.ROOT)));
FXUtils.setLimitWidth(boxMethods, 200);
AdvancedListItem offlineItem = new AdvancedListItem();
offlineItem.getStyleClass().add("navigation-drawer-item");
offlineItem.setActionButtonVisible(false);
offlineItem.setTitle(i18n("account.methods.offline"));
offlineItem.setLeftGraphic(wrap(SVG.PERSON));
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
boxMethods.getChildren().add(offlineItem);
AdvancedListItem microsoftItem = new AdvancedListItem();
microsoftItem.getStyleClass().add("navigation-drawer-item");
microsoftItem.setActionButtonVisible(false);
microsoftItem.setTitle(i18n("account.methods.microsoft"));
microsoftItem.setLeftGraphic(wrap(SVG.MICROSOFT));
microsoftItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_MICROSOFT)));
boxMethods.getChildren().add(microsoftItem);
AdvancedListItem offlineItem = new AdvancedListItem();
offlineItem.getStyleClass().add("navigation-drawer-item");
offlineItem.setActionButtonVisible(false);
offlineItem.setTitle(i18n("account.methods.offline"));
offlineItem.setLeftGraphic(wrap(SVG.PERSON));
offlineItem.setOnAction(e -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
VBox boxAuthServers = new VBox();
authServerItems = MappedObservableList.create(skinnable.authServersProperty(), server -> {
@@ -149,7 +171,29 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
return item;
});
Bindings.bindContent(boxAuthServers.getChildren(), authServerItems);
boxMethods.getChildren().add(boxAuthServers);
ClassTitle title = new ClassTitle(i18n("account.create").toUpperCase(Locale.ROOT));
if (RESTRICTED.get()) {
VBox wrapper = new VBox(offlineItem, boxAuthServers);
wrapper.setPadding(Insets.EMPTY);
FXUtils.installFastTooltip(wrapper, i18n("account.login.restricted"));
offlineItem.setDisable(true);
boxAuthServers.setDisable(true);
boxMethods.getChildren().setAll(title, microsoftItem, wrapper);
holder = FXUtils.onWeakChange(RESTRICTED, value -> {
if (!value) {
holder = null;
offlineItem.setDisable(false);
boxAuthServers.setDisable(false);
boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers);
}
});
} else {
boxMethods.getChildren().setAll(title, microsoftItem, offlineItem, boxAuthServers);
}
}
AdvancedListItem addAuthServerItem = new AdvancedListItem();
@@ -108,12 +108,17 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
public CreateAccountPane(AccountFactory<?> factory) {
if (factory == null) {
showMethodSwitcher = true;
String preferred = config().getPreferredLoginType();
try {
factory = Accounts.getAccountFactory(preferred);
} catch (IllegalArgumentException e) {
factory = Accounts.FACTORY_OFFLINE;
if (AccountListPage.RESTRICTED.get()) {
showMethodSwitcher = false;
factory = Accounts.FACTORY_MICROSOFT;
} else {
showMethodSwitcher = true;
String preferred = config().getPreferredLoginType();
try {
factory = Accounts.getAccountFactory(preferred);
} catch (IllegalArgumentException e) {
factory = Accounts.FACTORY_OFFLINE;
}
}
} else {
showMethodSwitcher = false;
@@ -43,15 +43,15 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
public ModpackTypeSelectionPage(WizardController controller) {
this.controller = controller;
this.setStyle("-fx-background-color: white; -fx-background-radius: 5;");
this.setPadding(new Insets(10));
Label title = new Label(i18n("modpack.export.as"));
title.setStyle("-fx-font-size: 14px; -fx-font-weight: bold;");
VBox.setMargin(title, new Insets(8, 0, 8, 12));
this.getStyleClass().add("jfx-list-view");
this.setMaxSize(500, 150);
this.setMaxSize(400, 150);
this.setSpacing(8);
this.getChildren().setAll(
title,
createButton(MODPACK_TYPE_MCBBS, McbbsModpackExportTask.OPTION),
@@ -62,6 +62,8 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
private JFXButton createButton(String type, ModpackExportInfo.Options option) {
JFXButton button = new JFXButton();
button.getStyleClass().add("card");
button.setOnAction(e -> {
controller.getSettings().put(MODPACK_TYPE, type);
controller.getSettings().put(MODPACK_INFO_OPTION, option);
@@ -55,11 +55,11 @@ public final class HMCLLocalizedDownloadListPage extends DownloadListPage {
repository = new Repository(type, curseForge, modrinth);
supportChinese.set(true);
downloadSources.get().setAll("mods.curseforge", "mods.modrinth");
if (curseForge != null) {
downloadSource.set("mods.curseforge");
} else if (modrinth != null) {
downloadSources.get().setAll("mods.modrinth", "mods.curseforge");
if (modrinth != null) {
downloadSource.set("mods.modrinth");
} else if (curseForge != null) {
downloadSource.set("mods.curseforge");
} else {
throw new AssertionError("Should not be here.");
}
@@ -88,6 +88,7 @@ account.login.skip=Log in offline
account.login.retry=Retry
account.login.refresh=Log in again
account.login.refresh.microsoft.hint=You need to log in to your Microsoft account again because the account authorization is invalid.
account.login.restricted=Sign in to your Microsoft account to enable this feature
account.logout=Logout
account.register=Register
account.manage=Account List
@@ -91,6 +91,7 @@ account.login.skip=跳過重新整理帳戶
account.login.retry=再次重新整理帳戶
account.login.refresh=重新登入
account.login.refresh.microsoft.hint=由於帳戶授權失效,你需要重新加入 Microsoft 帳戶
account.login.restricted=登入微軟帳戶以啟用此功能
account.logout=登出
account.register=註冊
account.manage=帳戶清單
@@ -92,6 +92,7 @@ account.login.skip=跳过账户刷新
account.login.retry=再次刷新账户
account.login.refresh=重新登录
account.login.refresh.microsoft.hint=由于账户授权失效,你需要重新添加微软账户。
account.login.restricted=登录微软账户以启用此功能
account.logout=登出
account.register=注册
account.manage=账户列表
+2 -2
View File
@@ -8,14 +8,14 @@ dependencies {
api("org.glavo.kala:kala-compress-archivers-zip:$kalaCompressVersion")
api("org.glavo.kala:kala-compress-archivers-tar:$kalaCompressVersion")
api("org.glavo:simple-png-javafx:0.3.0")
api("com.google.code.gson:gson:2.12.1")
api("com.google.code.gson:gson:2.13.0")
api("com.moandjiezana.toml:toml4j:0.7.2")
api("org.tukaani:xz:1.10")
api("org.hildan.fxgson:fx-gson:5.0.0")
api("org.jenkins-ci:constant-pool-scanner:1.2")
api("com.github.steveice10:opennbt:1.5")
api("org.nanohttpd:nanohttpd:2.3.1")
api("org.jsoup:jsoup:1.18.3")
api("org.jsoup:jsoup:1.19.1")
compileOnlyApi("org.jetbrains:annotations:26.0.1")
if (JavaVersion.current().isJava8) {
@@ -10,6 +10,7 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static org.jackhuang.hmcl.util.Lang.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemoteVersion> {
private final DownloadProvider downloadProvider;
@@ -56,8 +57,20 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
}
for (String version : results[1].versions) {
int si1 = version.indexOf('.'), si2 = version.indexOf('.', version.indexOf('.') + 1);
String mcVersion = "1." + version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2);
String mcVersion;
try {
int si1 = version.indexOf('.'), si2 = version.indexOf('.', version.indexOf('.') + 1);
int majorVersion = Integer.parseInt(version.substring(0, si1));
if (majorVersion == 0) { // Snapshot version.
mcVersion = version.substring(si1 + 1, si2);
} else {
mcVersion = "1." + version.substring(0, Integer.parseInt(version.substring(si1 + 1, si2)) == 0 ? si1 : si2);
}
} catch (RuntimeException e) {
LOG.warning(String.format("Cannot parse NeoForge version %s for cracking its mc version.", version), e);
continue;
}
versions.put(mcVersion, new NeoForgeRemoteVersion(
mcVersion, NeoForgeRemoteVersion.normalize(version),
@@ -189,30 +189,57 @@ public class DefaultLauncher extends Launcher {
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
res.addDefault("-Duser.home=", options.getGameDir().getParent());
final int javaVersion = options.getJava().getParsedVersion();
final boolean is64bit = options.getJava().getBits() == Bits.BIT_64;
res.addUnstableDefault("UnlockExperimentalVMOptions", true);
res.addUnstableDefault("UnlockDiagnosticVMOptions", true);
// Using G1GC with its settings by default
if (options.getJava().getParsedVersion() >= 8
if (javaVersion >= 8
&& res.noneMatch(arg -> "-XX:-UseG1GC".equals(arg) || (arg.startsWith("-XX:+Use") && arg.endsWith("GC")))) {
res.addUnstableDefault("UnlockExperimentalVMOptions", true);
res.addUnstableDefault("UseG1GC", true);
res.addUnstableDefault("G1MixedGCCountTarget", "5");
res.addUnstableDefault("G1NewSizePercent", "20");
res.addUnstableDefault("G1ReservePercent", "20");
res.addUnstableDefault("MaxGCPauseMillis", "50");
res.addUnstableDefault("G1HeapRegionSize", "32m");
}
res.addUnstableDefault("UseAdaptiveSizePolicy", false);
res.addUnstableDefault("OmitStackTraceInFastThrow", false);
res.addUnstableDefault("DontCompileHugeMethods", false);
// JIT Options
if (javaVersion <= 8) {
res.addUnstableDefault("MaxInlineLevel", "15");
}
if (is64bit && OperatingSystem.TOTAL_MEMORY > 4 * 1024) {
res.addUnstableDefault("DontCompileHugeMethods", false);
res.addUnstableDefault("MaxNodeLimit", "240000");
res.addUnstableDefault("NodeLimitFudgeFactor", "8000");
res.addUnstableDefault("TieredCompileTaskTimeout", "10000");
res.addUnstableDefault("ReservedCodeCacheSize", "400M");
if (javaVersion >= 9) {
res.addUnstableDefault("NonNMethodCodeHeapSize", "12M");
res.addUnstableDefault("ProfiledCodeHeapSize", "194M");
}
if (javaVersion >= 8) {
res.addUnstableDefault("NmethodSweepActivity", "1");
}
}
// As 32-bit JVM allocate 320KB for stack by default rather than 64-bit version allocating 1MB,
// causing Minecraft 1.13 crashed accounting for java.lang.StackOverflowError.
if (options.getJava().getBits() == Bits.BIT_32) {
if (!is64bit) {
res.addDefault("-Xss", "1m");
}
if (options.getJava().getParsedVersion() == 16)
if (javaVersion == 16)
res.addDefault("--illegal-access=", "permit");
if (javaVersion == 24 || javaVersion == 25)
res.addDefault("--sun-misc-unsafe-memory-access=", "allow");
res.addDefault("-Dfml.ignoreInvalidMinecraftCertificates=", "true");
res.addDefault("-Dfml.ignorePatchDiscrepancies=", "true");
}