Compare commits

...
36 Commits
Author SHA1 Message Date
huangyuhui db29f97397 Update the link for crash reports 2018-06-17 18:22:22 +08:00
huangyuhui 573f41611d Force using Java 8 when launching Minecraft 1.13 or later 2018-06-17 18:13:47 +08:00
huanghongxun 28ed103ec0 Merge pull request #353 from yushijinhun/translations
完善 UI 提示消息
2018-06-17 17:45:59 +08:00
huanghongxun 5e37750f00 Merge pull request #352 from yushijinhun/refactor-update
添加 hmcl.version.override 选项用于调试更新功能
2018-06-17 17:40:07 +08:00
yushijinhun 5fe3cd9406 移除input.url.http错误提示 2018-06-17 17:28:12 +08:00
yushijinhun b00cc99408 BMCLAPI的链接使用https 2018-06-17 17:27:44 +08:00
yushijinhun 9a66c3bba9 完善翻译
中英文间补白、更正大小写、调整措辞
2018-06-17 17:25:22 +08:00
yushijinhun 546921b445 删除未被使用过的i18n条目 2018-06-17 16:40:22 +08:00
yushijinhun 8b698d3a4c 更新设置页面中依赖信息 2018-06-17 15:33:35 +08:00
yushijinhun c4608a949b 处理未捕获异常时立即进行log,防止异常被忽略
THROWABLE_SET改名为CAUGHT_EXCEPTIONS,使用并发安全集合
2018-06-17 15:04:31 +08:00
yushijinhun bba1c559a3 为连接authlib-injector服务器失败添加错误消息 2018-06-17 14:41:26 +08:00
yushijinhun bdee9ea72f 将获取验证服务器信息的代码移入AuthlibInjectorServer 2018-06-17 14:26:38 +08:00
yushijinhun 520923ba08 跳过更新时记录日志 2018-06-17 13:44:04 +08:00
yushijinhun 936a74597f 若版本号包含SNAPSHOT则也不更新
通过Gradle构建出来的JAR版本号为3.1.SNAPSHOT这样的
2018-06-17 13:40:09 +08:00
yushijinhun 97132745cc 增加hmcl.version.override选项用于覆写HMCL版本 2018-06-17 13:30:05 +08:00
yushijinhun 2bb30f65f3 在AppDataUpgrader中使用ReflectiveOperationException替代具体反射异常 2018-06-17 13:11:56 +08:00
huangyuhui ec04d55793 Fix transition 2018-06-17 12:05:39 +08:00
huangyuhui 88671db566 Use spinner instead of indeterminate progress bar 2018-06-17 12:05:25 +08:00
huanghongxun 538f501bc0 Merge pull request #351 from yushijinhun/refactor-yggdrasil
authlib-injector 部分重构
2018-06-17 10:34:47 +08:00
huangyuhui 3b75d41a2f 修改更新服务器连接 2018-06-17 10:34:13 +08:00
yushijinhun 110149d0e6 AuthlibInjectorAccountFactory的addtionalData改为AuthlibInjectorServer类型 2018-06-16 23:14:04 +08:00
yushijinhun 89b68781fa 修复添加验证服务器页面的错误信息不会被清除 2018-06-16 23:09:16 +08:00
yushijinhun 4cdc54b9c1 修复添加账号时未选中验证服务器会NPE 2018-06-16 23:06:21 +08:00
yushijinhun 7673595a43 AddAccountPane中服务器列表数据视图分离 2018-06-16 22:58:39 +08:00
yushijinhun 76a259c107 AuthlibInjectorAccount中存储AuthlibInjectorServer对象而不是url 2018-06-16 21:26:36 +08:00
yushijinhun 5f40d01fb4 在AuthlibInjectorAccountFactory中使用AuthlibInjectorServer而不是url 2018-06-16 21:26:36 +08:00
yushijinhun df29d47c0a workaround: 将Settings中的Config字段改为静态 2018-06-16 21:26:36 +08:00
yushijinhun 10d4084dde AuthlibInjectorServerInfo重命名为AuthlibInjectorServer
更正其它措辞:ip->url
2018-06-16 21:26:34 +08:00
yushijinhun ba8cdb1b42 移除AuthlibInjectorServersPage的spinner 2018-06-16 21:25:25 +08:00
yushijinhun 22732fee6a 存储AuthlibInjectorServerInfo对象而不是url字符串 2018-06-16 21:24:30 +08:00
yushijinhun bfbc4613b9 在添加authlib-injector服务器的过程中,传递AuthlibInjectorServerInfo而不是url 2018-06-16 20:33:48 +08:00
yushijinhun 92e5eecaf2 将Settings中DEFAULT_PROFILE, HOME_PROFILE字段移至Profiles 2018-06-16 20:30:14 +08:00
yushijinhun 593159f4a6 将DEFAULT_ICON移至Constants, 并使用懒加载
当类初始化时, JavaFX可能还未启动, 这将使图像加载失败
2018-06-16 20:10:10 +08:00
yushijinhun 53cad0e56a 在因无法读取而移除账户时记录日志 2018-06-16 20:10:10 +08:00
huangyuhui d61006dd67 Fix uncaught IOException 2018-06-13 23:40:16 +08:00
huangyuhui eb565285b2 Fix curse mod downloading 2018-06-10 20:05:36 +08:00
34 changed files with 583 additions and 633 deletions

No files matched your search

@@ -152,7 +152,7 @@ public final class Launcher extends Application {
public static final File HMCL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
public static final File LOG_DIRECTORY = new File(Launcher.HMCL_DIRECTORY, "logs");
public static final String VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@";
public static final String VERSION = System.getProperty("hmcl.version.override", "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@");
public static final String NAME = "HMCL";
public static final String TITLE = NAME + " " + VERSION;
public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
@@ -160,7 +160,7 @@ public final class Launcher extends Application {
public static final IUpgrader UPGRADER = new AppDataUpgrader();
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
public static final String UPDATE_SERVER = "http://47.101.47.110";
public static final String UPDATE_SERVER = "https://hmcl.huangyuhui.net";
public static final String CONTACT = UPDATE_SERVER + "/hmcl.php";
public static final String PUBLISH = "http://www.mcbbs.net/thread-142335-1-1.html";
}
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.launch.*;
import org.jackhuang.hmcl.mod.CurseCompletionTask;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.setting.LauncherVisibility;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Settings;
@@ -40,6 +41,7 @@ import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
@@ -76,7 +78,7 @@ public final class LauncherHelper {
}
private void launch0(Profile profile, Account account, String selectedVersion, File scriptFile) {
GameRepository repository = profile.getRepository();
HMCLGameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency();
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
VersionSetting setting = profile.getVersionSetting(selectedVersion);
@@ -90,7 +92,17 @@ public final class LauncherHelper {
return dependencyManager.checkGameCompletionAsync(version);
})
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
.then(new CurseCompletionTask(dependencyManager, selectedVersion))
.then(var -> {
try {
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
if ("Curse".equals(configuration.getType()))
return new CurseCompletionTask(dependencyManager, selectedVersion);
else
return null;
} catch (IOException e) {
return null;
}
})
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
.then(Task.of(Launcher.i18n("account.methods"), variables -> {
try {
@@ -179,10 +191,12 @@ public final class LauncherHelper {
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
boolean flag = false;
// Without onAccept called, the launching operation will be terminated.
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
JavaVersion java = setting.getJavaVersion();
if (java == null) {
Controllers.dialog(Launcher.i18n("launch.wrong_javadir"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
Controllers.dialog(Launcher.i18n("launch.wrong_javadir"), Launcher.i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
setting.setJava(null);
setting.setDefaultJavaPath(null);
java = JavaVersion.fromCurrentEnvironment();
@@ -190,10 +204,19 @@ public final class LauncherHelper {
}
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8) {
Controllers.dialog(Launcher.i18n("launch.advice.newer_java"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
// Minecraft 1.13 and later versions only support Java 8 or later.
// Terminate launching operation.
Controllers.dialog(Launcher.i18n("launch.advice.java8_1_13"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
} else {
// Most mods require Java 8 or later version.
Controllers.dialog(Launcher.i18n("launch.advice.newer_java"), Launcher.i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
}
flag = true;
}
// LaunchWrapper will crash because of assuming the system class loader is an instance of URLClassLoader.
// cpw has claimed that he will make MinecraftForge of 1.13 and later versions able to run on Java 9.
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
Controllers.dialog(Launcher.i18n("launch.advice.java9"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
flag = true;
@@ -206,6 +229,8 @@ public final class LauncherHelper {
}
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
setting.getMaxMemory() > 1.5 * 1024) {
// 1.5 * 1024 is an inaccurate number.
// Actual memory limit depends on operating system and memory.
Controllers.dialog(Launcher.i18n("launch.advice.too_large_memory_for_32bit"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
@@ -59,12 +59,12 @@ public final class ModpackHelper {
throw new UnsupportedModpackException(file.toString());
}
public static <T> ModpackConfiguration<T> readModpackConfiguration(File file) throws IOException {
public static ModpackConfiguration<?> readModpackConfiguration(File file) throws IOException {
if (!file.exists())
throw new FileNotFoundException(file.getPath());
else
try {
return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<T>>() {
return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
}.getType());
} catch (JsonParseException e) {
throw new IOException("Malformed modpack configuration");
@@ -23,26 +23,23 @@ import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorBuildInfo;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerResponse;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.NetworkUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
/**
@@ -58,11 +55,9 @@ public final class Accounts {
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = mapOf(
pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
pair(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector, Accounts::getOrCreateAuthlibInjectorServer))
);
private static final Map<String, String> AUTHLIB_INJECTOR_SERVER_NAMES = new HashMap<>();
public static String getAccountType(Account account) {
if (account instanceof OfflineAccount) return OFFLINE_ACCOUNT_KEY;
else if (account instanceof AuthlibInjectorAccount) return AUTHLIB_INJECTOR_ACCOUNT_KEY;
@@ -94,17 +89,23 @@ public final class Accounts {
return jar.getAbsolutePath();
}
public static String getAuthlibInjectorServerName(String serverIp) throws Exception {
if (AUTHLIB_INJECTOR_SERVER_NAMES.containsKey(serverIp))
return AUTHLIB_INJECTOR_SERVER_NAMES.get(serverIp);
else {
AuthlibInjectorServerResponse response = Constants.GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(serverIp)), AuthlibInjectorServerResponse.class);
AUTHLIB_INJECTOR_SERVER_NAMES.put(serverIp, response.getMeta().getServerName());
return response.getMeta().getServerName();
}
}
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
return Settings.SETTINGS.authlibInjectorServers.stream()
.filter(server -> url.equals(server.getUrl()))
.findFirst()
.orElseGet(() -> {
// this usually happens when migrating data from an older version
AuthlibInjectorServer server;
try {
server = AuthlibInjectorServer.fetchServerInfo(url);
LOG.info("Migrated authlib injector server " + server);
} catch (IOException e) {
server = new AuthlibInjectorServer(url, url);
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
}
public static TaskResult<String> getAuthlibInjectorServerNameAsync(AuthlibInjectorAccount account) {
return Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(account.getServerBaseURL()));
Settings.SETTINGS.authlibInjectorServers.add(server);
return server;
});
}
}
@@ -17,11 +17,12 @@
*/
package org.jackhuang.hmcl.setting;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeMap;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import com.google.gson.annotations.SerializedName;
import javafx.beans.property.BooleanProperty;
@@ -35,7 +36,6 @@ import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
public final class Config implements Cloneable {
@@ -99,8 +99,7 @@ public final class Config implements Cloneable {
@SerializedName("logLines")
public final IntegerProperty logLines = new SimpleIntegerProperty(100);
@SerializedName("authlibInjectorServerURLs")
public final ObservableSet<String> authlibInjectorServerURLs = FXCollections.observableSet(new HashSet<>());
public final ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
@Override
public Config clone() {
@@ -20,14 +20,18 @@ package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.Launcher;
public final class Profiles {
public static final String DEFAULT_PROFILE = "Default";
public static final String HOME_PROFILE = "Home";
private Profiles() {
}
public static String getProfileDisplayName(Profile profile) {
switch (profile.getName()) {
case Settings.DEFAULT_PROFILE:
case Profiles.DEFAULT_PROFILE:
return Launcher.i18n("profile.default");
case Settings.HOME_PROFILE:
case Profiles.HOME_PROFILE:
return Launcher.i18n("profile.home");
default:
return profile.getName();
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.setting;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
@@ -35,6 +36,7 @@ import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.event.*;
import org.jackhuang.hmcl.task.Schedulers;
@@ -51,7 +53,10 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG;
public class Settings {
public static final Gson GSON = new GsonBuilder()
@@ -65,15 +70,12 @@ public class Settings {
.setPrettyPrinting()
.create();
public static final String DEFAULT_PROFILE = "Default";
public static final String HOME_PROFILE = "Home";
public static final String SETTINGS_FILE_NAME = "hmcl.json";
public static final File SETTINGS_FILE = new File(SETTINGS_FILE_NAME).getAbsoluteFile();
public static final Settings INSTANCE = new Settings();
public static final Config SETTINGS = initSettings();
private final Config SETTINGS = initSettings();
public static final Settings INSTANCE = new Settings();
private final Map<String, Account> accounts = new ConcurrentHashMap<>();
@@ -84,7 +86,7 @@ public class Settings {
Map<Object, Object> settings = iterator.next();
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(tryCast(settings.get("type"), String.class).orElse(""));
if (factory == null) {
// unrecognized account type, so remove it.
LOG.warning("Unrecognized account type, removing: " + settings);
iterator.remove();
continue;
}
@@ -93,7 +95,7 @@ public class Settings {
try {
account = factory.fromStorage(settings, getProxy());
} catch (Exception e) {
// storage is malformed, delete.
LOG.log(Level.WARNING, "Malformed account storage, removing: " + settings, e);
iterator.remove();
continue;
}
@@ -101,7 +103,8 @@ public class Settings {
accounts.put(Accounts.getAccountId(account), account);
}
checkAuthlibInjectorAccounts();
SETTINGS.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
checkProfileMap();
save();
@@ -115,7 +118,7 @@ public class Settings {
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
}
private Config initSettings() {
private static Config initSettings() {
Config c = new Config();
if (SETTINGS_FILE.exists())
try {
@@ -305,30 +308,17 @@ public class Settings {
* AUTHLIB INJECTORS *
****************************************/
public Set<String> getAuthlibInjectorServerURLs() {
return SETTINGS.authlibInjectorServerURLs;
}
public void removeAuthlibInjectorServerURL(String serverURL) {
SETTINGS.authlibInjectorServerURLs.remove(serverURL);
checkAuthlibInjectorAccounts();
save();
}
public void addAuthlibInjectorServerURL(String serverURL) {
SETTINGS.authlibInjectorServerURLs.add(serverURL);
save();
}
private void checkAuthlibInjectorAccounts() {
for (Account account : getAccounts()) {
if (account instanceof AuthlibInjectorAccount) {
AuthlibInjectorAccount injectorAccount = (AuthlibInjectorAccount) account;
if (!SETTINGS.authlibInjectorServerURLs.contains(injectorAccount.getServerBaseURL()))
deleteAccount(account);
}
}
/**
* After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.
* This method performs a check and removes the dangling accounts.
* Don't call this before {@link #migrateAuthlibInjectorServers()} is called, otherwise old data would be lost.
*/
private void removeDanglingAuthlibInjectorAccounts() {
accounts.values().stream()
.filter(AuthlibInjectorAccount.class::isInstance)
.filter(it -> !SETTINGS.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
.collect(toList())
.forEach(this::deleteAccount);
}
/****************************************
@@ -561,8 +551,8 @@ public class Settings {
private void checkProfileMap() {
if (getProfileMap().isEmpty()) {
getProfileMap().put(DEFAULT_PROFILE, new Profile(DEFAULT_PROFILE));
getProfileMap().put(HOME_PROFILE, new Profile(HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY));
getProfileMap().put(Profiles.DEFAULT_PROFILE, new Profile(Profiles.DEFAULT_PROFILE));
getProfileMap().put(Profiles.HOME_PROFILE, new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY));
}
}
@@ -34,7 +34,6 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.AccountHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Schedulers;
@@ -80,8 +79,7 @@ public class AccountPage extends StackPane implements DecoratorPage {
FXUtils.setLimitWidth(this, 300);
if (account instanceof AuthlibInjectorAccount) {
Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account)
.subscribe(Schedulers.javafx(), variables -> lblServer.setText(variables.get("serverName")));
lblServer.setText(((AuthlibInjectorAccount) account).getServer().getName());
FXUtils.setLimitHeight(this, 182);
} else {
componentList.removeChildren(paneServer);
@@ -33,6 +33,7 @@ import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
@@ -45,17 +46,19 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.IconedItem;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Logging;
import java.util.Collection;
import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class AddAccountPane extends StackPane {
@@ -64,25 +67,26 @@ public class AddAccountPane extends StackPane {
@FXML private Label lblCreationWarning;
@FXML private Label lblPassword;
@FXML private JFXComboBox<String> cboType;
@FXML private JFXComboBox<TwoLineListItem> cboServers;
@FXML private JFXComboBox<AuthlibInjectorServer> cboServers;
@FXML private Label lblInjectorServer;
@FXML private Hyperlink linkManageInjectorServers;
@FXML private JFXDialogLayout layout;
@FXML private JFXButton btnAccept;
@FXML private StackPane acceptPane;
@FXML private JFXSpinner spinnerAccept;
@FXML private SpinnerPane acceptPane;
private final Consumer<Region> finalization;
private final TransitionHandler transitionHandler;
public AddAccountPane(Consumer<Region> finalization) {
this.finalization = finalization;
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
transitionHandler = new TransitionHandler(acceptPane);
acceptPane.getChildren().setAll(btnAccept);
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
cboServers.setItems(Settings.SETTINGS.authlibInjectorServers);
loadServers();
// workaround: otherwise the combox will be black
if (!cboServers.getItems().isEmpty())
cboServers.getSelectionModel().select(0);
cboType.getItems().setAll(Launcher.i18n("account.methods.offline"), Launcher.i18n("account.methods.yggdrasil"), Launcher.i18n("account.methods.authlib_injector"));
cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> {
@@ -95,10 +99,6 @@ public class AddAccountPane extends StackPane {
});
cboType.getSelectionModel().select(0);
// These two lines can eliminate black, don't know why.
cboServers.getItems().setAll(new TwoLineListItem("", ""));
cboServers.getSelectionModel().select(0);
txtPassword.setOnAction(e -> onCreationAccept());
txtUsername.setOnAction(e -> onCreationAccept());
txtUsername.getValidators().add(new Validator(Launcher.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
@@ -111,62 +111,53 @@ public class AddAccountPane extends StackPane {
btnAccept.setDisable(!txtUsername.validate() || (cboType.getSelectionModel().getSelectedIndex() != 0 && !txtPassword.validate()));
}
private void loadServers() {
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
.flatMap(serverURL -> {
try {
return Stream.of(new TwoLineListItem(Accounts.getAuthlibInjectorServerName(serverURL), serverURL));
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e);
return Stream.empty();
}
})
.collect(Collectors.toList()))
.subscribe(Task.of(Schedulers.javafx(), variables -> {
cboServers.getItems().setAll(variables.<Collection<TwoLineListItem>>get("list"));
if (!cboServers.getItems().isEmpty())
cboServers.getSelectionModel().select(0);
}));
}
private void showSpinner() {
transitionHandler.setContent(spinnerAccept, ContainerAnimations.FADE.getAnimationProducer());
}
private void hideSpinner() {
transitionHandler.setContent(btnAccept, ContainerAnimations.FADE.getAnimationProducer());
}
@FXML
private void onCreationAccept() {
int type = cboType.getSelectionModel().getSelectedIndex();
String username = txtUsername.getText();
String password = txtPassword.getText();
String apiRoot = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem()).map(TwoLineListItem::getSubtitle).orElse(null);
showSpinner();
lblCreationWarning.setText("");
Task.ofResult("create_account", () -> {
AccountFactory<?> factory;
switch (type) {
case 0: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY); break;
case 1: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY); break;
case 2: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY); break;
default: throw new Error();
}
Object addtionalData;
return factory.create(new Selector(), username, password, apiRoot, Settings.INSTANCE.getProxy());
}).finalized(Schedulers.javafx(), variables -> {
Settings.INSTANCE.addAccount(variables.get("create_account"));
hideSpinner();
finalization.accept(this);
}, exception -> {
if (exception instanceof NoSelectedCharacterException) {
finalization.accept(this);
} else {
lblCreationWarning.setText(accountException(exception));
}
hideSpinner();
}).start();
int type = cboType.getSelectionModel().getSelectedIndex();
AccountFactory<?> factory;
switch (type) {
case 0:
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY);
addtionalData = null;
break;
case 1:
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY);
addtionalData = null;
break;
case 2:
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY);
Optional<AuthlibInjectorServer> server = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem());
if (server.isPresent()) {
addtionalData = server.get();
} else {
lblCreationWarning.setText(Launcher.i18n("account.failed.no_selected_server"));
return;
}
break;
default:
throw new Error();
}
acceptPane.showSpinner();
lblCreationWarning.setText("");
Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, addtionalData, Settings.INSTANCE.getProxy()))
.finalized(Schedulers.javafx(), variables -> {
Settings.INSTANCE.addAccount(variables.get("create_account"));
acceptPane.hideSpinner();
finalization.accept(this);
}, exception -> {
if (exception instanceof NoSelectedCharacterException) {
finalization.accept(this);
} else {
lblCreationWarning.setText(accountException(exception));
}
acceptPane.hideSpinner();
}).start();
}
@FXML
@@ -224,13 +215,13 @@ public class AddAccountPane extends StackPane {
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4, Settings.INSTANCE.getProxy());
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e);
image = FXUtils.DEFAULT_ICON;
image = null;
}
ImageView portraitView = new ImageView();
portraitView.setSmooth(false);
if (image == FXUtils.DEFAULT_ICON)
portraitView.setImage(FXUtils.DEFAULT_ICON);
else {
if (image == null) {
portraitView.setImage(Constants.DEFAULT_ICON.get());
} else {
portraitView.setImage(image);
portraitView.setViewport(AccountHelper.getViewport(4));
}
@@ -23,19 +23,19 @@ import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.setting.Theme;
import java.util.function.Consumer;
public final class AuthlibInjectorServerItem extends BorderPane {
private final AuthlibInjectorServerInfo info;
private final AuthlibInjectorServer server;
private final Label lblServerName = new Label();
private final Label lblServerIp = new Label();
public AuthlibInjectorServerItem(AuthlibInjectorServerInfo info, Consumer<AuthlibInjectorServerItem> deleteCallback) {
this.info = info;
public AuthlibInjectorServerItem(AuthlibInjectorServer server, Consumer<AuthlibInjectorServerItem> deleteCallback) {
this.server = server;
lblServerName.setStyle("-fx-font-size: 15;");
lblServerIp.setStyle("-fx-font-size: 10;");
@@ -54,11 +54,11 @@ public final class AuthlibInjectorServerItem extends BorderPane {
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
lblServerName.setText(info.getServerName());
lblServerIp.setText(info.getServerIp());
lblServerName.setText(server.getName());
lblServerIp.setText(server.getUrl());
}
public AuthlibInjectorServerInfo getInfo() {
return info;
public AuthlibInjectorServer getServer() {
return server;
}
}
@@ -4,49 +4,47 @@ import com.jfoenix.controls.*;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.NetworkUtils;
import java.util.Collection;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
import java.io.IOException;
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("account.injector.server"));
@FXML private ScrollPane scrollPane;
@FXML private StackPane addServerContainer;
@FXML private Label lblServerIp;
@FXML private Label lblServerUrl;
@FXML private Label lblServerName;
@FXML private Label lblCreationWarning;
@FXML private Label lblServerWarning;
@FXML private VBox listPane;
@FXML private JFXTextField txtServerIp;
@FXML private JFXTextField txtServerUrl;
@FXML private JFXDialogLayout addServerPane;
@FXML private JFXDialogLayout confirmServerPane;
@FXML private JFXDialog dialog;
@FXML private StackPane contentPane;
@FXML private JFXSpinner spinner;
@FXML private JFXProgressBar progressBar;
@FXML private SpinnerPane nextPane;
@FXML private JFXButton btnAddNext;
private final TransitionHandler transitionHandler;
private AuthlibInjectorServer serverBeingAdded;
{
FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
FXUtils.smoothScrolling(scrollPane);
@@ -55,51 +53,35 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
getChildren().remove(dialog);
dialog.setDialogContainer(this);
txtServerIp.textProperty().addListener((a, b, newValue) ->
btnAddNext.setDisable(!txtServerIp.validate()));
txtServerUrl.textProperty().addListener((a, b, newValue) ->
btnAddNext.setDisable(!txtServerUrl.validate()));
loading();
reload();
}
private void removeServer(AuthlibInjectorServerItem item) {
Settings.INSTANCE.removeAuthlibInjectorServerURL(item.getInfo().getServerIp());
loading();
Settings.SETTINGS.authlibInjectorServers.remove(item.getServer());
reload();
}
private void loading() {
getChildren().remove(contentPane);
spinner.setVisible(true);
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
.flatMap(serverURL -> {
try {
return Stream.of(new AuthlibInjectorServerItem(new AuthlibInjectorServerInfo(serverURL, Accounts.getAuthlibInjectorServerName(serverURL)), this::removeServer));
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e);
return Stream.empty();
}
})
.collect(Collectors.toList()))
.subscribe(Task.of(Schedulers.javafx(), variables -> {
listPane.getChildren().setAll(variables.<Collection<? extends Node>>get("list"));
loadingCompleted();
}));
}
private void loadingCompleted() {
getChildren().add(contentPane);
spinner.setVisible(false);
if (Settings.INSTANCE.getAuthlibInjectorServerURLs().isEmpty())
private void reload() {
listPane.getChildren().setAll(
Settings.SETTINGS.authlibInjectorServers.stream()
.map(server -> new AuthlibInjectorServerItem(server, this::removeServer))
.collect(toList()));
if (Settings.SETTINGS.authlibInjectorServers.isEmpty()) {
onAdd();
}
}
@FXML
private void onAdd() {
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
txtServerIp.setText("");
txtServerUrl.setText("");
txtServerUrl.resetValidation();
lblCreationWarning.setText("");
addServerPane.setDisable(false);
progressBar.setVisible(false);
nextPane.hideSpinner();
dialog.show();
}
@@ -110,26 +92,28 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
@FXML
private void onAddNext() {
String serverIp = txtServerIp.getText();
progressBar.setVisible(true);
String url = fixInputUrl(txtServerUrl.getText());
nextPane.showSpinner();
addServerPane.setDisable(true);
Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(serverIp))
.finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
progressBar.setVisible(false);
addServerPane.setDisable(false);
Task.of(() -> {
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
nextPane.hideSpinner();
addServerPane.setDisable(false);
if (isDependentsSucceeded) {
lblServerName.setText(variables.get("serverName"));
lblServerIp.setText(txtServerIp.getText());
if (isDependentsSucceeded) {
lblServerName.setText(serverBeingAdded.getName());
lblServerUrl.setText(serverBeingAdded.getUrl());
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverIp).getProtocol()));
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
} else
lblCreationWarning.setText(variables.<Exception>get("lastException").getLocalizedMessage());
}).start();
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
} else {
lblCreationWarning.setText(resolveFetchExceptionMessage(variables.<Exception>get("lastException")));
}
}).start();
}
@@ -140,11 +124,10 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
@FXML
private void onAddFinish() {
String ip = txtServerIp.getText();
if (!ip.endsWith("/"))
ip += "/";
Settings.INSTANCE.addAuthlibInjectorServerURL(ip);
loading();
if (!Settings.INSTANCE.SETTINGS.authlibInjectorServers.contains(serverBeingAdded)) {
Settings.INSTANCE.SETTINGS.authlibInjectorServers.add(serverBeingAdded);
}
reload();
dialog.close();
}
@@ -160,4 +143,19 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
public void setTitle(String title) {
this.title.set(title);
}
private String fixInputUrl(String url) {
if (!url.endsWith("/")) {
url += "/";
}
return url;
}
private String resolveFetchExceptionMessage(Throwable exception) {
if (exception instanceof IOException) {
return Launcher.i18n("account.failed.connect_injector_server");
} else {
return exception.getClass() + ": " + exception.getLocalizedMessage();
}
}
}
@@ -23,6 +23,7 @@ import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
@@ -30,18 +31,22 @@ import javafx.beans.value.WeakChangeListener;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;
import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.util.*;
import java.io.File;
@@ -52,6 +57,7 @@ import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
@@ -68,6 +74,10 @@ public final class FXUtils {
}
}
public static InvalidationListener onInvalidating(Runnable action) {
return arg -> action.run();
}
public static <T> void onChange(ObservableValue<T> value, Consumer<T> consumer) {
value.addListener((a, b, c) -> consumer.accept(c));
}
@@ -402,7 +412,32 @@ public final class FXUtils {
}
}
public static final Image DEFAULT_ICON = new Image("/assets/img/icon.png");
public static <T> StringConverter<T> stringConverter(Function<T, String> func) {
return new StringConverter<T>() {
@Override
public String toString(T object) {
return object == null ? "" : func.apply(object);
}
@Override
public T fromString(String string) {
throw new UnsupportedOperationException();
}
};
}
public static <T> Callback<ListView<T>, ListCell<T>> jfxListCellFactory(Function<T, Node> graphicBuilder) {
return view -> new JFXListCell<T>() {
@Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setGraphic(graphicBuilder.apply(item));
}
}
};
}
public static final Interpolator SINE = new Interpolator() {
@Override
@@ -186,9 +186,9 @@ public final class LeftPaneController {
} else
item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4));
if (account instanceof AuthlibInjectorAccount)
Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account)
.subscribe(Schedulers.javafx(), variables -> FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(variables.get("serverName"))));
if (account instanceof AuthlibInjectorAccount) {
FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName()));
}
if (selectedAccount == account)
ripplerContainer.setSelected(true);
@@ -264,7 +264,7 @@ public final class VersionSettingsController {
if (iconFile.exists())
iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath()));
else
iconPickerItem.setImage(FXUtils.DEFAULT_ICON);
iconPickerItem.setImage(Constants.DEFAULT_ICON.get());
FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
}
}
@@ -93,7 +93,7 @@ public final class TransitionHandler implements AnimationHandler {
} else
previousNode = NULL;
if (previousNode == currentNode)
if (previousNode == newView)
previousNode = NULL;
currentNode = newView;
@@ -0,0 +1,61 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXSpinner;
import javafx.beans.DefaultProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
@DefaultProperty("content")
public class SpinnerPane extends StackPane {
private final TransitionHandler transitionHandler = new TransitionHandler(this);
private final JFXSpinner spinner = new JFXSpinner();
private final StackPane contentPane = new StackPane();
private final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
public SpinnerPane() {
getChildren().setAll(contentPane);
content.addListener((a, b, newValue) -> contentPane.getChildren().setAll(newValue));
}
public void showSpinner() {
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
}
public void hideSpinner() {
transitionHandler.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer());
}
public Node getContent() {
return content.get();
}
public ObjectProperty<Node> contentProperty() {
return content;
}
public void setContent(Node content) {
this.content.set(content);
}
}
@@ -69,7 +69,7 @@ public final class DownloadWizardProvider implements WizardProvider {
return null;
File selected = tryCast(settings.get(ModpackPage.MODPACK_FILE), File.class).orElse(null);
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_CURSEFORGE_MANIFEST), Modpack.class).orElse(null);
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_MANIFEST), Modpack.class).orElse(null);
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
if (selected == null || modpack == null || name == null) return null;
@@ -90,7 +90,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
try {
manifest = ModpackHelper.readModpackManifest(selectedFile);
controller.getSettings().put(MODPACK_CURSEFORGE_MANIFEST, manifest);
controller.getSettings().put(MODPACK_MANIFEST, manifest);
lblName.setText(manifest.getName());
lblVersion.setText(manifest.getVersion());
lblAuthor.setText(manifest.getAuthor());
@@ -130,5 +130,5 @@ public final class ModpackPage extends StackPane implements WizardPage {
public static final String MODPACK_FILE = "MODPACK_FILE";
public static final String MODPACK_NAME = "MODPACK_NAME";
public static final String MODPACK_CURSEFORGE_MANIFEST = "CURSEFORGE_MANIFEST";
public static final String MODPACK_MANIFEST = "MODPACK_MANIFEST";
}
@@ -33,7 +33,6 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
@@ -49,7 +48,7 @@ import java.util.zip.GZIPInputStream;
*/
public class AppDataUpgrader extends IUpgrader {
private void launchNewerVersion(List<String> args, File jar) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InvocationTargetException, IllegalAccessException {
private void launchNewerVersion(List<String> args, File jar) throws IOException, ReflectiveOperationException {
try (JarFile jarFile = new JarFile(jar)) {
String mainClass = jarFile.getManifest().getMainAttributes().getValue("Main-Class");
if (mainClass == null)
@@ -91,7 +90,7 @@ public class AppDataUpgrader extends IUpgrader {
}
} catch (JsonParseException ex) {
f.delete();
} catch (IOException | NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException | ClassNotFoundException t) {
} catch (IOException | ReflectiveOperationException t) {
Logging.LOG.log(Level.SEVERE, "Unable to execute newer version application", t);
AppDataUpgraderPackGzTask.HMCL_VER_FILE.delete(); // delete version json, let HMCL re-download the newer version.
}
@@ -27,10 +27,11 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.VersionNumber;
import static org.jackhuang.hmcl.util.Logging.LOG;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
@@ -78,8 +79,10 @@ public final class UpdateChecker {
@Override
public void execute() throws Exception {
if (Launcher.VERSION.contains("@"))
if (isDevelopmentVersion(Launcher.VERSION)) {
LOG.info("Current version is a development version, skip updating");
return;
}
if (value == null) {
versionString = http.getResult();
@@ -87,7 +90,7 @@ public final class UpdateChecker {
}
if (value == null) {
Logging.LOG.warning("Unable to check update...");
LOG.warning("Unable to check update...");
if (showMessage)
MessageBox.show(Launcher.i18n("update.failed"));
} else if (base.compareTo(value) < 0)
@@ -103,6 +106,11 @@ public final class UpdateChecker {
};
}
private boolean isDevelopmentVersion(String version) {
return version.contains("@") || // eg. @HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@
version.contains("SNAPSHOT"); // eg. 3.1.SNAPSHOT
}
/**
* Get the <b>cached</b> newest version number, use "process" method to
* download!
@@ -124,12 +132,13 @@ public final class UpdateChecker {
return new TaskResult<Map<String, String>>() {
@Override
public void execute() {
if (download_link == null)
if (download_link == null) {
try {
download_link = Constants.GSON.<Map<String, String>>fromJson(NetworkUtils.doGet(NetworkUtils.toURL(Launcher.UPDATE_SERVER + "/hmcl/update_link.php")), Map.class);
} catch (JsonSyntaxException | IOException e) {
Logging.LOG.log(Level.SEVERE, "Failed to get update link.", e);
LOG.log(Level.SEVERE, "Failed to get update link.", e);
}
}
setResult(download_link);
}
@@ -22,11 +22,16 @@ import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.ui.CrashWindow;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import static java.util.Collections.newSetFromMap;
import static org.jackhuang.hmcl.util.Logging.LOG;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
/**
@@ -34,7 +39,7 @@ import java.util.logging.Level;
*/
public class CrashReporter implements Thread.UncaughtExceptionHandler {
private static final HashMap<String, String> SOURCE = new HashMap<String, String>() {
private static final Map<String, String> SOURCE = new HashMap<String, String>() {
{
put("javafx.fxml.LoadException", Launcher.i18n("crash.NoClassDefFound"));
put("Location is not set", Launcher.i18n("crash.NoClassDefFound"));
@@ -59,11 +64,11 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
if (s.contains(entry.getKey())) {
if (StringUtils.isNotBlank(entry.getValue())) {
String info = entry.getValue();
Logging.LOG.severe(info);
LOG.severe(info);
try {
MessageBox.show(info);
} catch (Throwable t) {
Logging.LOG.log(Level.SEVERE, "Unable to show message", t);
LOG.log(Level.SEVERE, "Unable to show message", t);
}
}
return false;
@@ -71,17 +76,21 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
return true;
}
private static Set<String> CAUGHT_EXCEPTIONS = newSetFromMap(new ConcurrentHashMap<>());
@Override
public void uncaughtException(Thread t, Throwable e) {
String stackTrace = StringUtils.getStackTrace(e);
if (!stackTrace.contains("org.jackhuang"))
return;
if (THROWABLE_SET.contains(stackTrace))
return;
THROWABLE_SET.add(stackTrace);
LOG.log(Level.SEVERE, "Uncaught exception in thread " + t.getName(), e);
try {
String stackTrace = StringUtils.getStackTrace(e);
if (!stackTrace.contains("org.jackhuang"))
return;
if (CAUGHT_EXCEPTIONS.contains(stackTrace))
return;
CAUGHT_EXCEPTIONS.add(stackTrace);
String text = "---- Hello Minecraft! Crash Report ----\n" +
" Version: " + Launcher.VERSION + "\n" +
" Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\n" +
@@ -93,21 +102,18 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n";
Logging.LOG.log(Level.SEVERE, text);
LOG.log(Level.SEVERE, text);
if (checkThrowable(e)) {
Platform.runLater(() -> new CrashWindow(text).show());
if (!Launcher.UPDATE_CHECKER.isOutOfDate())
reportToServer(text);
}
} catch (Throwable ex) {
Logging.LOG.log(Level.SEVERE, "Unable to caught exception", ex);
Logging.LOG.log(Level.SEVERE, "There is the original exception", e);
} catch (Throwable handlingException) {
LOG.log(Level.SEVERE, "Unable to handle uncaught exception", handlingException);
}
}
private static final HashSet<String> THROWABLE_SET = new HashSet<>();
private void reportToServer(final String text) {
Thread t = new Thread(() -> {
HashMap<String, String> map = new HashMap<>();
@@ -115,11 +121,11 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
map.put("version", Launcher.VERSION);
map.put("log", Logging.getLogs());
try {
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/crash.php"), map);
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://hmcl.huangyuhui.net/hmcl/crash.php"), map);
if (StringUtils.isNotBlank(response))
Logging.LOG.log(Level.SEVERE, "Crash server response: " + response);
LOG.log(Level.SEVERE, "Crash server response: " + response);
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Unable to post HMCL server.", ex);
LOG.log(Level.SEVERE, "Unable to post HMCL server.", ex);
}
});
t.setDaemon(true);
@@ -919,6 +919,14 @@
-fx-stroke-width: 3.0;
}
.small-spinner-pane .jfx-spinner {
-jfx-radius: 10;
}
.small-spinner-pane .jfx-spinner > .arc {
-fx-stroke-width: 3.0;
}
.second-spinner {
-jfx-radius: 30;
}
@@ -5,6 +5,7 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import org.jackhuang.hmcl.ui.FXUtils?>
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
<fx:root xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
type="StackPane">
@@ -56,10 +57,9 @@
</body>
<actions>
<Label fx:id="lblCreationWarning"/>
<StackPane fx:id="acceptPane">
<SpinnerPane fx:id="acceptPane" styleClass="small-spinner-pane">
<JFXButton fx:id="btnAccept" onMouseClicked="#onCreationAccept" text="%button.ok" styleClass="dialog-accept"/>
<JFXSpinner fx:id="spinnerAccept" styleClass="small-spinner" />
</StackPane>
</SpinnerPane>
<JFXButton onMouseClicked="#onCreationCancel" text="%button.cancel" styleClass="dialog-cancel"/>
</actions>
</JFXDialogLayout>
@@ -5,10 +5,10 @@
<?import javafx.scene.layout.*?>
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
<?import java.lang.String?>
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
<JFXSpinner fx:id="spinner" style="-fx-radius:16" styleClass="materialDesign-purple, first-spinner" />
<StackPane fx:id="contentPane">
<ScrollPane fx:id="scrollPane" fitToWidth="true" fitToHeight="true">
<VBox fx:id="listPane" spacing="10" style="-fx-padding: 20 20 70 20;">
@@ -32,11 +32,9 @@
<Label text="%account.injector.add" />
</heading>
<body>
<JFXTextField fx:id="txtServerIp" promptText="%account.injector.server_ip">
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url">
<validators>
<URLValidator message="%input.url">
</URLValidator>
<URLValidator message="%input.url.http">
<protocols>
<String fx:value="http" />
<String fx:value="https" />
@@ -48,7 +46,9 @@
<actions>
<Label fx:id="lblCreationWarning" />
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
</SpinnerPane>
</actions>
</JFXDialogLayout>
@@ -62,10 +62,10 @@
<ColumnConstraints maxWidth="100" />
<ColumnConstraints />
</columnConstraints>
<Label text="%account.injector.server_ip" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label text="%account.injector.server_url" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<Label fx:id="lblServerIp" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label fx:id="lblServerUrl" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label fx:id="lblServerName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label fx:id="lblServerWarning" text="%account.injector.http" style="-fx-text-fill: red;" GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.columnIndex="0" />
@@ -78,7 +78,6 @@
</actions>
</JFXDialogLayout>
</StackPane>
<JFXProgressBar fx:id="progressBar" visible="false" StackPane.alignment="TOP_CENTER" />
</StackPane>
</JFXDialog>
</fx:root>
@@ -20,9 +20,10 @@ about.copyright.statement=Copyright (c) 2018 huangyuhui.
about.author=Author
about.author.statement=huanghongxun ([email protected])
about.thanks_to=Thanks to
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors.
about.dependency=Dependency
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors who participated in this project via issues, pull requests, etc.
about.dependency=Dependencies
# Due to space limitations, only first authors are listed
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)
about.claim=Statement
about.claim.statement=Minecraft is copyrighted by Mojang AB. We are not responsible for any copyright infringement arising from the use of this software.
about.open_source=Open Source
@@ -31,19 +32,20 @@ about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
account=Accounts
account.character=character
account.choose=Choose a character
account.current=Current
account.create=Create a new account
account.email=Email
account.failed.connect_authentication_server=Cannot connect the authentication server. Check your network.
account.failed.invalid_credentials=Incorrect password. Or forbidden to log in temporarily.
account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your network.
account.failed.invalid_credentials=Incorrect password, or you are forbidden to login temporarily.
account.failed.invalid_password=Invalid password
account.failed.invalid_token=Please log out and re-input your password to log in.
account.failed.invalid_token=Please log out and re-input your password to login.
account.failed.no_charactor=No character in this account.
account.failed.no_selected_server=No authentication server is selected.
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
account.injector.add=Add authentication server
account.injector.manage=Manage authentication servers
account.injector.http=Warning: This server is using HTTP, which will cause your password be transmitted in clear text.
account.injector.server=Auth Server
account.injector.server_ip=Server URL
account.injector.server_url=Server URL
account.injector.server_name=Server Name
account.methods=Login Type
account.methods.authlib_injector=authlib-injector
@@ -59,66 +61,30 @@ archive.game_version=Game
archive.version=Version
assets.download=Download assets
assets.download_all=Check for the completion of assets
assets.failed=Failed to get the list, try again.
assets.failed_download=Failed to download assets, may cause no sounds and language files.
assets.download_all=Check the integrity of assets
button.about=About
button.cancel=Cancel
button.clear=Clear
button.close=Close
button.copy=Copy
button.delete=Delete
button.download=Download
button.edit=Edit
button.explore=Explore
button.info=Info
button.install=Install
button.more=More
button.no=No
button.ok=OK
button.others=Others
button.preview=Preview
button.refresh=Refresh
button.retry=Retry
button.save=Save
button.test=Test
button.yes=Yes
color.blue=Blue
color.dark_blue=Dark Blue
color.green=Green
color.orange=Orange
color.purple=Purple
color.red=Red
color.recent=Recommended
color.custom=Custom Color
crash.NoClassDefFound=Please check "HMCL" software is complete.
crash.advice.ClassNotFoundException=Minecraft or mods are incomplete. Retry if there are some libraries that have not downloaded or update your game and mods! Or you can try Game Settings -> Manage (Version) -> Delete library files to solve the problem.
crash.advice.ConcurrentModificationException=Maybe your Java is newer than 1.8.0_11, you could downgrade to Java 7.
crash.advice.LWJGLException=Maybe your video driver does not work well, please update your video driver.
crash.advice.NoSuchFieldError=Minecraft or mods are incomplete. Retry if there are some libraries that have not downloaded or update your game and mods!
crash.advice.OpenGL=Maybe drivers caused problems.
crash.advice.OutOfMemoryError=The maximum memory of JVM is too small, please modify it.
crash.advice.SecurityException=Maybe you have modified minecraft.jar but have not removed the META-INF.
crash.advice.UnsupportedClassVersionError=Maybe your java is too old, try to update the java.
crash.advice.no=No advice.
crash.advice.no_lwjgl=Maybe drivers caused problems.
crash.advice.otherwise=Maybe mods caused problems.
crash.error=Minecraft has crashed.
crash.launcher=Launcher has crashed!
crash.minecraft=Minecraft has crashed!
crash.user_fault=Your OS or Java environment may not be properly installed resulting in crashing of this software, please check your Java Environment or your computer!
download=Download
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
download.BMCL=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
download.failed=Failed to download
download.failed.refresh=Unable to load version list. Click here to retry.
download.mojang=Mojang
download.not_200=Failed to download, the response code is %s.
download.source=Download Source
download.successfully=Downloaded successfully
extension.bat=Windows Bat file
extension.mod=Mod file
@@ -135,14 +101,12 @@ folder.screenshots=Screenshots
input.email=The username must be an e-mail.
input.number=Must be a number.
input.not_empty=Input Requrired!
input.not_empty=Required field
input.url=Must be a valid URL.
input.url.http=Only Http or Https accepted.
install=Install New Game
install.failed=Failed to install
install.installer.choose=Choose a %s version
install.installer.failed=Unable to install %s.
install.installer.forge=Forge
install.installer.game=Game
install.installer.install=
@@ -154,37 +118,29 @@ install.modpack=Install a modpack (CurseForge supported)
install.new_game=Install a New Game
install.new_game.already_exists=This version has already been existing.
install.new_game.current_game_version=Current Game Version
install.no_version=The version is not found.
install.no_version_if_intall=The required version is not found, do you wish to install the version automatically?
install.release_time=Release Time
install.select=Select an operation
install.success=Installed successfully
install.time=Time
install.type=Type
lang=English
lang.default=Belong to OS language.
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
launch.advice.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or newer Java version.
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
launch.advice.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or later Java versions.
launch.advice.newer_java=Java 8 is suggested, which can make game run more fluently. And many mods and Minecraft 1.12 and newer versions requires Java 8.
launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game probably crash. The launcher will try to launch it.
launch.advice.too_large_memory_for_32bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game probably crash. The maximum memory is 1024MB. The launcher will try to launch it.
launch.circular_dependency_versions=Found circular dependency versions, please check if your client has been modified.
launch.failed=Unable to launch
launch.failed.cannot_create_jvm=Java virtual machine cannot be created. Java arguments may have problems. You can enable the no args mode in the settings.
launch.failed.creating_process=Failed to create process, maybe your java path is wrong, please modify your java path.
launch.failed.decompressing_natives=Unable to decompress native libraries.
launch.failed.downloading_libraries=Did not finish downloading libraries, continue launching game?
launch.failed.executable_permission=Unable to add permission to the launch script
launch.failed.exited_abnormally=Game exited abnormally, please visit the log, or ask someone for help.
launch.failed.prelaunch_command=Prelaunch command fail.
launch.state.dependencies=Dependencies
launch.state.done=Done
launch.state.logging_in=Logging In
launch.state.modpack=Preparing for modpack
launch.state.waiting_launching=Waiting for game launching
launch.unsupported_launcher_version=Sorry, the launcher cannot launch minecraft, but will retry launching it.
launch.wrong_javadir=Incorrect Java directory, will reset to default Java directory.
launcher=Launcher
@@ -196,61 +152,31 @@ launcher.common_directory.choose=Choose common directory.
launcher.contact=Contact Us
launcher.crash=Hello Minecraft! Launcher has crashed!
launcher.crash_out_dated=Hello Minecraft! Launcher has crashed! Your launcher is outdated. Update it!
launcher.exit_failed=Failed to shutdown.
launcher.modpack=Documentations for modpacks.
launcher.restart=Options will be in operations only if restart this app.
launcher.tab.about=About
launcher.tab.general=General
launcher.tab.ui=UI
launcher.update_java=Please upgrade your Java.
launcher.update_launcher=Check for update
launcher.versions_json_not_formatted=The version information of %s is malformed! Redownload it?
launcher.versions_json_not_matched=The version %s is malformed! There are a json:%s in this version. Do you want to fix this problem?
launcher.versions_json_not_matched_cannot_auto_completion=The version %s lost version information file, delete it?
login.changed_client_token=The server response has changed the client token.
login.choose_charactor=Please choose the character you want
login.empty_username=You have not set a username!
login.enter_password=Please enter your password.
login.enter_username=Please enter your name.
login.failed=Failed to login
login.invalid_username=Invalid username
login.invalid_uuid_and_username=Invalid UUID and username
login.no_valid_character=No valid character, please visit skinme.cc and create your own character.
logwindow.search=Search
logwindow.show_lines=Show Lines
logwindow.terminate_game=Terminate Game
logwindow.title=Log
main_page=Home
message.cancelled=Cancelled
message.cannot_open_explorer=Cannot open explorer:
message.confirm=Confirm
message.doing=Please wait
message.downloading=Downloading...
message.error=Error
message.info=Info
message.loading=Loading...
message.success=Tasks succeeded
message.unknown=Unknown
minecraft.invalid=Invalid
minecraft.invalid_jar=Invalid Jar
minecraft.modified=(Modified!)
minecraft.not_a_file=Not a file
minecraft.not_found=Not found
minecraft.not_readable=Not readable
minecraft.wrong_path=Wrong Minecraft path, the launcher could not find the path.
modpack=Mod pack
modpack.choose=Choose a modpack zip file which you want to import. If you want to update the modpack, please enter the version you want to update.
modpack.desc=Describe your modpack, including precautions, changlog, supporting Markdown(also supporting online pictures).
modpack.enter_name=Enter your desired name for this game.
modpack.export=Export Modpack
modpack.export_error=Failed to export the modpack, maybe the format of your game directory is incorrect or failed to manage files.
modpack.export_finished=Exporting the modpack finished. See
modpack.files.blueprints=BuildCraft blueprints
modpack.files.config=Mod configs
modpack.files.dumps=NEI debug output
@@ -263,16 +189,13 @@ modpack.files.resourcepacks=Resource(Texture) packs
modpack.files.saves=Saved games
modpack.files.scripts=MineTweaker configuration
modpack.files.servers_dat=Multiplayer servers list
modpack.included_launcher=The modpack is included the launcher, you can publish it directly.
modpack.install=Install %s modpack
modpack.installing=Installing modpack
modpack.introduction=Curse, MultiMC, HMCL, MCBBS modpacks supprted.
modpack.invalid=Invalid modpack file.
modpack.mismatched_type=Mismatched modpack type, your current game is %s modpack, but your update file is %s modpack.
modpack.name=Modpack Name
modpack.not_a_valid_location=Not a valid modpack path
modpack.not_a_valid_name=Not a valid modpack name
modpack.not_included_launcher=You can use the modpack by clicking the "Import Modpack" button.
modpack.scan=Scanning this modpack
modpack.task.install=Import Modpack
modpack.task.install.error=Failed to install the modpack. Maybe the files is incorrect, or a management issue occurred.
@@ -289,7 +212,6 @@ modpack.wizard.step.1.title=Set the basic options to the modpack.
modpack.wizard.step.2=Files selection
modpack.wizard.step.2.title=Choose the files you want to put into the modpack.
modpack.wizard.step.3=Description
modpack.wizard.step.3.title=Describe your modpack.
modpack.wizard.step.initialization.exported_version=The exported game version
modpack.wizard.step.initialization.include_launcher=Include the launcher
modpack.wizard.step.initialization.save=Choose a path to export the game files to
@@ -300,11 +222,8 @@ mods.add=Add mods
mods.add.failed=Failed to add mods %s.
mods.add.success=Successfully added mods %s.
mods.choose_mod=Choose your mods
mods.default_information=Please ensure that you have installed Forge or LiteLoader before installing mods!\nYou can drop your mod files from explorer/finder, and delete mods by the delete button.\nDisable a mod by leaving the check box unchecked; Choose an item to get the information.
mods.remove=Remove
operation.confirm_stop=Terminate the operations?
operation.stopped=The operation was aborted.
profile=Profile
profile.default=Current directory
@@ -312,8 +231,6 @@ profile.home=User home
profile.instance_directory=Game Directory
profile.instance_directory.choose=Choose Game Directory
profile.new=New Config
profile.new_name=New Profile Name:
profile.remove=Sure to remove profile %s?
profile.title=Game Directories
profile.selected=Selected
@@ -342,10 +259,7 @@ settings.advanced.precall_command=Pre-Launch command(will be executed before gam
settings.advanced.server_ip=Server Host
settings.advanced.wrapper_launcher=Wrapper Launcher(i.e. optirun...)
settings.cannot_remove_default_config=Cannot remove the default configution.
settings.custom=Custom
settings.default=Default
settings.failed_load=Failed to load settings file. Remove it?
settings.game=Games
settings.game.dimension=Game Window Dimension
@@ -378,16 +292,11 @@ settings.launcher.theme=Theme
settings.max_memory=Max Memory/MB
settings.physical_memory=Physical Memory Size
settings.run_directory=Run Directory(Version Isolation)
settings.show_log=Show Logs
settings.tabs.assets_downloads=Assets
settings.tabs.game_download=Games
settings.tabs.installers=Installers
settings.test_game=Test game
settings.type.global=Global version settings(all shared)
settings.type=Version setting type
settings.type.special=Specialized version settings(will not affect other versions)
settings.update_version=Update version json.
update=Update
update.failed=Failed to check for updates.
@@ -395,7 +304,6 @@ update.found=Found Update!
update.newest_version=Newest version: %s
update.latest=This is latest Version.
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
update.should_open_link=Are you willing to update the launcher?
update.tooltip=Update
version.cannot_read=Unable to gather the game version. Cannot continue auto-installing.
@@ -409,7 +317,6 @@ version.launch_script.failed=Unable to make launch script.
version.launch_script.save=Save the launch script
version.launch_script.success=Finished script creation, %s.
version.manage.redownload_assets_index=Redownload Assets Index
version.manage.redownload_json=Redownload Minecraft Configuration(minecraft.json)
version.manage.remove=Delete this version
version.manage.remove.confirm=Sure to remove version %s?
version.manage.remove_libraries=Delete library files
@@ -419,10 +326,6 @@ version.settings=Settings
version.update=Update
wizard.prev=< Prev
wizard.close=Close
wizard.failed=Failed
wizard.finish=Finish
wizard.help=Help
wizard.next=Next >
wizard.steps=Steps
wizard.summary=Summary
@@ -20,9 +20,10 @@ about.copyright.statement=Copyright (c) 2018 huangyuhui.
about.author=作者
about.author.statement=huanghongxun ([email protected])
about.thanks_to=鸣谢
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有参与本项目,issue,pull requests的贡献者
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有通过 Issues、Pull Requests 等方式参与本项目的贡献者
about.dependency=依赖
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
# 由于篇幅限制,仅列出第一作者
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)
about.claim=免责声明
about.claim.statement=Minecraft 版权归 Mojang AB 所有,使用本软件产生的版权问题,软件制作方概不负责。请支持正版。
about.open_source=开源
@@ -31,7 +32,6 @@ about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
account=账户
account.character=角色
account.choose=选择一个角色
account.current=当前账户
account.create=新建账户
account.email=邮箱
account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题
@@ -39,11 +39,13 @@ account.failed.invalid_credentials=您的用户名或密码错误,或者登录
account.failed.invalid_password=无效的密码
account.failed.invalid_token=请尝试登出并重新输入密码登录
account.failed.no_charactor=该帐号没有角色
account.failed.no_selected_server=未选择认证服务器
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
account.injector.add=添加认证服务器
account.injector.manage=管理认证服务器
account.injector.http=警告:此服务器使用不安全的http协议,您的密码在登录时会被明文传输。
account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。
account.injector.server=认证服务器
account.injector.server_ip=服务器地址
account.injector.server_url=服务器地址
account.injector.server_name=服务器名称
account.methods=登录方式
account.methods.authlib_injector=authlib-injector 登录
@@ -60,65 +62,29 @@ archive.version=版本
assets.download=下载资源
assets.download_all=检查资源文件完整性
assets.failed=获取列表失败,请刷新重试。
assets.failed_download=下载资源文件失败,可能导致没有中文和声音。
button.about=关于
button.cancel=取消
button.clear=清除
button.close=关闭
button.copy=复制
button.delete=删除
button.download=下载
button.edit=修改
button.explore=浏览
button.info=信息
button.install=安装
button.more=更多
button.no=否
button.ok=确定
button.others=其他
button.preview=预览
button.refresh=刷新
button.retry=重试
button.save=保存
button.test=测试
button.yes=是
color.blue=蓝色
color.dark_blue=深蓝色
color.green=绿色
color.orange=橙色
color.purple=紫色
color.red=红色
color.recent=推荐
color.custom=自定义颜色
crash.NoClassDefFound=请确认HMCL本体是否完整,或更新您的Java。
crash.advice.ClassNotFoundException=Minecraft不完整或Mod冲突,如果有未能下载的文件请下载成功后重试或是客户端损坏请重试请重新制作客户端或下载整合包解决问题,另可尝试游戏设置->(版本)管理->删除库文件解决问题
crash.advice.ConcurrentModificationException=这可能是因为您的Java版本高于Java 1.8.0_11导致的,可以尝试卸载Java8安装Java7。
crash.advice.LWJGLException=您的电脑不正常,可能需要使用驱动精灵或其他安装器更新显卡驱动。
crash.advice.NoSuchFieldError=Minecraft不完整或Mod冲突,如果有未能下载的文件请下载成功后重试或是客户端损坏请重试请重新制作客户端或下载整合包解决问题。
crash.advice.OpenGL=可能是显卡/声卡驱动问题,也可能是Mod导致的问题。
crash.advice.OutOfMemoryError=内存溢出,您设置的Minecraft最大内存过小,请调大!
crash.advice.SecurityException=可能是您修改了minecraft.jar但未删除META-INF文件夹的原因。请通过压缩软件删除jar中的META-INF文件夹。
crash.advice.UnsupportedClassVersionError=这可能是因为您的Java版本过于老旧,可以尝试更换最新Java并在版本设置的Java路径中设置.
crash.advice.no=无建议。
crash.advice.no_lwjgl=可能是游戏依赖库不完整或解压依赖库时出错。可以通过下载整合包解决问题。
crash.advice.otherwise=可能是Mod或其他问题。
crash.error=您的Minecraft崩溃了。
crash.launcher=启动器崩溃了!
crash.minecraft=Minecraft崩溃了!请认真阅读建议。
crash.user_fault=您的系统或Java环境可能安装不当导致本软件崩溃,请检查您的Java环境或您的电脑!可以尝试重新安装Java。
crash.NoClassDefFound=请确认 HMCL 本体是否完整,或更新您的 Java。
crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,请检查您的 Java 环境或您的电脑!可以尝试重新安装 Java。
download=下载
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
download.failed=下载失败
download.failed.refresh=加载版本列表失败,点击此处重试。
download.mojang=官方
download.not_200=下载失败,HTTP状态码:%s.
download.source=下载源
download.successfully=下载完成
extension.bat=Windows 脚本
extension.mod=模组文件
@@ -137,174 +103,127 @@ input.email=用户名必须是邮箱
input.number=必须是数字
input.not_empty=必填项
input.url=必须是合法的链接
input.url.http=只支持Http或Https协议
install=添加游戏
install.failed=安装失败
install.installer.choose=选择%s版本
install.installer.failed=未能成功安装%s.
install.installer.choose=选择 %s 版本
install.installer.forge=Forge
install.installer.game=游戏
install.installer.install=安装%s
install.installer.install=安装 %s
install.installer.liteloader=LiteLoader
install.installer.not_installed=暂不安装%s,可以点击此处安装
install.installer.not_installed=暂不安装 %s,可以点击此处安装
install.installer.optifine=OptiFine
install.installer.version=%s版本
install.installer.version=%s 版本
install.modpack=安装整合包
install.new_game=安装新游戏版本
install.new_game.already_exists=此版本已经存在,请换一个名字
install.new_game.current_game_version=当前游戏版本
install.no_version=未找到要安装的对应MC版本
install.no_version_if_intall=未找到要安装的对应MC版本,是否自动安装需要的MC版本?
install.release_time=发布时间
install.select=请选择安装方式
install.success=安装成功
install.time=时间
install.type=类型
lang=简体中文
lang.default=跟随系统语言
launch.advice.different_platform=您的系统是64位的但是Java是32位的,推荐您安装64位Java.
launch.advice.java9=不高于1.13的Minecraft不支持Java 9或更高版本,请使用Java 8.
launch.advice.newer_java=检测到您未使用Java 8及更新版本,Java 8能使游戏更流畅而且Minecraft 1.12及更新版本和很多Mod强制需要需要Java 8。
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小%dMB,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了32位Java的内存分配限制,所以可能无法启动游戏,请将内存调至1024MB或更小,启动器仍会尝试启动。
launch.circular_dependency_versions=发现游戏版本循环引用,请确认您的客户端未被修改或修改导致出现此问题。
launch.advice.different_platform=您的系统是 64 位的但是 Java 是 32 位的,推荐您安装 64 位 Java。
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,请使用 Java 8 或最新版本。
launch.advice.java9=不高于 1.13 的安装了 Mod 的 Minecraft 不支持 Java 9 或更高版本,请使用 Java 8。
launch.advice.newer_java=检测到您未使用 Java 8 及更新版本,Java 8 能使游戏更流畅而且 Minecraft 1.12 及更新版本和很多 Mod 强制需要需要 Java 8。
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小 %dMB,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小,启动器仍会尝试启动。
launch.failed=启动失败
launch.failed.cannot_create_jvm=截获到无法创建Java虚拟机,可能是Java参数有问题,可以在设置中开启无参数模式启动
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是Java路径错误。
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。
launch.failed.decompressing_natives=未能解压游戏本地库。
launch.failed.downloading_libraries=未完成游戏依赖库的下载,还要继续启动游戏吗?
launch.failed.executable_permission=未能为启动文件添加执行权限
launch.failed.executable_permission=未能为启动文件添加执行权限。
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
launch.failed.prelaunch_command=启动前执行命令执行失败。
launch.state.dependencies=正在处理游戏依赖
launch.state.done=启动完成
launch.state.logging_in=登录中
launch.state.modpack=正在下载必要文件
launch.state.waiting_launching=等待游戏启动
launch.unsupported_launcher_version=对不起,本启动器现在可能不能启动这个版本的Minecraft,但启动器还是会尝试启动,请尽快将此问题报告给作者。
launch.wrong_javadir=错误的Java路径,将自动重置为默认Java路径。
launch.wrong_javadir=错误的 Java 路径,将自动重置为默认 Java 路径。
launcher=启动器
launcher.background=背景地址
launcher.background.choose=选择背景路径
launcher.background.default=默认(自动检索启动器同目录下的background.png/jpg及bg文件夹内的图片)
launcher.background.default=默认(自动检索启动器同目录下的 background.png/jpg 及 bg 文件夹内的图片)
launcher.common_directory=公共文件夹
launcher.common_directory.choose=选择公共文件夹
launcher.contact=联系我们
launcher.crash=Hello Minecraft!遇到了无法处理的错误,请复制下列内容并通过mcbbs、贴吧、Github或Minecraft Forum反馈bug。
launcher.crash_out_dated=Hello Minecraft! Launcher遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
launcher.exit_failed=强制退出失败,可能是Forge 1.7.10及更高版本导致的,无法解决。
launcher.modpack=整合包作者帮助
launcher.restart=本界面选项需要重启本启动器生效
launcher.tab.about=关于
launcher.tab.general=通用
launcher.tab.ui=界面
launcher.update_java=请更新您的Java
launcher.update_launcher=检查更新
launcher.versions_json_not_formatted=版本%s信息文件格式错误,是否重新下载?
launcher.versions_json_not_matched=版本%s格式不规范!该版本文件夹下有json:%s,是否更名这个文件来规范格式?
launcher.versions_json_not_matched_cannot_auto_completion=版本%s缺失必要的版本信息文件,是否删除该版本?
launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并通过 MCBBS、贴吧、GitHub 或 Minecraft Forum 反馈 bug。
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
launcher.update_java=请更新您的 Java
login.changed_client_token=服务器回应已经修改客户端令牌
login.choose_charactor=请选择您要使用的角色
login.empty_username=你还未设置用户名!
login.enter_password=请输入您的密码
login.enter_username=请输入您的账号
login.failed=登录失败:
login.invalid_username=无效的用户名
login.invalid_uuid_and_username=无效的UUID和用户名
login.no_valid_character=无有效的角色,自行到skinme.cc登陆并创建角色
logwindow.search=查找
logwindow.show_lines=显示行数
logwindow.terminate_game=结束游戏进程
logwindow.title=日志
main_page=主页
message.cancelled=已取消
message.cannot_open_explorer=无法打开文件管理器:
message.confirm=提示
message.doing=请耐心等待
message.downloading=正在下载...
message.error=错误
message.info=提示
message.loading=加载中...
message.success=已完成
message.unknown=未知
minecraft.invalid=无效的
minecraft.invalid_jar=无效的jar包
minecraft.modified=(修改的!)
minecraft.not_a_file=不是文件
minecraft.not_found=找不到minecraft.jar
minecraft.not_readable=minecraft.jar不可读
minecraft.wrong_path=错误的Minecraft路径,启动器未找到设定的Minecraft路径,请检查。
modpack=整合包
modpack.choose=选择要导入的游戏整合包文件,如果您希望更新整合包,请输入要更新的版本名
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持Markdown(图片请用网络图片)。
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown(图片请用网络图)。
modpack.enter_name=给游戏起个你喜欢的名字
modpack.export=导出整合包
modpack.export_error=导出失败,可能是您的游戏文件夹格式不正确或操作文件失败
modpack.export_finished=整合包导出完成,参见
modpack.files.blueprints=BuildCraft蓝图
modpack.files.config=Mod配置文件
modpack.files.dumps=NEI调试输出
modpack.files.liteconfig=Mod配置文件
modpack.files.blueprints=BuildCraft 蓝图
modpack.files.config=Mod 配置文件
modpack.files.dumps=NEI 调试输出
modpack.files.liteconfig=Mod 配置文件
modpack.files.mods=Mod
modpack.files.mods.voxelmods=VoxelMods配置,如小地图
modpack.files.mods.voxelmods=VoxelMods 配置,如小地图
modpack.files.options_txt=游戏设定
modpack.files.optionsshaders_txt=光影设定
modpack.files.resourcepacks=资源包(材质包)
modpack.files.saves=游戏存档
modpack.files.scripts=MineTweaker配置
modpack.files.scripts=MineTweaker 配置
modpack.files.servers_dat=多人游戏服务器列表
modpack.included_launcher=整合包已包含启动器,可直接发布
modpack.install=安装%s整合包
modpack.installing=正在安装整合包
modpack.introduction=支持 Curse, MultiMC, HMCL, MCBBS 整合包。
modpack.introduction=支持 Curse、MultiMC、HMCL、MCBBS 整合包。
modpack.invalid=无效的整合包升级文件,可能是下载时出现问题。
modpack.mismatched_type=整合包类型不匹配,当前游戏是 %s 整合包,但是提供的整合包更新文件是 %s 整合包。
modpack.name=整合包名称
modpack.not_a_valid_location=不是一个有效整合包位置
modpack.not_a_valid_name=不是一个有效的整合包名称
modpack.not_included_launcher=整合包未包含启动器,可使用本软件的导入整合包功能导入整合包
modpack.scan=解析整合包
modpack.task.install=导入整合包
modpack.task.install.error=安装失败,可能是整合包格式不正确或操作文件失败
modpack.task.install.error=安装失败,可能是整合包格式不正确或操作文件失败。
modpack.task.install.will=将会安装整合包:
modpack.type.curse=Curse
modpack.type.curse.completion=下载Curse整合包相关文件
modpack.type.curse.completion=下载 Curse 整合包相关文件
modpack.type.hmcl=HMCL
modpack.type.multimc=MultiMC
modpack.unsupported=该整合包不被支持。仅HMCL、MultiMC、Curse整合包受支持。
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。
modpack.update=正在升级整合包
modpack.wizard=导出整合包向导
modpack.wizard.step.1=基本设置
modpack.wizard.step.1.title=设置整合包的主要信息
modpack.wizard.step.2=文件选择
modpack.wizard.step.2.title=选中你想加到整合包中的文件(夹)
modpack.wizard.step.2.title=选中你想加到整合包中的文件或文件夹
modpack.wizard.step.3=整合包描述
modpack.wizard.step.3.title=描述你要制作的整合包
modpack.wizard.step.initialization.exported_version=要导出的游戏版本
modpack.wizard.step.initialization.include_launcher=包含启动器
modpack.wizard.step.initialization.save=选择要导出到的游戏整合包位置
modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的Minecraft是正式版而非快照版,\n而且不应当将不允许非官方途径传播的Mod、材质包等纳入整合包。\n整合包会保存您目前的下载源设置
modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的 Minecraft 是正式版而非快照版,\n而且不应当将不允许非官方途径传播的 Mod、材质包等纳入整合包。\n整合包会保存您目前的下载源设置
mods=模组管理
mods.add=添加模组
mods.add.failed=添加模组%s失败。
mods.add.success=成功添加模组%s。
mods.add.failed=添加模组 %s 失败。
mods.add.success=成功添加模组 %s。
mods.choose_mod=选择模组
mods.default_information=安装Mod前你需要确保已安装Forge或LiteLoader!\n您可以从资源管理器拖动mod文件到列表中来添加mod,同时使用删除键可快速删除选中mod\n点掉mod前面的勾可禁用mod,不会加载;选择mod可以获取mod信息
mods.remove=删除
operation.confirm_stop=真的要终止操作吗?
operation.stopped=操作被强行终止
profile=配置
profile.default=当前目录
@@ -312,8 +231,6 @@ profile.home=主文件夹
profile.instance_directory=游戏路径
profile.instance_directory.choose=选择游戏路径
profile.new=新建配置
profile.new_name=新配置名:
profile.remove=真的要删除配置%s吗?
profile.title=游戏目录
profile.selected=已选中
@@ -325,27 +242,24 @@ settings=普通设置
settings.advanced=高级设置
settings.advanced.dont_check_game_completeness=不检查游戏完整性
settings.advanced.game_dir.default=默认(.minecraft/)
settings.advanced.game_dir.independent=各版本独立(.minecraft/versions/<版本名>/,除assets,libraries)
settings.advanced.game_dir.default=默认(.minecraft/)
settings.advanced.game_dir.independent=各版本独立(.minecraft/versions/<版本名>/,除 assets、libraries)
settings.advanced.java_args_default=启动器默认添加的参数(请不要重复添加):-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
settings.advanced.java_permanent_generation_space=内存永久保存区域(不必填写,MB)
settings.advanced.jvm_args=Java虚拟机参数(不必填写)
settings.advanced.java_permanent_generation_space=内存永久保存区域(不必填写,MB)
settings.advanced.jvm_args=Java 虚拟机参数(不必填写)
settings.advanced.launcher_visibility.close=游戏启动后结束启动器
settings.advanced.launcher_visibility.hide=游戏启动后隐藏启动器
settings.advanced.launcher_visibility.hide_reopen=隐藏启动器并在游戏结束后重新打开
settings.advanced.launcher_visibility.keep=保持启动器可见
settings.advanced.launcher_visible=启动器可见性
settings.advanced.minecraft_arguments=Minecraft额外参数(不必填写)
settings.advanced.minecraft_arguments=Minecraft 额外参数(不必填写)
settings.advanced.no_common=不使用公共路径
settings.advanced.no_jvm_args=不添加默认的JVM参数(使用Java9时必勾)
settings.advanced.precall_command=启动前执行命令(不必填写,将在游戏启动前调用)
settings.advanced.server_ip=直入服务器ip地址(不必填写,启动游戏后直接进入对应服务器)
settings.advanced.wrapper_launcher=前置指令(不必填写,如optirun)
settings.advanced.no_jvm_args=不添加默认的 JVM 参数(使用 Java 9 时必勾)
settings.advanced.precall_command=启动前执行命令(不必填写,将在游戏启动前调用)
settings.advanced.server_ip=直入服务器 IP 地址(不必填写,启动游戏后直接进入对应服务器)
settings.advanced.wrapper_launcher=前置指令(不必填写,如 optirun)
settings.cannot_remove_default_config=不能删除默认配置
settings.custom=自定义
settings.default=默认
settings.failed_load=设置文件加载失败,可能是升级了启动器或被人工修改造成错误,是否清除?
settings.game=游戏设置
settings.game.dimension=游戏窗口分辨率
@@ -353,8 +267,8 @@ settings.game.exploration=浏览
settings.game.fullscreen=全屏
settings.game.game_directory=游戏路径(版本隔离)
settings.game.game_directory.choose=选择游戏路径
settings.game.java_directory=Java路径
settings.game.java_directory.choose=选择Java路径
settings.game.java_directory=Java 路径
settings.game.java_directory.choose=选择 Java 路径
settings.game.management=管理
settings.icon=游戏图标
@@ -376,18 +290,13 @@ settings.launcher.proxy.socks=Socks
settings.launcher.proxy.username=账户
settings.launcher.theme=主题
settings.max_memory=最大内存/MB
settings.max_memory=最大内存(MB)
settings.physical_memory=物理内存大小
settings.run_directory=运行路径(版本隔离)
settings.show_log=查看日志
settings.tabs.assets_downloads=资源下载
settings.tabs.game_download=游戏下载
settings.tabs.installers=自动安装
settings.test_game=测试游戏
settings.type.global=全局版本设置(使用该设置的版本共用一套设定)
settings.type=版本设置类型
settings.type.special=单独版本设置(不会影响到其他版本的设定)
settings.update_version=更新版本文件
update=启动器更新
update.failed=检查更新失败
@@ -395,7 +304,6 @@ update.found=发现更新
update.newest_version=最新版本为:%s
update.latest=当前版本为最新版本
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
update.should_open_link=是否更新?
update.tooltip=更新
version.cannot_read=读取游戏版本失败,无法进行自动安装
@@ -407,9 +315,8 @@ version.launch=启动游戏
version.launch_script=生成启动脚本
version.launch_script.failed=生成启动脚本失败
version.launch_script.save=保存启动脚本
version.launch_script.success=启动脚本已生成完毕: %s.
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
version.manage.redownload_json=重新下载版本配置(minecraft.json)
version.launch_script.success=启动脚本已生成完毕:%s
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
version.manage.remove=删除该版本
version.manage.remove.confirm=真的要删除版本 %s 吗?
version.manage.remove_libraries=删除所有库文件
@@ -419,10 +326,6 @@ version.settings=游戏设置
version.update=更新
wizard.prev=< 上一步
wizard.close=关闭
wizard.failed=失败
wizard.finish=完成
wizard.help=帮助
wizard.next=下一步 >
wizard.steps=步骤
wizard.summary=概要
@@ -37,14 +37,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
import static java.nio.charset.StandardCharsets.UTF_8;
public class AuthlibInjectorAccount extends YggdrasilAccount {
private final String serverBaseURL;
private final AuthlibInjectorServer server;
private final ExceptionalSupplier<String, ?> injectorJarPath;
protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier<String, ?> injectorJarPath, String username, UUID characterUUID, YggdrasilSession session) {
protected AuthlibInjectorAccount(YggdrasilService service, AuthlibInjectorServer server, ExceptionalSupplier<String, ?> injectorJarPath, String username, UUID characterUUID, YggdrasilSession session) {
super(service, username, characterUUID, session);
this.injectorJarPath = injectorJarPath;
this.serverBaseURL = serverBaseURL;
this.server = server;
}
@Override
@@ -59,7 +59,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> supplier) throws AuthenticationException {
// Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time.
GetTask getTask = new GetTask(NetworkUtils.toURL(serverBaseURL));
GetTask getTask = new GetTask(NetworkUtils.toURL(server.getUrl()));
AtomicBoolean flag = new AtomicBoolean(true);
Thread thread = Lang.thread(() -> flag.set(getTask.test()));
@@ -67,7 +67,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
try {
thread.join();
Arguments arguments = new Arguments().addJVMArguments("-javaagent:" + injectorJarPath.get() + "=" + serverBaseURL);
Arguments arguments = new Arguments().addJVMArguments("-javaagent:" + injectorJarPath.get() + "=" + server.getUrl());
if (flag.get())
arguments = arguments.addJVMArguments("-Dorg.to2mbn.authlibinjector.config.prefetched=" + new String(Base64.getEncoder().encode(getTask.getResult().getBytes()), UTF_8));
@@ -81,12 +81,12 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
@Override
public Map<Object, Object> toStorage() {
Map<Object, Object> map = super.toStorage();
map.put("serverBaseURL", serverBaseURL);
map.put("serverBaseURL", server.getUrl());
return map;
}
public String getServerBaseURL() {
return serverBaseURL;
public AuthlibInjectorServer getServer() {
return server;
}
}
@@ -6,33 +6,37 @@ import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
import org.jackhuang.hmcl.util.ExceptionalSupplier;
import org.jackhuang.hmcl.util.NetworkUtils;
import java.net.Proxy;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import static org.jackhuang.hmcl.util.Lang.tryCast;
public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjectorAccount> {
private final ExceptionalSupplier<String, ?> injectorJarPathSupplier;
private Function<String, AuthlibInjectorServer> serverLookup;
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> injectorJarPathSupplier) {
/**
* @param serverLookup a function that looks up {@link AuthlibInjectorServer} by url
*/
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> injectorJarPathSupplier, Function<String, AuthlibInjectorServer> serverLookup) {
this.injectorJarPathSupplier = injectorJarPathSupplier;
this.serverLookup = serverLookup;
}
@Override
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object serverBaseURL, Proxy proxy) throws AuthenticationException {
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException {
Objects.requireNonNull(selector);
Objects.requireNonNull(username);
Objects.requireNonNull(password);
Objects.requireNonNull(proxy);
if (!(serverBaseURL instanceof String) || !NetworkUtils.isURL((String) serverBaseURL))
throw new IllegalArgumentException("Additional data should be server base url string for authlib injector accounts.");
AuthlibInjectorServer server = (AuthlibInjectorServer) additionalData;
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider((String) serverBaseURL), proxy),
(String) serverBaseURL, injectorJarPathSupplier, username, null, null);
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
server, injectorJarPathSupplier, username, null, null);
account.logInWithPassword(password, selector);
return account;
}
@@ -49,7 +53,9 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
String apiRoot = tryCast(storage.get("serverBaseURL"), String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have API root."));
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(apiRoot), proxy),
apiRoot, injectorJarPathSupplier, username, session.getSelectedProfile().getId(), session);
AuthlibInjectorServer server = serverLookup.apply(apiRoot);
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
server, injectorJarPathSupplier, username, session.getSelectedProfile().getId(), session);
}
}
@@ -0,0 +1,84 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.authlibinjector;
import static org.jackhuang.hmcl.util.Lang.tryCast;
import java.io.IOException;
import java.util.Optional;
import org.jackhuang.hmcl.util.JsonUtils;
import org.jackhuang.hmcl.util.NetworkUtils;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
public class AuthlibInjectorServer {
public static AuthlibInjectorServer fetchServerInfo(String url) throws IOException {
try {
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class);
String name = extractServerName(response).orElse(url);
return new AuthlibInjectorServer(url, name);
} catch (JsonSyntaxException e) {
throw new IOException("Malformed response", e);
}
}
private static Optional<String> extractServerName(JsonObject response){
return tryCast(response.get("meta"), JsonObject.class)
.flatMap(meta -> tryCast(meta.get("serverName"), JsonPrimitive.class).map(JsonPrimitive::getAsString));
}
private String url;
private String name;
public AuthlibInjectorServer(String url, String name) {
this.url = url;
this.name = name;
}
public String getUrl() {
return url;
}
public String getName() {
return name;
}
@Override
public String toString() {
return url + " (" + name + ")";
}
@Override
public int hashCode() {
return url.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof AuthlibInjectorServer))
return false;
AuthlibInjectorServer another = (AuthlibInjectorServer) obj;
return this.url.equals(another.url);
}
}
@@ -1,36 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.authlibinjector;
public class AuthlibInjectorServerInfo {
private final String serverIp;
private final String serverName;
public AuthlibInjectorServerInfo(String serverIp, String serverName) {
this.serverIp = serverIp;
this.serverName = serverName;
}
public String getServerIp() {
return serverIp;
}
public String getServerName() {
return serverName;
}
}
@@ -1,51 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.authlibinjector;
public class AuthlibInjectorServerResponse {
private final Meta meta;
public AuthlibInjectorServerResponse() {
this(new Meta());
}
public AuthlibInjectorServerResponse(Meta meta) {
this.meta = meta;
}
public Meta getMeta() {
return meta;
}
public static class Meta {
private final String serverName;
public Meta() {
this("");
}
public Meta(String serverName) {
this.serverName = serverName;
}
public String getServerName() {
return serverName;
}
}
}
@@ -84,6 +84,7 @@ public class DefaultLauncher extends Launcher {
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
res.add("-Duser.home=" + options.getGameDir().getParent());
// Force using G1GC with its settings
if (options.getJava().getParsedVersion() >= JavaVersion.JAVA_7) {
res.add("-XX:+UnlockExperimentalVMOptions");
res.add("-XX:+UseG1GC");
@@ -103,6 +104,12 @@ public class DefaultLauncher extends Launcher {
res.add("-XX:-OmitStackTraceInFastThrow");
res.add("-Xmn128m");
// 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().getPlatform() == Platform.BIT_32) {
res.add("-Xss1M");
}
if (options.getMaxMemory() != null && options.getMaxMemory() > 0)
res.add("-Xmx" + options.getMaxMemory() + "m");
@@ -113,7 +113,10 @@ public final class CurseInstallTask extends Task {
throw new IOException("Unable to delete mod file " + oldFile);
}
dependencies.add(new CurseCompletionTask(dependencyManager, name));
File root = repository.getVersionRoot(name);
FileUtils.writeText(new File(root, "manifest.json"), Constants.GSON.toJson(manifest));
dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest));
dependencies.add(new MinecraftInstanceTask<>(zipFile, manifest.getOverrides(), manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
}
@@ -19,6 +19,11 @@ package org.jackhuang.hmcl.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.game.Argument;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.RuledArgument;
@@ -65,6 +70,9 @@ public final class Constants {
javafx.application.Platform.runLater(s);
};
// lazy loading
public static final ObjectBinding<Image> DEFAULT_ICON = Bindings.createObjectBinding(() -> new Image("/assets/img/icon.png"));
public static final Gson GSON = new GsonBuilder()
.enableComplexMapKeySerialization()
.setPrettyPrinting()