Merge branch 'javafx' into javafx
This commit is contained in:
@@ -20,8 +20,12 @@ package org.jackhuang.hmcl;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.AuthlibInjectorServers;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.CrashReporter;
|
||||
@@ -40,6 +44,7 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -53,6 +58,14 @@ public final class Launcher extends Application {
|
||||
try {
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
AuthlibInjectorServers.init();
|
||||
|
||||
if (ConfigHolder.isNewlyCreated() && !AuthlibInjectorServers.getConfigInstance().getUrls().isEmpty()) {
|
||||
config().setPreferredLoginType(Accounts.getLoginType(Accounts.FACTORY_AUTHLIB_INJECTOR));
|
||||
AuthlibInjectorServers.getConfigInstance().getUrls().stream()
|
||||
.map(AuthlibInjectorServer::new)
|
||||
.forEach(config().getAuthlibInjectorServers()::add);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Main.showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
|
||||
}
|
||||
@@ -77,6 +90,7 @@ public final class Launcher extends Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);
|
||||
TaskExecutor.setUncaughtExceptionHandler(new CrashReporter(false));
|
||||
|
||||
try {
|
||||
LOG.info("*** " + Metadata.TITLE + " ***");
|
||||
@@ -142,5 +156,5 @@ public final class Launcher extends Application {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter(true);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public final class Main {
|
||||
checkDirectoryPath();
|
||||
|
||||
// This environment check will take ~300ms
|
||||
thread(() -> checkDSTRootCAX3(), "CA Certificate Check", true);
|
||||
thread(Main::checkDSTRootCAX3, "CA Certificate Check", true);
|
||||
|
||||
Logging.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
|
||||
|
||||
|
||||
@@ -29,16 +29,16 @@ import java.util.Map;
|
||||
*/
|
||||
public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
|
||||
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) {
|
||||
this(repository, versionId, authInfo, options, null);
|
||||
public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {
|
||||
this(repository, version, authInfo, options, null);
|
||||
}
|
||||
|
||||
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {
|
||||
this(repository, versionId, authInfo, options, listener, true);
|
||||
public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {
|
||||
this(repository, version, authInfo, options, listener, true);
|
||||
}
|
||||
|
||||
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
|
||||
super(repository, versionId, authInfo, options, listener, daemon);
|
||||
public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
|
||||
super(repository, version, authInfo, options, listener, daemon);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -158,9 +158,11 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
File iconFile = getVersionIconFile(id);
|
||||
if (iconFile.exists())
|
||||
return new Image("file:" + iconFile.getAbsolutePath());
|
||||
else if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass())
|
||||
|| version.getMainClass().startsWith("net.fabricmc")
|
||||
|| "cpw.mods.modlauncher.Launcher".equals(version.getMainClass()))
|
||||
else if (!version.getPatches().isEmpty() ||
|
||||
version.getMainClass() != null &&
|
||||
("net.minecraft.launchwrapper.Launch".equals(version.getMainClass())
|
||||
|| version.getMainClass().startsWith("net.fabricmc")
|
||||
|| "cpw.mods.modlauncher.Launcher".equals(version.getMainClass())))
|
||||
return newImage("/assets/img/furnace.png");
|
||||
else
|
||||
return newImage("/assets/img/grass.png");
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.mod.ModAdviser;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
@@ -24,13 +25,14 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Export the game to a mod pack file.
|
||||
*/
|
||||
public class HMCLModpackExportTask extends Task {
|
||||
public class HMCLModpackExportTask extends Task<Void> {
|
||||
private final DefaultGameRepository repository;
|
||||
private final String version;
|
||||
private final List<String> whitelist;
|
||||
@@ -55,7 +57,7 @@ public class HMCLModpackExportTask extends Task {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
ArrayList<String> blackList = new ArrayList<>(HMCLModpackManager.MODPACK_BLACK_LIST);
|
||||
ArrayList<String> blackList = new ArrayList<>(ModAdviser.MODPACK_BLACK_LIST);
|
||||
blackList.add(version + ".jar");
|
||||
blackList.add(version + ".json");
|
||||
Logging.LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
|
||||
@@ -72,9 +74,9 @@ public class HMCLModpackExportTask extends Task {
|
||||
return false;
|
||||
});
|
||||
|
||||
Version mv = repository.getResolvedVersion(version);
|
||||
Version mv = repository.getResolvedPreservingPatchesVersion(version);
|
||||
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + version));
|
||||
.orElseThrow(() -> new IOException("Cannot parse the version of " + version));
|
||||
zip.putTextFile(JsonUtils.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
|
||||
zip.putTextFile(JsonUtils.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DependencyManager;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
@@ -36,16 +36,17 @@ import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public final class HMCLModpackInstallTask extends Task {
|
||||
public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
private final File zipFile;
|
||||
private final String name;
|
||||
private final HMCLGameRepository repository;
|
||||
private final DefaultDependencyManager dependency;
|
||||
private final Modpack modpack;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
|
||||
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String name) {
|
||||
DependencyManager dependency = profile.getDependency();
|
||||
dependency = profile.getDependency();
|
||||
repository = profile.getRepository();
|
||||
this.zipFile = zipFile;
|
||||
this.name = name;
|
||||
@@ -77,20 +78,30 @@ public final class HMCLModpackInstallTask extends Task {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task> getDependencies() {
|
||||
public List<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task> getDependents() {
|
||||
public List<Task<?>> getDependents() {
|
||||
return dependents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version version = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
dependencies.add(new VersionJsonSaveTask(repository, version));
|
||||
Version originalVersion = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(originalVersion);
|
||||
Task<Version> libraryTask = Task.supplyAsync(() -> originalVersion);
|
||||
// reinstall libraries
|
||||
// libraries of Forge and OptiFine should be obtained by installation.
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
if (LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(mark.getLibraryId()))
|
||||
continue;
|
||||
libraryTask = libraryTask.thenComposeAsync(version -> dependency.installLibraryAsync(modpack.getGameVersion(), version, mark.getLibraryId(), mark.getLibraryVersion()));
|
||||
}
|
||||
|
||||
dependencies.add(libraryTask.thenComposeAsync(repository::save));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
@@ -27,51 +26,12 @@ import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class HMCLModpackManager {
|
||||
|
||||
public static final List<String> MODPACK_BLACK_LIST = Lang.immutableListOf(
|
||||
"usernamecache.json", "usercache.json", // Minecraft
|
||||
"launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher
|
||||
"pack.json", "launcher.jar", "hmclmc.log", "cache", // HMCL
|
||||
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
|
||||
"minetweaker.log", // Mods
|
||||
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
|
||||
"downloads", // Curse
|
||||
"asm", "backups", "TCNodeTracker", "CustomDISkins", "data" // Mods
|
||||
);
|
||||
public static final List<String> MODPACK_SUGGESTED_BLACK_LIST = Lang.immutableListOf(
|
||||
"fonts", // BetterFonts
|
||||
"saves", "servers.dat", "options.txt", // Minecraft
|
||||
"blueprints" /* BuildCraft */,
|
||||
"optionsof.txt" /* OptiFine */,
|
||||
"journeymap" /* JourneyMap */,
|
||||
"optionsshaders.txt",
|
||||
"mods/VoxelMods");
|
||||
|
||||
public static ModAdviser.ModSuggestion suggestMod(String fileName, boolean isDirectory) {
|
||||
if (match(MODPACK_BLACK_LIST, fileName, isDirectory))
|
||||
return ModAdviser.ModSuggestion.HIDDEN;
|
||||
if (match(MODPACK_SUGGESTED_BLACK_LIST, fileName, isDirectory))
|
||||
return ModAdviser.ModSuggestion.NORMAL;
|
||||
else
|
||||
return ModAdviser.ModSuggestion.SUGGESTED;
|
||||
}
|
||||
|
||||
private static boolean match(List<String> l, String fileName, boolean isDirectory) {
|
||||
for (String s : l)
|
||||
if (isDirectory) {
|
||||
if (fileName.startsWith(s + "/"))
|
||||
return true;
|
||||
} else if (fileName.equals(s))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the manifest in a HMCL modpack.
|
||||
*
|
||||
|
||||
@@ -27,14 +27,20 @@ import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.launch.*;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.launch.NotDecompressingNativesException;
|
||||
import org.jackhuang.hmcl.launch.PermissionException;
|
||||
import org.jackhuang.hmcl.launch.ProcessCreationException;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
@@ -56,7 +62,13 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -103,7 +115,7 @@ public final class LauncherHelper {
|
||||
try {
|
||||
checkGameState(profile, setting, version, () -> {
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
Schedulers.newThread().schedule(this::launch0);
|
||||
Schedulers.newThread().execute(this::launch0);
|
||||
});
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
@@ -119,18 +131,18 @@ public final class LauncherHelper {
|
||||
private void launch0() {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
|
||||
Version version = MaintainTask.maintain(repository, repository.getResolvedVersion(selectedVersion));
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||
|
||||
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
.then(() -> {
|
||||
TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
.thenComposeAsync(() -> {
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
else
|
||||
return dependencyManager.checkGameCompletionAsync(version);
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
|
||||
.then(() -> {
|
||||
.thenRunAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS))
|
||||
.thenComposeAsync(() -> {
|
||||
try {
|
||||
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
||||
if ("Curse".equals(configuration.getType()))
|
||||
@@ -141,8 +153,8 @@ public final class LauncherHelper {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.thenCompose(() -> Task.ofResult(i18n("account.methods"), () -> {
|
||||
.thenRunAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN))
|
||||
.thenSupplyAsync(i18n("account.methods"), () -> {
|
||||
try {
|
||||
return account.logIn();
|
||||
} catch (CredentialExpiredException e) {
|
||||
@@ -152,21 +164,21 @@ public final class LauncherHelper {
|
||||
LOG.warning("Authentication failed, try playing offline: " + e);
|
||||
return account.playOffline().orElseThrow(() -> e);
|
||||
}
|
||||
}))
|
||||
.thenApply(Schedulers.javafx(), authInfo -> {
|
||||
})
|
||||
.thenApplyAsync(Schedulers.javafx(), authInfo -> {
|
||||
emitStatus(LoadingState.LAUNCHING);
|
||||
return authInfo;
|
||||
})
|
||||
.thenApply(authInfo -> new HMCLGameLauncher(
|
||||
.thenApplyAsync(authInfo -> new HMCLGameLauncher(
|
||||
repository,
|
||||
selectedVersion,
|
||||
version.getPatches().isEmpty() ? repository.getResolvedVersion(selectedVersion) : version,
|
||||
authInfo,
|
||||
setting.toLaunchOptions(profile.getGameDir()),
|
||||
launcherVisibility == LauncherVisibility.CLOSE
|
||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||
: new HMCLProcessListener(authInfo, setting, gameVersion.isPresent())
|
||||
: new HMCLProcessListener(authInfo, gameVersion.isPresent())
|
||||
))
|
||||
.thenCompose(launcher -> { // launcher is prev task's result
|
||||
.thenComposeAsync(launcher -> { // launcher is prev task's result
|
||||
if (scriptFile == null) {
|
||||
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch"));
|
||||
} else {
|
||||
@@ -176,7 +188,7 @@ public final class LauncherHelper {
|
||||
}).setName(i18n("version.launch_script"));
|
||||
}
|
||||
})
|
||||
.thenAccept(process -> { // process is LaunchTask's result
|
||||
.thenAcceptAsync(process -> { // process is LaunchTask's result
|
||||
if (scriptFile == null) {
|
||||
PROCESSES.add(process);
|
||||
if (launcherVisibility == LauncherVisibility.CLOSE)
|
||||
@@ -200,7 +212,7 @@ public final class LauncherHelper {
|
||||
final AtomicInteger finished = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public void onFinished(Task task) {
|
||||
public void onFinished(Task<?> task) {
|
||||
finished.incrementAndGet();
|
||||
int runningTasks = executor.getRunningTasks();
|
||||
Platform.runLater(() -> launchingStepsPane.setProgress(1.0 * finished.get() / runningTasks));
|
||||
@@ -214,7 +226,7 @@ public final class LauncherHelper {
|
||||
// because onStop will be invoked if tasks fail when the executor service shut down.
|
||||
if (!Controllers.isStopped()) {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Exception ex = executor.getLastException();
|
||||
Exception ex = executor.getException();
|
||||
if (ex != null) {
|
||||
String message;
|
||||
if (ex instanceof CurseCompletionException) {
|
||||
@@ -291,7 +303,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
// LaunchWrapper 1.12 will crash because of assuming the system class loader is an instance of URLClassLoader.
|
||||
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9
|
||||
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9_AND_LATER
|
||||
&& version.getMainClass().contains("launchwrapper")
|
||||
&& version.getLibraries().stream()
|
||||
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
|
||||
@@ -420,7 +432,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static class LaunchTask<T> extends TaskResult<T> {
|
||||
private static class LaunchTask<T> extends Task<T> {
|
||||
private final ExceptionalSupplier<T, Exception> supplier;
|
||||
|
||||
public LaunchTask(ExceptionalSupplier<T, Exception> supplier) {
|
||||
@@ -440,7 +452,6 @@ public final class LauncherHelper {
|
||||
*/
|
||||
class HMCLProcessListener implements ProcessListener {
|
||||
|
||||
private final VersionSetting setting;
|
||||
private final Map<String, String> forbiddenTokens;
|
||||
private ManagedProcess process;
|
||||
private boolean lwjgl;
|
||||
@@ -449,8 +460,7 @@ public final class LauncherHelper {
|
||||
private final LinkedList<Pair<String, Log4jLevel>> logs;
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
public HMCLProcessListener(AuthInfo authInfo, VersionSetting setting, boolean detectWindow) {
|
||||
this.setting = setting;
|
||||
public HMCLProcessListener(AuthInfo authInfo, boolean detectWindow) {
|
||||
this.detectWindow = detectWindow;
|
||||
|
||||
if (authInfo == null)
|
||||
|
||||
@@ -20,6 +20,11 @@ package org.jackhuang.hmcl.game;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseManifest;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
@@ -86,7 +91,7 @@ public final class ModpackHelper {
|
||||
throw new UnsupportedModpackException();
|
||||
}
|
||||
|
||||
public static Task getInstallTask(Profile profile, File zipFile, String name, Modpack modpack) {
|
||||
public static Task<Void> getInstallTask(Profile profile, File zipFile, String name, Modpack modpack) {
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
|
||||
ExceptionalRunnable<?> success = () -> {
|
||||
@@ -102,9 +107,6 @@ public final class ModpackHelper {
|
||||
if (ex instanceof CurseCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
|
||||
success.run();
|
||||
// This is tolerable and we will not delete the game
|
||||
} else {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
repository.removeVersionFromDisk(name);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -117,11 +119,11 @@ public final class ModpackHelper {
|
||||
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.then(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
|
||||
else throw new IllegalStateException("Unrecognized modpack: " + modpack);
|
||||
.thenComposeAsync(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
|
||||
else throw new IllegalArgumentException("Unrecognized modpack: " + modpack);
|
||||
}
|
||||
|
||||
public static Task getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, MismatchedModpackTypeException {
|
||||
public static Task<Void> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, MismatchedModpackTypeException {
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(zipFile.toPath(), charset);
|
||||
|
||||
switch (configuration.getType()) {
|
||||
|
||||
@@ -17,16 +17,15 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.mod.MultiMCInstanceConfiguration;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Scheduler;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class MultiMCInstallVersionSettingTask extends Task {
|
||||
public final class MultiMCInstallVersionSettingTask extends Task<Void> {
|
||||
private final Profile profile;
|
||||
private final MultiMCInstanceConfiguration manifest;
|
||||
private final String version;
|
||||
@@ -35,11 +34,8 @@ public final class MultiMCInstallVersionSettingTask extends Task {
|
||||
this.profile = profile;
|
||||
this.manifest = manifest;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scheduler getScheduler() {
|
||||
return Schedulers.javafx();
|
||||
setExecutor(Schedulers.javafx());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -121,6 +121,9 @@ public final class TexturesLoader {
|
||||
try (InputStream in = Files.newInputStream(file)) {
|
||||
img = ImageIO.read(in);
|
||||
}
|
||||
if (img == null)
|
||||
throw new IOException("Texture is malformed");
|
||||
|
||||
Map<String, String> metadata = texture.getMetadata();
|
||||
if (metadata == null) {
|
||||
metadata = emptyMap();
|
||||
|
||||
@@ -197,7 +197,7 @@ public final class Accounts {
|
||||
|
||||
Account selected = selectedAccount.get();
|
||||
if (selected != null) {
|
||||
Schedulers.io().schedule(() -> {
|
||||
Schedulers.io().execute(() -> {
|
||||
try {
|
||||
selected.logIn();
|
||||
} catch (AuthenticationException e) {
|
||||
@@ -209,7 +209,7 @@ public final class Accounts {
|
||||
for (AuthlibInjectorServer server : config().getAuthlibInjectorServers()) {
|
||||
if (selected instanceof AuthlibInjectorAccount && ((AuthlibInjectorAccount) selected).getServer() == server)
|
||||
continue;
|
||||
Schedulers.io().schedule(() -> {
|
||||
Schedulers.io().execute(() -> {
|
||||
try {
|
||||
server.fetchMetadataResponse();
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class AuthlibInjectorServers implements Validation {
|
||||
|
||||
public static final String CONFIG_FILENAME = "authlib-injectors.json";
|
||||
|
||||
private final List<String> urls;
|
||||
|
||||
public AuthlibInjectorServers(List<String> urls) {
|
||||
this.urls = urls;
|
||||
}
|
||||
|
||||
public List<String> getUrls() {
|
||||
return urls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (urls == null)
|
||||
throw new JsonParseException("authlib-injectors.json -> urls cannot be null");
|
||||
}
|
||||
|
||||
private static final Path configLocation = Paths.get(CONFIG_FILENAME);
|
||||
private static AuthlibInjectorServers configInstance;
|
||||
|
||||
public synchronized static void init() {
|
||||
if (configInstance != null) {
|
||||
throw new IllegalStateException("AuthlibInjectorServers is already loaded");
|
||||
}
|
||||
|
||||
configInstance = new AuthlibInjectorServers(Collections.emptyList());
|
||||
|
||||
if (Files.exists(configLocation)) {
|
||||
try {
|
||||
String content = FileUtils.readText(configLocation);
|
||||
configInstance = JsonUtils.GSON.fromJson(content, AuthlibInjectorServers.class);
|
||||
} catch (IOException | JsonParseException e) {
|
||||
LOG.log(Level.WARNING, "Malformed authlib-injectors.json", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static AuthlibInjectorServers getConfigInstance() {
|
||||
return configInstance;
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,10 @@ public final class ConfigHolder {
|
||||
return configInstance;
|
||||
}
|
||||
|
||||
public static boolean isNewlyCreated() {
|
||||
return newlyCreated;
|
||||
}
|
||||
|
||||
public synchronized static void init() throws IOException {
|
||||
if (configInstance != null) {
|
||||
throw new IllegalStateException("Configuration is already loaded");
|
||||
|
||||
@@ -226,7 +226,7 @@ public final class Profile implements Observable {
|
||||
|
||||
@Override
|
||||
public Profile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (json == null || json == JsonNull.INSTANCE || !(json instanceof JsonObject)) return null;
|
||||
if (json == JsonNull.INSTANCE || !(json instanceof JsonObject)) return null;
|
||||
JsonObject obj = (JsonObject) json;
|
||||
String gameDir = Optional.ofNullable(obj.get("gameDir")).map(JsonElement::getAsString).orElse("");
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -482,7 +483,7 @@ public final class VersionSetting {
|
||||
else if (isUsesCustomJavaDir()) {
|
||||
try {
|
||||
return JavaVersion.fromExecutable(Paths.get(getJavaDir()));
|
||||
} catch (IOException e) {
|
||||
} catch (IOException | InvalidPathException e) {
|
||||
return null; // Custom Java Directory not found,
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(getJava())) {
|
||||
@@ -537,6 +538,7 @@ public final class VersionSetting {
|
||||
.setGameDir(gameDir)
|
||||
.setJava(javaVersion)
|
||||
.setVersionName(Metadata.TITLE)
|
||||
.setVersionType(Metadata.TITLE)
|
||||
.setProfileName(Metadata.TITLE)
|
||||
.setMinecraftArgs(getMinecraftArgs())
|
||||
.setJavaArgs(getJavaArgs())
|
||||
@@ -595,7 +597,7 @@ public final class VersionSetting {
|
||||
|
||||
@Override
|
||||
public VersionSetting deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (json == null || json == JsonNull.INSTANCE || !(json instanceof JsonObject))
|
||||
if (json == JsonNull.INSTANCE || !(json instanceof JsonObject))
|
||||
return null;
|
||||
JsonObject obj = (JsonObject) json;
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ public final class Controllers {
|
||||
dialog(i18n("launcher.cache_directory.invalid"));
|
||||
}
|
||||
|
||||
Task.of(JavaVersion::initialize).start();
|
||||
Task.runAsync(JavaVersion::initialize).start();
|
||||
|
||||
scene = new Scene(decorator.getDecorator(), 800, 519);
|
||||
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
|
||||
|
||||
@@ -397,7 +397,7 @@ public final class FXUtils {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum> property) {
|
||||
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum<?>> property) {
|
||||
unbindEnum(comboBox);
|
||||
ChangeListener<Number> listener = (a, b, newValue) ->
|
||||
((Property) property).setValue(property.getValue().getClass().getEnumConstants()[newValue.intValue()]);
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -40,11 +41,16 @@ public class InstallerItem extends BorderPane {
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
|
||||
{
|
||||
if (version != null) {
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.setTitle(artifact);
|
||||
item.setSubtitle(i18n("archive.version") + ": " + version);
|
||||
setCenter(item);
|
||||
} else {
|
||||
Label label = new Label(artifact);
|
||||
label.setStyle("-fx-font-size: 15px;");
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
setCenter(label);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
@@ -28,7 +27,6 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.account.AddAccountPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
@@ -39,7 +37,6 @@ import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
@@ -118,11 +115,11 @@ public final class LeftPaneController extends AdvancedListBox {
|
||||
if (repository.getVersionCount() == 0) {
|
||||
File modpackFile = new File("modpack.zip").getAbsoluteFile();
|
||||
if (modpackFile.exists()) {
|
||||
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
|
||||
.thenApply(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
||||
.thenApply(modpack -> ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), this::checkAccount)).executor())
|
||||
.thenAccept(Schedulers.javafx(), executor -> {
|
||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
|
||||
.thenApplyAsync(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
||||
.thenApplyAsync(modpack -> ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.withRunAsync(Schedulers.javafx(), this::checkAccount).executor())
|
||||
.thenAcceptAsync(Schedulers.javafx(), executor -> {
|
||||
Controllers.taskDialog(executor, i18n("modpack.installing"));
|
||||
executor.start();
|
||||
}).start();
|
||||
|
||||
@@ -23,7 +23,12 @@ import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -33,7 +38,6 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -114,7 +118,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
launchButton.setPrefHeight(55);
|
||||
launchButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
launchButton.getStyleClass().add("jfx-button-raised");
|
||||
launchButton.setOnMouseClicked(e -> launch());
|
||||
launchButton.setOnAction(e -> launch());
|
||||
launchButton.setDefaultButton(true);
|
||||
launchButton.setClip(new Rectangle(-100, -100, 310, 200));
|
||||
{
|
||||
VBox graphic = new VBox();
|
||||
|
||||
@@ -50,7 +50,6 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
@@ -118,7 +117,7 @@ public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
selectedItemPropertyFor(proxyConfigurationGroup, Proxy.Type.class).bindBidirectional(config().proxyTypeProperty());
|
||||
// ====
|
||||
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
fileCommonLocation.loadChildren(Collections.singletonList(
|
||||
fileCommonLocation.createChildren(i18n("launcher.cache_directory.default"), EnumCommonDirectory.DEFAULT)
|
||||
), EnumCommonDirectory.CUSTOM);
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
|
||||
@@ -61,7 +61,7 @@ public class AccountLoginPane extends StackPane {
|
||||
String password = txtPassword.getText();
|
||||
progressBar.setVisible(true);
|
||||
lblCreationWarning.setText("");
|
||||
Task.ofResult(() -> oldAccount.logInWithPassword(password))
|
||||
Task.supplyAsync(() -> oldAccount.logInWithPassword(password))
|
||||
.whenComplete(Schedulers.javafx(), authInfo -> {
|
||||
success.accept(authInfo);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
|
||||
@@ -77,7 +77,7 @@ public class AddAccountPane extends StackPane {
|
||||
@FXML private SpinnerPane acceptPane;
|
||||
@FXML private HBox linksContainer;
|
||||
|
||||
private ListProperty<Hyperlink> links = new SimpleListProperty<>();;
|
||||
private ListProperty<Hyperlink> links = new SimpleListProperty<>();
|
||||
|
||||
public AddAccountPane() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
|
||||
@@ -197,7 +197,7 @@ public class AddAccountPane extends StackPane {
|
||||
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
|
||||
Object additionalData = getAuthAdditionalData();
|
||||
|
||||
Task.ofResult(() -> factory.create(new Selector(), username, password, additionalData))
|
||||
Task.supplyAsync(() -> factory.create(new Selector(), username, password, additionalData))
|
||||
.whenComplete(Schedulers.javafx(), account -> {
|
||||
int oldIndex = Accounts.getAccounts().indexOf(account);
|
||||
if (oldIndex == -1) {
|
||||
|
||||
@@ -104,13 +104,13 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
nextPane.showSpinner();
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.of(() -> {
|
||||
Task.runAsync(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.locateServer(url);
|
||||
}).whenComplete(Schedulers.javafx(), (isDependentSucceeded, exception) -> {
|
||||
}).whenComplete(Schedulers.javafx(), exception -> {
|
||||
addServerPane.setDisable(false);
|
||||
nextPane.hideSpinner();
|
||||
|
||||
if (isDependentSucceeded) {
|
||||
if (exception == null) {
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
|
||||
@@ -21,10 +21,6 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
@@ -32,8 +28,6 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.smoothScrolling;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AuthlibInjectorServersPage extends ListPage<AuthlibInjectorServerItem> implements DecoratorPage {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
@@ -75,9 +75,7 @@ public class JFXCheckBoxTreeTableCell<S,T> extends TreeTableCell<S,T> {
|
||||
}
|
||||
|
||||
private ObjectProperty<Callback<Integer, ObservableValue<Boolean>>>
|
||||
selectedStateCallback =
|
||||
new SimpleObjectProperty<Callback<Integer, ObservableValue<Boolean>>>(
|
||||
this, "selectedStateCallback");
|
||||
selectedStateCallback = new SimpleObjectProperty<>(this, "selectedStateCallback");
|
||||
|
||||
public final ObjectProperty<Callback<Integer, ObservableValue<Boolean>>> selectedStateCallbackProperty() {
|
||||
return selectedStateCallback;
|
||||
|
||||
@@ -32,7 +32,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MessageDialogPane extends StackPane {
|
||||
|
||||
public static enum MessageType {
|
||||
public enum MessageType {
|
||||
ERROR,
|
||||
INFORMATION,
|
||||
WARNING,
|
||||
|
||||
@@ -26,11 +26,17 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameInstallTask;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackUpdateTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
@@ -42,7 +48,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class TaskListPane extends StackPane {
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final Map<Task, ProgressListNode> nodes = new HashMap<>();
|
||||
private final Map<Task<?>, ProgressListNode> nodes = new HashMap<>();
|
||||
private final ReadOnlyIntegerWrapper finishedTasks = new ReadOnlyIntegerWrapper();
|
||||
private final ReadOnlyIntegerWrapper totTasks = new ReadOnlyIntegerWrapper();
|
||||
|
||||
@@ -72,17 +78,19 @@ public final class TaskListPane extends StackPane {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReady(Task task) {
|
||||
public void onReady(Task<?> task) {
|
||||
Platform.runLater(() -> totTasks.set(totTasks.getValue() + 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunning(Task task) {
|
||||
public void onRunning(Task<?> task) {
|
||||
if (!task.getSignificance().shouldShow())
|
||||
return;
|
||||
|
||||
if (task instanceof GameAssetDownloadTask) {
|
||||
task.setName(i18n("assets.download_all"));
|
||||
} else if (task instanceof GameInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.game")));
|
||||
} else if (task instanceof ForgeInstallTask) {
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
|
||||
} else if (task instanceof LiteLoaderInstallTask) {
|
||||
@@ -113,7 +121,7 @@ public final class TaskListPane extends StackPane {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinished(Task task) {
|
||||
public void onFinished(Task<?> task) {
|
||||
ProgressListNode node = nodes.remove(task);
|
||||
if (node == null)
|
||||
return;
|
||||
@@ -125,7 +133,7 @@ public final class TaskListPane extends StackPane {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed(Task task, Throwable throwable) {
|
||||
public void onFailed(Task<?> task, Throwable throwable) {
|
||||
ProgressListNode node = nodes.remove(task);
|
||||
if (node == null)
|
||||
return;
|
||||
@@ -142,7 +150,7 @@ public final class TaskListPane extends StackPane {
|
||||
private final Label title = new Label();
|
||||
private final Label state = new Label();
|
||||
|
||||
public ProgressListNode(Task task) {
|
||||
public ProgressListNode(Task<?> task) {
|
||||
bar.progressProperty().bind(task.progressProperty());
|
||||
title.setText(task.getName());
|
||||
state.textProperty().bind(task.messageProperty());
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.svg.SVGGlyph;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.geometry.BoundingBox;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -56,7 +55,6 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean allowMove, isDragging;
|
||||
private BoundingBox originalBox, maximizedBox;
|
||||
|
||||
/**
|
||||
* Constructor for all SkinBase instances.
|
||||
@@ -125,16 +123,13 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
{
|
||||
StackPane container = new StackPane();
|
||||
Bindings.bindContent(container.getChildren(), skinnable.containerProperty());
|
||||
ListChangeListener<Node> listener = new ListChangeListener<Node>() {
|
||||
@Override
|
||||
public void onChanged(Change<? extends Node> c) {
|
||||
if (skinnable.getContainer().isEmpty()) {
|
||||
container.setMouseTransparent(true);
|
||||
container.setVisible(false);
|
||||
} else {
|
||||
container.setMouseTransparent(false);
|
||||
container.setVisible(true);
|
||||
}
|
||||
ListChangeListener<Node> listener = c -> {
|
||||
if (skinnable.getContainer().isEmpty()) {
|
||||
container.setMouseTransparent(true);
|
||||
container.setVisible(false);
|
||||
} else {
|
||||
container.setMouseTransparent(false);
|
||||
container.setVisible(true);
|
||||
}
|
||||
};
|
||||
skinnable.containerProperty().addListener(listener);
|
||||
|
||||
@@ -23,6 +23,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -33,6 +34,7 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
@@ -42,6 +44,8 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
@FXML
|
||||
private VBox list;
|
||||
@FXML
|
||||
private JFXButton btnFabric;
|
||||
@FXML
|
||||
private JFXButton btnForge;
|
||||
@FXML
|
||||
private JFXButton btnLiteLoader;
|
||||
@@ -52,6 +56,8 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
@FXML
|
||||
private Label lblVersionName;
|
||||
@FXML
|
||||
private Label lblFabric;
|
||||
@FXML
|
||||
private Label lblForge;
|
||||
@FXML
|
||||
private Label lblLiteLoader;
|
||||
@@ -69,26 +75,17 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
lblGameVersion.setText(provider.getGameVersion());
|
||||
lblVersionName.setText(provider.getVersion().getId());
|
||||
|
||||
btnForge.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 0);
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), provider.getGameVersion(), downloadProvider, "forge", () -> {
|
||||
controller.onPrev(false);
|
||||
}));
|
||||
});
|
||||
JFXButton[] buttons = new JFXButton[]{btnFabric, btnForge, btnLiteLoader, btnOptiFine};
|
||||
String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"};
|
||||
|
||||
btnLiteLoader.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 1);
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), provider.getGameVersion(), downloadProvider, "liteloader", () -> {
|
||||
controller.onPrev(false);
|
||||
}));
|
||||
});
|
||||
|
||||
btnOptiFine.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 2);
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), provider.getGameVersion(), downloadProvider, "optifine", () -> {
|
||||
controller.onPrev(false);
|
||||
}));
|
||||
});
|
||||
for (int i = 0; i < libraryIds.length; ++i) {
|
||||
String libraryId = libraryIds[i];
|
||||
buttons[i].setOnMouseClicked(e -> {
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), provider.getGameVersion(), downloadProvider, libraryId, () -> {
|
||||
controller.onPrev(false);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
btnInstall.setOnMouseClicked(e -> onInstall());
|
||||
}
|
||||
@@ -109,30 +106,27 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
|
||||
btnForge.setDisable(provider.getForge() != null);
|
||||
if (provider.getForge() != null || controller.getSettings().containsKey("forge"))
|
||||
lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge")));
|
||||
else
|
||||
lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge")));
|
||||
|
||||
btnLiteLoader.setDisable(provider.getLiteLoader() != null);
|
||||
if (provider.getLiteLoader() != null || controller.getSettings().containsKey("liteloader"))
|
||||
lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader")));
|
||||
else
|
||||
lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader")));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(provider.getVersion().resolvePreservingPatches(provider.getProfile().getRepository()));
|
||||
String fabric = analyzer.getVersion(FABRIC).orElse(null);
|
||||
String forge = analyzer.getVersion(FORGE).orElse(null);
|
||||
String liteLoader = analyzer.getVersion(LITELOADER).orElse(null);
|
||||
String optiFine = analyzer.getVersion(OPTIFINE).orElse(null);
|
||||
|
||||
btnOptiFine.setDisable(provider.getOptiFine() != null);
|
||||
if (provider.getOptiFine() != null || controller.getSettings().containsKey("optifine"))
|
||||
lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine")));
|
||||
else
|
||||
lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine")));
|
||||
Label[] labels = new Label[]{lblFabric, lblForge, lblLiteLoader, lblOptiFine};
|
||||
String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"};
|
||||
String[] versions = new String[]{fabric, forge, liteLoader, optiFine};
|
||||
|
||||
for (int i = 0; i < libraryIds.length; ++i) {
|
||||
String libraryId = libraryIds[i];
|
||||
if (versions[i] != null || controller.getSettings().containsKey(libraryId))
|
||||
labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + Lang.nonNull(getVersion(libraryId), versions[i]));
|
||||
else
|
||||
labels[i].setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
settings.remove(INSTALLER_TYPE);
|
||||
}
|
||||
|
||||
public static final String INSTALLER_TYPE = "INSTALLER_TYPE";
|
||||
}
|
||||
|
||||
@@ -18,18 +18,17 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.VersionMismatchException;
|
||||
import org.jackhuang.hmcl.download.fabric.FabricInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.DownloadException;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
@@ -41,26 +40,17 @@ import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class InstallerWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final String gameVersion;
|
||||
private final Version version;
|
||||
private final String forge;
|
||||
private final String liteLoader;
|
||||
private final String optiFine;
|
||||
|
||||
public InstallerWizardProvider(Profile profile, String gameVersion, Version version) {
|
||||
this.profile = profile;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
forge = analyzer.get(FORGE).map(Library::getVersion).orElse(null);
|
||||
liteLoader = analyzer.get(LITELOADER).map(Library::getVersion).orElse(null);
|
||||
optiFine = analyzer.get(OPTIFINE).map(Library::getVersion).orElse(null);
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
@@ -75,18 +65,6 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getForge() {
|
||||
return forge;
|
||||
}
|
||||
|
||||
public String getLiteLoader() {
|
||||
return liteLoader;
|
||||
}
|
||||
|
||||
public String getOptiFine() {
|
||||
return optiFine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
}
|
||||
@@ -96,18 +74,14 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> alertFailureMessage(exception, next));
|
||||
|
||||
TaskResult<Version> ret = Task.ofResult(() -> version);
|
||||
Task<Version> ret = Task.supplyAsync(() -> version);
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("forge")));
|
||||
for (Object value : settings.values()) {
|
||||
if (value instanceof RemoteVersion)
|
||||
ret = ret.thenComposeAsync(profile.getDependency().installLibraryAsync((RemoteVersion) value));
|
||||
}
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("liteloader")));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
ret = ret.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get("optifine")));
|
||||
|
||||
return ret.then(profile.getRepository().refreshVersionsAsync());
|
||||
return ret.thenComposeAsync(profile.getRepository().refreshVersionsAsync());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,9 +116,10 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
} else {
|
||||
Controllers.dialog(i18n("install.failed.downloading.detail", ((DownloadException) exception).getUrl()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
}
|
||||
} else if (exception instanceof OptiFineInstallTask.UnsupportedOptiFineInstallationException) {
|
||||
} else if (exception instanceof OptiFineInstallTask.UnsupportedOptiFineInstallationException ||
|
||||
exception instanceof FabricInstallTask.UnsupportedFabricInstallationException) {
|
||||
Controllers.dialog(i18n("install.failed.optifine_conflict"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof UnsupportedOperationException) {
|
||||
} else if (exception instanceof DefaultDependencyManager.UnsupportedLibraryInstallerException) {
|
||||
Controllers.dialog(i18n("install.failed.install_online"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof VersionMismatchException) {
|
||||
VersionMismatchException e = ((VersionMismatchException) exception);
|
||||
|
||||
@@ -30,8 +30,11 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -42,6 +45,9 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
@FXML
|
||||
private VBox list;
|
||||
|
||||
@FXML
|
||||
private JFXButton btnFabric;
|
||||
|
||||
@FXML
|
||||
private JFXButton btnForge;
|
||||
|
||||
@@ -54,6 +60,9 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
@FXML
|
||||
private Label lblGameVersion;
|
||||
|
||||
@FXML
|
||||
private Label lblFabric;
|
||||
|
||||
@FXML
|
||||
private Label lblForge;
|
||||
|
||||
@@ -77,24 +86,20 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
String gameVersion = ((RemoteVersion) controller.getSettings().get("game")).getGameVersion();
|
||||
Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s));
|
||||
hasVersion.setMessage(i18n("install.new_game.already_exists"));
|
||||
txtName.getValidators().add(hasVersion);
|
||||
Validator nameValidator = new Validator(OperatingSystem::isNameValid);
|
||||
nameValidator.setMessage(i18n("install.new_game.malformed"));
|
||||
txtName.getValidators().addAll(hasVersion, nameValidator);
|
||||
txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate()));
|
||||
txtName.setText(gameVersion);
|
||||
|
||||
btnForge.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 0);
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false)));
|
||||
});
|
||||
JFXButton[] buttons = new JFXButton[]{btnFabric, btnForge, btnLiteLoader, btnOptiFine};
|
||||
String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"};
|
||||
|
||||
btnLiteLoader.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 1);
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false)));
|
||||
});
|
||||
|
||||
btnOptiFine.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 2);
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false)));
|
||||
});
|
||||
for (int i = 0; i < libraryIds.length; ++i) {
|
||||
String libraryId = libraryIds[i];
|
||||
buttons[i].setOnMouseClicked(e ->
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, downloadProvider, libraryId, () -> controller.onPrev(false))));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -109,25 +114,21 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + getVersion("game"));
|
||||
if (controller.getSettings().containsKey("forge"))
|
||||
lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + getVersion("forge"));
|
||||
else
|
||||
lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge")));
|
||||
|
||||
if (controller.getSettings().containsKey("liteloader"))
|
||||
lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + getVersion("liteloader"));
|
||||
else
|
||||
lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader")));
|
||||
Label[] labels = new Label[]{lblFabric, lblForge, lblLiteLoader, lblOptiFine};
|
||||
String[] libraryIds = new String[]{"fabric", "forge", "liteloader", "optifine"};
|
||||
|
||||
if (controller.getSettings().containsKey("optifine"))
|
||||
lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + getVersion("optifine"));
|
||||
else
|
||||
lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine")));
|
||||
for (int i = 0; i < libraryIds.length; ++i) {
|
||||
String libraryId = libraryIds[i];
|
||||
if (controller.getSettings().containsKey(libraryId))
|
||||
labels[i].setText(i18n("install.installer.version", i18n("install.installer." + libraryId)) + ": " + getVersion(libraryId));
|
||||
else
|
||||
labels[i].setText(i18n("install.installer.not_installed", i18n("install.installer." + libraryId)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
settings.remove(INSTALLER_TYPE);
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -135,6 +136,4 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
controller.getSettings().put("name", txtName.getText());
|
||||
controller.onFinish();
|
||||
}
|
||||
|
||||
public static final String INSTALLER_TYPE = "INSTALLER_TYPE";
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
@@ -71,7 +71,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
settings.put(PROFILE, profile);
|
||||
}
|
||||
|
||||
private Task finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||
private Task<Void> finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||
if (!settings.containsKey(ModpackPage.MODPACK_FILE))
|
||||
return null;
|
||||
|
||||
@@ -93,7 +93,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
return null;
|
||||
} else {
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack)
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
.thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,8 +109,8 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
}
|
||||
|
||||
spinnerPane.showSpinner();
|
||||
Task.ofResult(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
|
||||
.thenApply(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
|
||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
|
||||
.thenApplyAsync(encoding -> manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding))
|
||||
.whenComplete(Schedulers.javafx(), manifest -> {
|
||||
spinnerPane.hideSpinner();
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest);
|
||||
|
||||
@@ -19,16 +19,14 @@ package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.download.InstallerWizardProvider.alertFailureMessage;
|
||||
@@ -39,14 +37,14 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
private final String gameVersion;
|
||||
private final Version version;
|
||||
private final String libraryId;
|
||||
private final Library oldLibrary;
|
||||
private final String oldLibraryVersion;
|
||||
|
||||
public UpdateInstallerWizardProvider(Profile profile, String gameVersion, Version version, String libraryId, Library oldLibrary) {
|
||||
public UpdateInstallerWizardProvider(@NotNull Profile profile, @NotNull String gameVersion, @NotNull Version version, @NotNull String libraryId, @NotNull String oldLibraryVersion) {
|
||||
this.profile = profile;
|
||||
this.gameVersion = gameVersion;
|
||||
this.version = version;
|
||||
this.libraryId = libraryId;
|
||||
this.oldLibrary = oldLibrary;
|
||||
this.oldLibraryVersion = oldLibraryVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -60,11 +58,8 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
|
||||
// We remove library but not save it,
|
||||
// so if installation failed will not break down current version.
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(oldLibrary);
|
||||
return new MaintainTask(version.setLibraries(newList))
|
||||
.thenCompose(profile.getDependency().installLibraryAsync((RemoteVersion) settings.get(libraryId)))
|
||||
.then(profile.getRepository().refreshVersionsAsync());
|
||||
return profile.getDependency().installLibraryAsync(version, (RemoteVersion) settings.get(libraryId))
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,7 +68,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, provider, libraryId, () -> {
|
||||
Controllers.confirmDialog(i18n("install.change_version.confirm", i18n("install.installer." + libraryId), oldLibrary.getVersion(), ((RemoteVersion) settings.get(libraryId)).getSelfVersion()),
|
||||
Controllers.confirmDialog(i18n("install.change_version.confirm", i18n("install.installer." + libraryId), oldLibraryVersion, ((RemoteVersion) settings.get(libraryId)).getSelfVersion()),
|
||||
i18n("install.change_version"), controller::onFinish, controller::onCancel);
|
||||
});
|
||||
default:
|
||||
|
||||
@@ -43,24 +43,19 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
settings.put(PROFILE, profile);
|
||||
}
|
||||
|
||||
private Task finishVersionDownloadingAsync(Map<String, Object> settings) {
|
||||
private Task<Void> finishVersionDownloadingAsync(Map<String, Object> settings) {
|
||||
GameBuilder builder = profile.getDependency().gameBuilder();
|
||||
|
||||
String name = (String) settings.get("name");
|
||||
builder.name(name);
|
||||
builder.gameVersion(((RemoteVersion) settings.get("game")).getGameVersion());
|
||||
|
||||
if (settings.containsKey("forge"))
|
||||
builder.version((RemoteVersion) settings.get("forge"));
|
||||
for (Map.Entry<String, Object> entry : settings.entrySet())
|
||||
if (!"game".equals(entry.getKey()) && entry.getValue() instanceof RemoteVersion)
|
||||
builder.version((RemoteVersion) entry.getValue());
|
||||
|
||||
if (settings.containsKey("liteloader"))
|
||||
builder.version((RemoteVersion) settings.get("liteloader"));
|
||||
|
||||
if (settings.containsKey("optifine"))
|
||||
builder.version((RemoteVersion) settings.get("optifine"));
|
||||
|
||||
return builder.buildAsync().whenComplete((a, b) -> profile.getRepository().refreshVersions())
|
||||
.then(Task.of(Schedulers.javafx(), () -> profile.setSelectedVersion(name)));
|
||||
return builder.buildAsync().whenComplete(any -> profile.getRepository().refreshVersions())
|
||||
.thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -128,8 +128,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
@Override
|
||||
public void refresh() {
|
||||
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
||||
executor = versionList.refreshAsync(gameVersion, downloadProvider).whenComplete((isDependentSucceeded, exception) -> {
|
||||
if (isDependentSucceeded) {
|
||||
executor = versionList.refreshAsync(gameVersion, downloadProvider).whenComplete(exception -> {
|
||||
if (exception == null) {
|
||||
List<VersionsPageItem> items = loadVersions();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
|
||||
@@ -20,14 +20,18 @@ package org.jackhuang.hmcl.ui.export;
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackManager;
|
||||
import org.jackhuang.hmcl.mod.ModAdviser;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;
|
||||
import org.jackhuang.hmcl.setting.Config;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
@@ -57,30 +61,40 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> whitelist = (List<String>) settings.get(ModpackFileSelectionPage.MODPACK_FILE_SELECTION);
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
boolean includeLauncher = (Boolean) settings.get(ModpackInfoPage.MODPACK_INCLUDE_LAUNCHER) && launcherJar != null;
|
||||
File modpackFile = (File) settings.get(ModpackInfoPage.MODPACK_FILE);
|
||||
String modpackName = (String) settings.get(ModpackInfoPage.MODPACK_NAME);
|
||||
String modpackAuthor = (String) settings.get(ModpackInfoPage.MODPACK_AUTHOR);
|
||||
String modpackVersion = (String) settings.get(ModpackInfoPage.MODPACK_VERSION);
|
||||
String modpackDescription = (String) settings.get(ModpackInfoPage.MODPACK_DESCRIPTION);
|
||||
String modpackType = (String) settings.get(ModpackTypeSelectionPage.MODPACK_TYPE);
|
||||
boolean includeLauncher = (Boolean) settings.get(ModpackInfoPage.MODPACK_INCLUDE_LAUNCHER);
|
||||
|
||||
return new Task() {
|
||||
Task dependency = null;
|
||||
switch (modpackType) {
|
||||
case ModpackTypeSelectionPage.MODPACK_TYPE_HMCL:
|
||||
return exportAsHMCL(whitelist, modpackFile, modpackName, modpackAuthor, modpackVersion, modpackDescription, includeLauncher);
|
||||
case ModpackTypeSelectionPage.MODPACK_TYPE_MULTIMC:
|
||||
return exportAsMultiMC(whitelist, modpackFile, modpackName, modpackAuthor, modpackVersion, modpackDescription);
|
||||
default:
|
||||
throw new IllegalStateException("Unrecognized modpack type " + modpackType);
|
||||
}
|
||||
}
|
||||
|
||||
private Task<?> exportAsHMCL(List<String> whitelist, File modpackFile, String modpackName, String modpackAuthor, String modpackVersion, String modpackDescription, boolean includeLauncherRaw) {
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
boolean includeLauncher = includeLauncherRaw && launcherJar != null;
|
||||
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency = null;
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
File modpackFile = (File) settings.get(ModpackInfoPage.MODPACK_FILE);
|
||||
File tempModpack = includeLauncher ? Files.createTempFile("hmcl", ".zip").toFile() : modpackFile;
|
||||
|
||||
dependency = new HMCLModpackExportTask(profile.getRepository(), version, whitelist,
|
||||
new Modpack(
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_NAME),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_AUTHOR),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_VERSION),
|
||||
null,
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_DESCRIPTION),
|
||||
StandardCharsets.UTF_8,
|
||||
null
|
||||
), tempModpack);
|
||||
new Modpack(modpackName, modpackAuthor, modpackVersion, null, modpackDescription, StandardCharsets.UTF_8, null), tempModpack);
|
||||
|
||||
if (includeLauncher) {
|
||||
dependency = dependency.then(Task.of(() -> {
|
||||
dependency = dependency.thenRunAsync(() -> {
|
||||
try (Zipper zip = new Zipper(modpackFile.toPath())) {
|
||||
Config exported = new Config();
|
||||
|
||||
@@ -109,12 +123,55 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
for (File jar : launcherJar)
|
||||
zip.putFile(jar, jar.getName());
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return Collections.singleton(dependency);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Task<?> exportAsMultiMC(List<String> whitelist, File modpackFile, String modpackName, String modpackAuthor, String modpackVersion, String modpackDescription) {
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency;
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
VersionSetting vs = profile.getVersionSetting(version);
|
||||
dependency = new MultiMCModpackExportTask(profile.getRepository(), version, whitelist,
|
||||
new MultiMCInstanceConfiguration(
|
||||
"OneSix",
|
||||
modpackName + "-" + modpackVersion,
|
||||
null,
|
||||
Lang.toIntOrNull(vs.getPermSize()),
|
||||
vs.getWrapper(),
|
||||
vs.getPreLaunchCommand(),
|
||||
null,
|
||||
modpackDescription,
|
||||
null,
|
||||
vs.getJavaArgs(),
|
||||
vs.isFullscreen(),
|
||||
vs.getWidth(),
|
||||
vs.getHeight(),
|
||||
vs.getMaxMemory(),
|
||||
vs.getMinMemory(),
|
||||
vs.isShowLogs(),
|
||||
/* showConsoleOnError */ true,
|
||||
/* autoCloseConsole */ false,
|
||||
/* overrideMemory */ true,
|
||||
/* overrideJavaLocation */ false,
|
||||
/* overrideJavaArgs */ true,
|
||||
/* overrideConsole */ true,
|
||||
/* overrideCommands */ true,
|
||||
/* overrideWindow */ true
|
||||
), modpackFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return Collections.singleton(dependency);
|
||||
}
|
||||
};
|
||||
@@ -124,7 +181,8 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
switch (step) {
|
||||
case 0: return new ModpackInfoPage(controller, version);
|
||||
case 1: return new ModpackFileSelectionPage(controller, profile, version, HMCLModpackManager::suggestMod);
|
||||
case 1: return new ModpackFileSelectionPage(controller, profile, version, ModAdviser::suggestMod);
|
||||
case 2: return new ModpackTypeSelectionPage(controller);
|
||||
default: throw new IllegalArgumentException("step");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.game.ModAdviser;
|
||||
import org.jackhuang.hmcl.mod.ModAdviser;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
@@ -146,7 +146,7 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
LinkedList<String> list = new LinkedList<>();
|
||||
getFilesNeeded(rootNode, "minecraft", list);
|
||||
controller.getSettings().put(MODPACK_FILE_SELECTION, list);
|
||||
controller.onFinish();
|
||||
controller.onNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.export;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModpackTypeSelectionPage extends StackPane implements WizardPage {
|
||||
private final WizardController controller;
|
||||
@FXML
|
||||
private JFXButton btnHMCL;
|
||||
@FXML
|
||||
private JFXButton btnMultiMC;
|
||||
|
||||
public ModpackTypeSelectionPage(WizardController controller) {
|
||||
this.controller = controller;
|
||||
FXUtils.loadFXML(this, "/assets/fxml/modpack/type.fxml");
|
||||
|
||||
JFXButton[] buttons = new JFXButton[]{btnHMCL, btnMultiMC};
|
||||
String[] types = new String[]{MODPACK_TYPE_HMCL, MODPACK_TYPE_MULTIMC};
|
||||
for (int i = 0; i < types.length; ++i) {
|
||||
String type = types[i];
|
||||
buttons[i].setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(MODPACK_TYPE, type);
|
||||
controller.onFinish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return i18n("modpack.wizard.step.3.title");
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "modpack.type";
|
||||
|
||||
public static final String MODPACK_TYPE_MULTIMC = "multimc";
|
||||
public static final String MODPACK_TYPE_HMCL = "hmcl";
|
||||
}
|
||||
@@ -26,11 +26,9 @@ import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
@@ -38,7 +36,6 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -60,17 +60,18 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
|
||||
|
||||
setItems(items = MappedObservableList.create(datapack.getInfo(), DatapackListPageSkin.DatapackInfoObject::new));
|
||||
|
||||
FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)), mods -> {
|
||||
mods.forEach(it -> {
|
||||
try {
|
||||
Datapack zip = new Datapack(it.toPath());
|
||||
zip.loadFromZip();
|
||||
zip.installTo(worldDir);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + it, e);
|
||||
}
|
||||
});
|
||||
}, this::refresh);
|
||||
FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)),
|
||||
mods -> mods.forEach(this::installSingleDatapack), this::refresh);
|
||||
}
|
||||
|
||||
private void installSingleDatapack(File datapack) {
|
||||
try {
|
||||
Datapack zip = new Datapack(datapack.toPath());
|
||||
zip.loadFromZip();
|
||||
zip.installTo(worldDir);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + datapack, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,8 +81,8 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
|
||||
|
||||
public void refresh() {
|
||||
setLoading(true);
|
||||
Task.of(datapack::loadFromDir)
|
||||
.with(Task.of(Schedulers.javafx(), () -> setLoading(false)))
|
||||
Task.runAsync(datapack::loadFromDir)
|
||||
.withRunAsync(Schedulers.javafx(), () -> setLoading(false))
|
||||
.start();
|
||||
}
|
||||
|
||||
@@ -97,15 +98,7 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
|
||||
if (res != null)
|
||||
res.forEach(it -> {
|
||||
try {
|
||||
Datapack zip = new Datapack(it.toPath());
|
||||
zip.loadFromZip();
|
||||
zip.installTo(worldDir);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + it, e);
|
||||
}
|
||||
});
|
||||
res.forEach(this::installSingleDatapack);
|
||||
|
||||
datapack.loadFromDir();
|
||||
}
|
||||
|
||||
@@ -18,25 +18,29 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
|
||||
import static org.jackhuang.hmcl.util.Lang.handleUncaught;
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GameItem extends Control {
|
||||
|
||||
private static final ThreadPoolExecutor POOL_VERSION_RESOLVE = threadPool("VersionResolve", true, 1, 1, TimeUnit.SECONDS);
|
||||
@@ -52,13 +56,21 @@ public class GameItem extends Control {
|
||||
this.version = id;
|
||||
|
||||
// GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms)
|
||||
CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse("Unknown"), POOL_VERSION_RESOLVE)
|
||||
CompletableFuture.supplyAsync(() -> GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE)
|
||||
.thenAcceptAsync(game -> {
|
||||
StringBuilder libraries = new StringBuilder(game);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getVersion(id));
|
||||
analyzer.get(FORGE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))));
|
||||
analyzer.get(LITELOADER).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))));
|
||||
analyzer.get(OPTIFINE).ifPresent(library -> libraries.append(", ").append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
String libraryId = mark.getLibraryId();
|
||||
String libraryVersion = mark.getLibraryVersion();
|
||||
if (libraryId.equals(MINECRAFT.getPatchId())) continue;
|
||||
if (I18n.hasKey("install.installer." + libraryId)) {
|
||||
libraries.append(", ").append(i18n("install.installer." + libraryId));
|
||||
if (libraryVersion != null)
|
||||
libraries.append(": ").append(modifyVersion("", libraryVersion.replaceAll("(?i)" + libraryId, "")));
|
||||
}
|
||||
}
|
||||
|
||||
subtitle.set(libraries.toString());
|
||||
}, Platform::runLater)
|
||||
.exceptionally(handleUncaught);
|
||||
|
||||
@@ -22,7 +22,9 @@ import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -49,22 +51,24 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
chkSelected.setToggleGroup(skinnable.getToggleGroup());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
root.setCenter(new GameItem(skinnable.getProfile(), skinnable.getVersion()));
|
||||
GameItem gameItem = new GameItem(skinnable.getProfile(), skinnable.getVersion());
|
||||
gameItem.setMouseTransparent(true);
|
||||
root.setCenter(gameItem);
|
||||
|
||||
PopupMenu menu = new PopupMenu();
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
menu.getContent().setAll(
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch.test"), FXUtils.withJFXPopupClosing(skinnable::launch, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(skinnable::generateLaunchScript, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.gear(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.manage"), FXUtils.withJFXPopupClosing(skinnable::modifyGameSettings, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(skinnable::rename, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(skinnable::remove, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(skinnable::export, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(skinnable::browse, popup)),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.launch(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch.test"), FXUtils.withJFXPopupClosing(skinnable::launch, popup)),
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(skinnable::generateLaunchScript, popup)));
|
||||
new IconedMenuItem(FXUtils.limitingSize(SVG.folderOpen(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("folder.game"), FXUtils.withJFXPopupClosing(skinnable::browse, popup)));
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
@@ -91,5 +95,16 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
|
||||
getChildren().setAll(root);
|
||||
|
||||
root.setCursor(Cursor.HAND);
|
||||
root.setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY) {
|
||||
if (e.getClickCount() == 1) {
|
||||
skinnable.modifyGameSettings();
|
||||
}
|
||||
} else if (e.getButton() == MouseButton.SECONDARY) {
|
||||
popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,29 +21,31 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
||||
import org.jackhuang.hmcl.ui.ListPageBase;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.ToolbarListPageSkin;
|
||||
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -68,38 +70,36 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
public void loadVersion(Profile profile, String versionId) {
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
this.version = profile.getRepository().getResolvedVersion(versionId);
|
||||
this.version = profile.getRepository().getVersion(versionId);
|
||||
this.gameVersion = null;
|
||||
|
||||
Task.ofResult(() -> {
|
||||
Task.supplyAsync(() -> {
|
||||
gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse(null);
|
||||
|
||||
return LibraryAnalyzer.analyze(version);
|
||||
}).thenAccept(Schedulers.javafx(), analyzer -> {
|
||||
Function<Library, Consumer<InstallerItem>> removeAction = library -> x -> {
|
||||
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
|
||||
newList.remove(library);
|
||||
new MaintainTask(version.setLibraries(newList))
|
||||
.then(maintainedVersion -> new VersionJsonSaveTask(profile.getRepository(), maintainedVersion))
|
||||
.with(profile.getRepository().refreshVersionsAsync())
|
||||
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
|
||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId));
|
||||
}).thenAcceptAsync(Schedulers.javafx(), analyzer -> {
|
||||
Function<String, Consumer<InstallerItem>> removeAction = libraryId -> x -> {
|
||||
profile.getDependency().removeLibraryAsync(version, libraryId)
|
||||
.thenComposeAsync(profile.getRepository()::save)
|
||||
.withComposeAsync(profile.getRepository().refreshVersionsAsync())
|
||||
.withRunAsync(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId))
|
||||
.start();
|
||||
};
|
||||
|
||||
itemsProperty().clear();
|
||||
analyzer.get(FORGE).ifPresent(library -> itemsProperty().add(
|
||||
new InstallerItem("Forge", library.getVersion(), () -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "forge", library));
|
||||
}, removeAction.apply(library))));
|
||||
analyzer.get(LITELOADER).ifPresent(library -> itemsProperty().add(
|
||||
new InstallerItem("LiteLoader", library.getVersion(), () -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "liteloader", library));
|
||||
}, removeAction.apply(library))));
|
||||
analyzer.get(OPTIFINE).ifPresent(library -> itemsProperty().add(
|
||||
new InstallerItem("OptiFine", library.getVersion(), () -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, "optifine", library));
|
||||
}, removeAction.apply(library))));
|
||||
analyzer.get(FABRIC).ifPresent(library -> itemsProperty().add(new InstallerItem("Fabric", library.getVersion(), null, null)));
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
String libraryId = mark.getLibraryId();
|
||||
String libraryVersion = mark.getLibraryVersion();
|
||||
String title = I18n.hasKey("install.installer." + libraryId) ? i18n("install.installer." + libraryId) : libraryId;
|
||||
Consumer<InstallerItem> action = "game".equals(libraryId) ? null : removeAction.apply(libraryId);
|
||||
if (libraryVersion != null && Lang.test(() -> profile.getDependency().getVersionList(libraryId)))
|
||||
itemsProperty().add(
|
||||
new InstallerItem(title, libraryVersion, () -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
|
||||
}, action));
|
||||
else
|
||||
itemsProperty().add(new InstallerItem(title, libraryVersion, null, action));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
}
|
||||
|
||||
private void doInstallOffline(File file) {
|
||||
Task task = profile.getDependency().installLibraryAsync(version, file.toPath())
|
||||
.then(profile.getRepository().refreshVersionsAsync());
|
||||
Task<?> task = profile.getDependency().installLibraryAsync(version, file.toPath())
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync());
|
||||
task.setName(i18n("install.installer.install_offline"));
|
||||
TaskExecutor executor = task.executor(new TaskListener() {
|
||||
@Override
|
||||
@@ -129,9 +129,9 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
loadVersion(profile, versionId);
|
||||
Controllers.dialog(i18n("install.success"));
|
||||
} else {
|
||||
if (executor.getLastException() == null)
|
||||
if (executor.getException() == null)
|
||||
return;
|
||||
InstallerWizardProvider.alertFailureMessage(executor.getLastException(), null);
|
||||
InstallerWizardProvider.alertFailureMessage(executor.getException(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,22 +79,22 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
}
|
||||
|
||||
public void loadVersion(Profile profile, String id) {
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedVersion(id));
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||
modded.set(libraryAnalyzer.hasModLoader());
|
||||
loadMods(profile.getRepository().getModManager(id));
|
||||
}
|
||||
|
||||
private void loadMods(ModManager modManager) {
|
||||
this.modManager = modManager;
|
||||
Task.ofResult(() -> {
|
||||
Task.supplyAsync(() -> {
|
||||
synchronized (ModListPage.this) {
|
||||
runInFX(() -> loadingProperty().set(true));
|
||||
modManager.refreshMods();
|
||||
return new LinkedList<>(modManager.getMods());
|
||||
}
|
||||
}).whenComplete(Schedulers.javafx(), (list, isDependentSucceeded, exception) -> {
|
||||
}).whenComplete(Schedulers.javafx(), (list, exception) -> {
|
||||
loadingProperty().set(false);
|
||||
if (isDependentSucceeded)
|
||||
if (exception == null)
|
||||
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
|
||||
if (newValue != null && newValue.getUserData() == ModListPage.this)
|
||||
itemsProperty().setAll(list.stream().map(ModListPageSkin.ModInfoObject::new).collect(Collectors.toList()));
|
||||
@@ -112,7 +112,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
List<String> succeeded = new LinkedList<>();
|
||||
List<String> failed = new LinkedList<>();
|
||||
if (res == null) return;
|
||||
Task.of(() -> {
|
||||
Task.runAsync(() -> {
|
||||
for (File file : res) {
|
||||
try {
|
||||
modManager.addMod(file);
|
||||
@@ -124,7 +124,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
// Actually addMod will not throw exceptions because FileChooser has already filtered files.
|
||||
}
|
||||
}
|
||||
}).with(Task.of(Schedulers.javafx(), () -> {
|
||||
}).withRunAsync(Schedulers.javafx(), () -> {
|
||||
List<String> prompt = new LinkedList<>();
|
||||
if (!succeeded.isEmpty())
|
||||
prompt.add(i18n("mods.add.success", String.join(", ", succeeded)));
|
||||
@@ -132,7 +132,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
prompt.add(i18n("mods.add.failed", String.join(", ", failed)));
|
||||
Controllers.dialog(String.join("\n", prompt), i18n("mods.add"));
|
||||
loadMods(modManager);
|
||||
})).start();
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void setParentTab(JFXTabPane parentTab) {
|
||||
@@ -142,6 +142,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
public void removeSelected(ObservableList<TreeItem<ModListPageSkin.ModInfoObject>> selectedItems) {
|
||||
try {
|
||||
modManager.removeMods(selectedItems.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(TreeItem::getValue)
|
||||
.filter(Objects::nonNull)
|
||||
.map(ModListPageSkin.ModInfoObject::getModInfo)
|
||||
|
||||
@@ -115,7 +115,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
Task.ofResult(JavaVersion::getJavas).thenAccept(Schedulers.javafx(), list -> {
|
||||
Task.supplyAsync(JavaVersion::getJavas).thenAcceptAsync(Schedulers.javafx(), list -> {
|
||||
javaItem.loadChildren(list.stream()
|
||||
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion() + i18n("settings.game.java_directory.bit",
|
||||
javaVersion.getPlatform().getBit()), javaVersion.getBinary().toString(), javaVersion))
|
||||
@@ -270,8 +270,8 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
VersionSetting versionSetting = lastVersionSetting;
|
||||
if (versionSetting == null)
|
||||
return;
|
||||
Task.ofResult(versionSetting::getJavaVersion)
|
||||
.thenAccept(Schedulers.javafx(), javaVersion -> javaItem.setSubtitle(Optional.ofNullable(javaVersion)
|
||||
Task.supplyAsync(versionSetting::getJavaVersion)
|
||||
.thenAcceptAsync(Schedulers.javafx(), javaVersion -> javaItem.setSubtitle(Optional.ofNullable(javaVersion)
|
||||
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Path")))
|
||||
.start();
|
||||
}
|
||||
|
||||
@@ -19,10 +19,8 @@ package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -78,8 +76,7 @@ public class Versions {
|
||||
}
|
||||
|
||||
public static void updateGameAssets(Profile profile, String version) {
|
||||
Version resolvedVersion = profile.getRepository().getResolvedVersion(version);
|
||||
TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), resolvedVersion, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY)
|
||||
TaskExecutor executor = new GameAssetDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version), GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY)
|
||||
.executor();
|
||||
Controllers.taskDialog(executor, i18n("version.manage.redownload_assets_index"));
|
||||
executor.start();
|
||||
|
||||
@@ -74,6 +74,6 @@ public class WorldExportPage extends WizardSinglePage {
|
||||
|
||||
@Override
|
||||
protected Object finish() {
|
||||
return Task.of(i18n("world.export.wizard", worldName.get()), () -> world.export(Paths.get(path.get()), worldName.get()));
|
||||
return Task.runAsync(i18n("world.export.wizard", worldName.get()), () -> world.export(Paths.get(path.get()), worldName.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -82,12 +84,12 @@ public class WorldListPage extends ListPageBase<WorldListItem> {
|
||||
|
||||
setLoading(true);
|
||||
Task
|
||||
.of(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null))
|
||||
.thenSupply(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
|
||||
.whenComplete(Schedulers.javafx(), (result, isDependentSucceeded, exception) -> {
|
||||
.runAsync(() -> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(id)).orElse(null))
|
||||
.thenSupplyAsync(() -> World.getWorlds(savesDir).parallel().collect(Collectors.toList()))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
worlds = result;
|
||||
setLoading(false);
|
||||
if (isDependentSucceeded)
|
||||
if (exception == null)
|
||||
itemsProperty().setAll(result.stream()
|
||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(WorldListItem::new).collect(Collectors.toList()));
|
||||
@@ -107,16 +109,18 @@ public class WorldListPage extends ListPageBase<WorldListItem> {
|
||||
private void installWorld(File zipFile) {
|
||||
// Only accept one world file because user is required to confirm the new world name
|
||||
// Or too many input dialogs are popped.
|
||||
Task.ofResult(() -> new World(zipFile.toPath()))
|
||||
Task.supplyAsync(() -> new World(zipFile.toPath()))
|
||||
.whenComplete(Schedulers.javafx(), world -> {
|
||||
Controllers.inputDialog(i18n("world.name.enter"), (name, resolve, reject) -> {
|
||||
Task.of(() -> world.install(savesDir, name))
|
||||
Task.runAsync(() -> world.install(savesDir, name))
|
||||
.whenComplete(Schedulers.javafx(), () -> {
|
||||
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name))));
|
||||
resolve.run();
|
||||
}, e -> {
|
||||
if (e instanceof FileAlreadyExistsException)
|
||||
reject.accept(i18n("world.import.failed", i18n("world.import.already_exists")));
|
||||
else if (e instanceof IOException && e.getCause() instanceof InvalidPathException)
|
||||
reject.accept(i18n("world.import.failed", i18n("install.new_game.malformed")));
|
||||
else
|
||||
reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage()));
|
||||
}).start();
|
||||
|
||||
@@ -32,8 +32,8 @@ public interface AbstractWizardDisplayer extends WizardDisplayer {
|
||||
Queue<Object> getCancelQueue();
|
||||
|
||||
@Override
|
||||
default void handleTask(Map<String, Object> settings, Task task) {
|
||||
TaskExecutor executor = task.with(Task.of(Schedulers.javafx(), this::navigateToSuccess)).executor();
|
||||
default void handleTask(Map<String, Object> settings, Task<?> task) {
|
||||
TaskExecutor executor = task.withRunAsync(Schedulers.javafx(), this::navigateToSuccess).executor();
|
||||
TaskListPane pane = new TaskListPane();
|
||||
pane.setExecutor(executor);
|
||||
navigateTo(pane, Navigation.NavigationDirection.FINISH);
|
||||
|
||||
@@ -23,7 +23,6 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
@@ -35,7 +35,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplayer {
|
||||
|
||||
@Override
|
||||
default void handleTask(Map<String, Object> settings, Task task) {
|
||||
default void handleTask(Map<String, Object> settings, Task<?> task) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
onEnd();
|
||||
@@ -70,11 +70,11 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
Controllers.dialog(i18n("message.success"), null, MessageType.FINE, () -> onEnd());
|
||||
} else {
|
||||
if (executor.getLastException() == null)
|
||||
if (executor.getException() == null)
|
||||
return;
|
||||
String appendix = StringUtils.getStackTrace(executor.getLastException());
|
||||
String appendix = StringUtils.getStackTrace(executor.getException());
|
||||
if (settings.get("failure_callback") instanceof WizardProvider.FailureCallback)
|
||||
((WizardProvider.FailureCallback)settings.get("failure_callback")).onFail(settings, executor.getLastException(), () -> onEnd());
|
||||
((WizardProvider.FailureCallback)settings.get("failure_callback")).onFail(settings, executor.getException(), () -> onEnd());
|
||||
else if (settings.get("failure_message") instanceof String)
|
||||
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageType.ERROR, () -> onEnd());
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
|
||||
@@ -111,7 +111,7 @@ public class WizardController implements Navigation {
|
||||
public void onFinish() {
|
||||
Object result = provider.finish(settings);
|
||||
if (result instanceof Summary) displayer.navigateTo(((Summary) result).getComponent(), NavigationDirection.NEXT);
|
||||
else if (result instanceof Task) displayer.handleTask(settings, ((Task) result));
|
||||
else if (result instanceof Task<?>) displayer.handleTask(settings, ((Task<?>) result));
|
||||
else if (result != null) throw new IllegalStateException("Unrecognized wizard result: " + result);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,5 +27,5 @@ public interface WizardDisplayer {
|
||||
void onEnd();
|
||||
void onCancel();
|
||||
void navigateTo(Node page, Navigation.NavigationDirection nav);
|
||||
void handleTask(Map<String, Object> settings, Task task);
|
||||
void handleTask(Map<String, Object> settings, Task<?> task);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.upgrade;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -38,10 +38,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -101,7 +98,7 @@ public final class UpdateHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
Task task = new HMCLDownloadTask(version, downloaded);
|
||||
Task<?> task = new HMCLDownloadTask(version, downloaded);
|
||||
|
||||
TaskExecutor executor = task.executor();
|
||||
Controllers.taskDialog(executor, i18n("message.downloading"));
|
||||
@@ -122,7 +119,7 @@ public final class UpdateHandler {
|
||||
}
|
||||
|
||||
} else {
|
||||
Throwable e = executor.getLastException();
|
||||
Exception e = executor.getException();
|
||||
LOG.log(Level.WARNING, "Failed to update to " + version, e);
|
||||
Platform.runLater(() -> Controllers.dialog(e.toString(), i18n("update.failed"), MessageType.ERROR));
|
||||
}
|
||||
@@ -161,9 +158,7 @@ public final class UpdateHandler {
|
||||
commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
|
||||
commandline.add("-jar");
|
||||
commandline.add(jar.toAbsolutePath().toString());
|
||||
for (String arg : appArgs) {
|
||||
commandline.add(arg);
|
||||
}
|
||||
commandline.addAll(Arrays.asList(appArgs));
|
||||
LOG.info("Starting process: " + commandline);
|
||||
new ProcessBuilder(commandline)
|
||||
.directory(Paths.get("").toAbsolutePath().toFile())
|
||||
@@ -206,11 +201,7 @@ public final class UpdateHandler {
|
||||
StackTraceElement element = stacktrace[i];
|
||||
if (Main.class.getName().equals(element.getClassName())) {
|
||||
// we've reached the main method
|
||||
if (i + 1 == stacktrace.length) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return i + 1 != stacktrace.length;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -55,6 +55,7 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
put("java.lang.VerifyError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchMethodError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchFieldError", i18n("crash.NoClassDefFound"));
|
||||
put("javax.imageio.IIOException", i18n("crash.NoClassDefFound"));
|
||||
put("netscape.javascript.JSException", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.IncompatibleClassChangeError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.ClassFormatError", i18n("crash.NoClassDefFound"));
|
||||
@@ -86,6 +87,12 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private static Set<String> CAUGHT_EXCEPTIONS = newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
private final boolean showCrashWindow;
|
||||
|
||||
public CrashReporter(boolean showCrashWindow) {
|
||||
this.showCrashWindow = showCrashWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
LOG.log(Level.SEVERE, "Uncaught exception in thread " + t.getName(), e);
|
||||
@@ -116,7 +123,9 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
LOG.log(Level.SEVERE, text);
|
||||
|
||||
if (checkThrowable(e)) {
|
||||
Platform.runLater(() -> new CrashWindow(text).show());
|
||||
if (showCrashWindow) {
|
||||
Platform.runLater(() -> new CrashWindow(text).show());
|
||||
}
|
||||
if (!UpdateChecker.isOutdated() && IntegrityChecker.isSelfVerified()) {
|
||||
reportToServer(text);
|
||||
}
|
||||
|
||||
@@ -47,16 +47,16 @@ public final class Locales {
|
||||
public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE);
|
||||
|
||||
/**
|
||||
* Vietnamese
|
||||
* Spanish
|
||||
*/
|
||||
public static final SupportedLocale VI = new SupportedLocale(new Locale("vi"));
|
||||
public static final SupportedLocale ES = new SupportedLocale(new Locale("es"));
|
||||
|
||||
/**
|
||||
* Russian
|
||||
*/
|
||||
public static final SupportedLocale RU = new SupportedLocale(new Locale("ru"));
|
||||
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH);
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH, ES, RU);
|
||||
|
||||
public static SupportedLocale getLocaleByName(String name) {
|
||||
if (name == null) return DEFAULT;
|
||||
@@ -64,7 +64,7 @@ public final class Locales {
|
||||
case "en": return EN;
|
||||
case "zh": return ZH;
|
||||
case "zh_cn": return ZH_CN;
|
||||
case "vi": return VI;
|
||||
case "es": return ES;
|
||||
case "ru": return RU;
|
||||
default: return DEFAULT;
|
||||
}
|
||||
@@ -74,7 +74,7 @@ public final class Locales {
|
||||
if (locale == EN) return "en";
|
||||
else if (locale == ZH) return "zh";
|
||||
else if (locale == ZH_CN) return "zh_CN";
|
||||
else if (locale == VI) return "vi";
|
||||
else if (locale == ES) return "es";
|
||||
else if (locale == RU) return "ru";
|
||||
else if (locale == DEFAULT) return "def";
|
||||
else throw new IllegalArgumentException("Unknown locale: " + locale);
|
||||
|
||||
@@ -526,6 +526,12 @@
|
||||
-fx-background-color: -fx-base-color;
|
||||
}
|
||||
|
||||
.jfx-button-raised .jfx-rippler {
|
||||
-jfx-rippler-fill: white;
|
||||
-jfx-mask-type: CIRCLE;
|
||||
-fx-padding: 0.0;
|
||||
}
|
||||
|
||||
.jfx-button-raised, .jfx-button-raised * {
|
||||
-fx-text-fill: -fx-base-text-fill;
|
||||
-fx-font-size: 14px;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import com.jfoenix.controls.*?>
|
||||
|
||||
@@ -15,6 +15,18 @@
|
||||
</top>
|
||||
<center>
|
||||
<VBox fx:id="list" styleClass="jfx-list-view" maxHeight="150" maxWidth="300">
|
||||
<JFXButton fx:id="btnFabric" prefWidth="${list.width}">
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<Label fx:id="lblFabric"/>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnForge" prefWidth="${list.width}">
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
|
||||
@@ -15,6 +15,18 @@
|
||||
</top>
|
||||
<center>
|
||||
<VBox fx:id="list" styleClass="jfx-list-view" maxHeight="150" maxWidth="300">
|
||||
<JFXButton fx:id="btnFabric" prefWidth="${list.width}">
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<Label fx:id="lblFabric"/>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnForge" prefWidth="${list.width}">
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<bottom>
|
||||
<HBox alignment="CENTER_RIGHT" style="-fx-padding: 16 16 16 0;">
|
||||
<JFXButton fx:id="btnNext" onMouseClicked="#onNext" prefWidth="100" prefHeight="40" buttonType="RAISED"
|
||||
text="%wizard.finish" styleClass="jfx-button-raised"/>
|
||||
text="%wizard.next" styleClass="jfx-button-raised"/>
|
||||
</HBox>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
|
||||
41
HMCL/src/main/resources/assets/fxml/modpack/type.fxml
Normal file
41
HMCL/src/main/resources/assets/fxml/modpack/type.fxml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.TwoLineListItem?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<fx:define>
|
||||
<Insets fx:id="insets" topRightBottomLeft="8" />
|
||||
</fx:define>
|
||||
<VBox fx:id="list" styleClass="jfx-list-view" maxHeight="150" maxWidth="300">
|
||||
<Label padding="${insets}" text="%modpack.export.as" />
|
||||
<JFXButton fx:id="btnHMCL" prefWidth="${list.width}">
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<TwoLineListItem title="%modpack.type.hmcl" subtitle="%modpack.type.hmcl.export" />
|
||||
</left>
|
||||
<right>
|
||||
<fx:include BorderPane.alignment="CENTER" source="/assets/svg/arrow-right.fxml"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<JFXButton fx:id="btnMultiMC" prefWidth="${list.width}">
|
||||
<graphic>
|
||||
<BorderPane mouseTransparent="true">
|
||||
<left>
|
||||
<TwoLineListItem title="%modpack.type.multimc" subtitle="%modpack.type.multimc.export" />
|
||||
</left>
|
||||
<right>
|
||||
<fx:include BorderPane.alignment="CENTER" source="/assets/svg/arrow-right.fxml"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</VBox>
|
||||
</fx:root>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<?import javafx.collections.FXCollections?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
|
||||
@@ -37,19 +37,19 @@ account.choose=Choose a character
|
||||
account.create=Create a new account
|
||||
account.email=Email
|
||||
account.failed.character_deleted=The character has been deleted.
|
||||
account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your network.
|
||||
account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your internet connection.
|
||||
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
|
||||
account.failed.injector_download_failure=Failed to download authlib-injector. Check your network and try switching to another download source.
|
||||
account.failed.injector_download_failure=Failed to download authlib-injector. Check your network and try switching to a different download source.
|
||||
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 login.
|
||||
account.failed.invalid_token=Please log out and re-enter your password to login.
|
||||
account.failed.no_character=No character in this account.
|
||||
account.failed.server_response_malformed=Invalid server response. The authentication server may have a failure.
|
||||
account.failed.server_response_malformed=Invalid server response. The authentication server may have an error.
|
||||
account.injector.add=Add an authentication server
|
||||
account.injector.empty=Empty (Click the plus button right to add)
|
||||
account.injector.empty=Empty (Click the plus button on the right to add)
|
||||
account.injector.manage=Manage authentication servers
|
||||
account.injector.manage.title=Authentication servers
|
||||
account.injector.http=Warning: This server is using HTTP, which will cause your password be transmitted in clear text.
|
||||
account.injector.http=Warning: This server uses HTTP so your password will be transmitted in clear text.
|
||||
account.injector.link.register=Register
|
||||
account.injector.server=Auth Server
|
||||
account.injector.server_url=Server URL
|
||||
@@ -66,11 +66,11 @@ account.username=Name
|
||||
|
||||
archive.author=Authors
|
||||
archive.game_version=Game
|
||||
archive.name=Name
|
||||
archive.name=Nickname
|
||||
archive.version=Version
|
||||
|
||||
assets.download=Download assets
|
||||
assets.download_all=Check the integrity of assets
|
||||
assets.download=Downloading assets
|
||||
assets.download_all=Asset Integrity Check
|
||||
|
||||
button.cancel=Cancel
|
||||
button.clear=Clear
|
||||
@@ -87,16 +87,16 @@ button.yes=Yes
|
||||
color.recent=Recommended
|
||||
color.custom=Custom Color
|
||||
|
||||
crash.NoClassDefFound=Please check "HMCL" software is complete.
|
||||
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!
|
||||
crash.NoClassDefFound=Please verify that the "Hello Minecraft! Launcher" software is not corrupted.
|
||||
crash.user_fault=Your OS or Java environment may not be properly installed which may result in a crash, please check your Java Runtime Environment or your computer!
|
||||
|
||||
download=Download
|
||||
download.code.404=File does not found in remote server
|
||||
download.code.404=File not found on the remote server
|
||||
download.failed=Failed to download
|
||||
download.failed.empty=No candidates. Click here to return.
|
||||
download.failed.refresh=Unable to load version list. Click here to retry.
|
||||
download.failed.refresh=Unable to download version list. Click here to retry.
|
||||
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
download.provider.mojang=Mojang (Forge and OptiFine installation being downloaded from BMCLAPI)
|
||||
download.provider.mojang=Mojang (Forge and OptiFine installation are downloaded from BMCLAPI)
|
||||
|
||||
extension.bat=Windows Bat file
|
||||
extension.mod=Mod file
|
||||
@@ -104,10 +104,10 @@ extension.png=Image file
|
||||
extension.sh=Bash shell
|
||||
|
||||
fatal.missing_javafx=JavaFX is missing.\nIf you are using Java 11 or later, please downgrade to Java 8 or 10.\nIf you are using OpenJDK, please ensure OpenJFX is included.
|
||||
fatal.missing_dst_root_ca_x3=The DST Root CA X3 certificate is missing on the current Java platform.\nYou can still use HMCL, but HMCL will be unable to connect to some sites (such as sites that use certificates issued by Let's Encrypt), which may cause HMCL not to function properly.\nPlease upgrade your Java to 8u101 or later to resolve the problem.
|
||||
fatal.config_loading_failure=The configuration is not accessible.\nPlease ensure HMCL has read and write access to "%s" and the files in it.
|
||||
fatal.migration_requires_manual_reboot=The upgrade is about to be completed. Please reopen HMCL.
|
||||
fatal.apply_update_failure=We're sorry that HMCL couldn't finish the upgrade because something went wrong.\nBut you can still manually finish the upgrade by downloading HMCL from %s.\nPlease consider reporting this issue to us.
|
||||
fatal.missing_dst_root_ca_x3=The DST Root CA X3 certificate is missing on the current Java platform.\nYou can still use Hello Minecraft! Launcher, but it will be unable to connect to some sites (such as sites that use certificates issued by Let's Encrypt), which may cause the launcher not to function properly.\nPlease upgrade your Java Runtime to 8u101 or later to resolve the problem.
|
||||
fatal.config_loading_failure=The configuration is not accessible.\nPlease ensure Hello Minecraft! Launcher has read and write access to "%s" and the files in it.
|
||||
fatal.migration_requires_manual_reboot=The update is complete. Please reopen Hello Minecraft! Launcher.
|
||||
fatal.apply_update_failure=We're sorry, Hello Minecraft! Launcher couldn't finish the upgrade because something went wrong.\nBut you can still manually finish the upgrade by downloading Hello Minecraft! Launcher from %s.\nPlease consider reporting this issue to us.
|
||||
|
||||
folder.config=Configs
|
||||
folder.game=Game Directory
|
||||
@@ -117,82 +117,84 @@ folder.saves=Saves
|
||||
folder.screenshots=Screenshots
|
||||
|
||||
help=Documentations
|
||||
help.detail=For datapacks, modpacks, etc. makers.
|
||||
help.detail=For manufacturers of datapacks, modpacks, etc.
|
||||
|
||||
input.email=The username must be an e-mail.
|
||||
input.email=Username must be an email.
|
||||
input.number=Must be a number.
|
||||
input.not_empty=Required field
|
||||
input.url=Must be a valid URL.
|
||||
|
||||
install=Install New Game
|
||||
install.change_version=Change version
|
||||
install.change_version.confirm=Sure to update %s from verison %s to %s?
|
||||
install.failed=Failed to install
|
||||
install.change_version.confirm=Are you sure you want to upgrade %s from verison %s to %s?
|
||||
install.failed=Version failed to install
|
||||
install.failed.downloading=Failed to install due to some files not downloaded successfully
|
||||
install.failed.downloading.detail=Failed to download file: %s
|
||||
install.failed.downloading.timeout=Download timed out: %s
|
||||
install.failed.install_online=Unable to recognize what you provided installer file is
|
||||
install.failed.optifine_conflict=OptiFine and Forge are both installed simultaneously on Minecraft 1.13
|
||||
install.failed.version_mismatch=The library requires game version %s, but actual version is %s.
|
||||
install.failed.downloading.timeout=Timed out while downloading the file: %s
|
||||
install.failed.install_online=Unable to recognize the provided installer file
|
||||
install.failed.optifine_conflict=Fabric, OptiFine and Forge are installed simultaneously on Minecraft 1.13
|
||||
install.failed.version_mismatch=The library requires the game version %s, but the actual version is %s.
|
||||
install.installer.choose=Choose a %s version
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Game
|
||||
install.installer.install=Install %s
|
||||
install.installer.install_offline=Install/Upgrade from local file
|
||||
install.installer.install_offline=Install/Upgrade from file
|
||||
install.installer.install_offline.extension=Forge/OptiFine installer
|
||||
install.installer.install_offline.tooltip=Support importing Forge/OptiFine installer jar file
|
||||
install.installer.install_offline.tooltip=Supports import of Forge/OptiFine installation files
|
||||
install.installer.install_online=Install Online
|
||||
install.installer.install_online.tooltip=Support Forge, OptiFine, LiteLoader installation.
|
||||
install.installer.install_online.tooltip=Support installation of Fabric, Forge, OptiFine, LiteLoader.
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=%s not Installed
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s Version
|
||||
install.modpack=Install a modpack
|
||||
install.new_game=Install a New Game
|
||||
install.new_game.already_exists=This version has already been existing.
|
||||
install.new_game.already_exists=This version already exists.
|
||||
install.new_game.current_game_version=Current Game Version
|
||||
install.new_game.malformed=Invalid name
|
||||
install.select=Select an operation
|
||||
install.success=Installed successfully
|
||||
install.success=successfully installed
|
||||
|
||||
lang=English
|
||||
lang.default=Use system language
|
||||
|
||||
launch.advice.corrected=We have already corrected JVM selection and game can be launched now. If you do want to keep your JVM choice, you can turn off the Java VM checker in game settings.
|
||||
launch.advice.uncorrected=If you are sure that game can be launched normally, you can turn off the Java VM checker in game settings.
|
||||
launch.advice.corrected=We have already fixed the JVM selection. If you want to keep your choice of Java version, you can disable the Java VM check in game settings.
|
||||
launch.advice.uncorrected=If you are sure that the game can be started normally, you can disable the Java VM check in game settings.
|
||||
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
|
||||
launch.advice.forge2760_liteloader=Forge 2760 and above is not compatible with LiteLoader, please consider upgrade Forge to 2773 or later.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 earlier than 1.8.0_51. You are supposed to install the latest version of Java 8.
|
||||
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.advice.forge2760_liteloader=Forge 2760 and higher are not compatible with LiteLoader, please consider upgrading Forge to 2773 or later.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later can only run on Java 8 or later.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 earlier than 1.8.0_51. Please install the latest version of Java 8.
|
||||
launch.advice.java9=You cannot launch Minecraft 1.12 oer earlier with Java 9 or later versions of Java.
|
||||
launch.advice.newer_java=Java 8 is recommended to make the game faster. For many Minecraft 1.12 of higher, and most mods, Java 8 is required.
|
||||
launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game may crash. The launcher attempt to launch.
|
||||
launch.advice.too_large_memory_for_32bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game may crash. The maximum memory capacity for 32 bit systems is is 1024MB. The launcher will attempt to launch.
|
||||
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.cannot_create_jvm=Java virtual machine could not be created. Java arguments may cause issues. Please restart without JVM arguments.
|
||||
launch.failed.creating_process=Failed to create process. Check your Java path.
|
||||
launch.failed.decompressing_natives=Unable to decompress native libraries.
|
||||
launch.failed.download_library=Unable to download library %s.
|
||||
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.exited_abnormally=Game exited abnormally, please check the log, or ask someone for help.
|
||||
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.wrong_javadir=Incorrect Java directory, will reset to default Java directory.
|
||||
launch.state.modpack=Loading modpack
|
||||
launch.state.waiting_launching=Launcing modpack
|
||||
launch.wrong_javadir=Invalid Java directory, default Java path will be applied.
|
||||
|
||||
launcher=Launcher
|
||||
launcher.background=Background Image
|
||||
launcher.background.choose=Choose background path.
|
||||
launcher.background.default=Default
|
||||
launcher.cache_directory=Download Cache Directory
|
||||
launcher.cache_directory.choose=Choose download cache directory
|
||||
launcher.cache_directory.default=Default
|
||||
launcher.background.choose=Choose a background image file
|
||||
launcher.background.default=Standard
|
||||
launcher.cache_directory=Directory for caching
|
||||
launcher.cache_directory.choose=Choose the directory for caching
|
||||
launcher.cache_directory.default=Standard
|
||||
launcher.cache_directory.disabled=Disabled
|
||||
launcher.cache_directory.invalid=Invalid cache directory. Restore default settings.
|
||||
launcher.cache_directory.invalid=Invalid directory. Restoring default settings.
|
||||
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.crash_out_dated=Hello Minecraft! Launcher has crashed! Your launcher is outdated. Please updater your launcher!
|
||||
launcher.update_java=Please upgrade your Java.
|
||||
|
||||
login.empty_username=You have not set a username!
|
||||
@@ -209,15 +211,16 @@ message.doing=Please wait
|
||||
message.downloading=Downloading...
|
||||
message.error=Error
|
||||
message.info=Info
|
||||
message.success=Tasks succeeded
|
||||
message.success=Job completed successfully
|
||||
message.unknown=Unknown
|
||||
message.warning=Warning
|
||||
|
||||
modpack=Mod pack
|
||||
modpack.choose=Choose a modpack zip file which you want to install.
|
||||
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=Modpack
|
||||
modpack.choose=Choose a modpack zip.
|
||||
modpack.desc=Describe your modpack, including precautions and changelog. Markdown and online pictures are supported.
|
||||
modpack.enter_name=Enter a name for this modpack.
|
||||
modpack.export=Export Modpack
|
||||
modpack.export.as=Export Modpack As...
|
||||
modpack.files.blueprints=BuildCraft blueprints
|
||||
modpack.files.config=Mod configs
|
||||
modpack.files.dumps=NEI debug output
|
||||
@@ -226,55 +229,58 @@ modpack.files.mods=Mods
|
||||
modpack.files.mods.voxelmods=VoxelMods (including VoxelMap) options
|
||||
modpack.files.options_txt=Game options
|
||||
modpack.files.optionsshaders_txt=Shaders options
|
||||
modpack.files.resourcepacks=Resource(Texture) packs
|
||||
modpack.files.resourcepacks=Resource/Texture packs
|
||||
modpack.files.saves=Saved games
|
||||
modpack.files.scripts=MineTweaker configuration
|
||||
modpack.files.servers_dat=Multiplayer servers list
|
||||
modpack.files.servers_dat=Server list
|
||||
modpack.install=Install %s modpack
|
||||
modpack.installing=Installing modpack
|
||||
modpack.introduction=Curse, MultiMC, HMCL modpacks supprted.
|
||||
modpack.introduction=Curse, MultiMC, HMCL modpacks are 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.mismatched_type=Inappropriate modpack type, your current game is a %s modpack, but your update file is a %s modpack.
|
||||
modpack.name=Modpack Name
|
||||
modpack.not_a_valid_name=Not a valid modpack name
|
||||
modpack.not_a_valid_name=Invalid modpack name
|
||||
modpack.scan=Scanning this modpack
|
||||
modpack.task.install=Import Modpack
|
||||
modpack.task.install.error=This modpack file cannot be recognized. Only Curse, MultiMC modpacks are supported.
|
||||
modpack.task.install.error=This modpack file cannot be recognized. Only Curse and MultiMC modpacks are supported.
|
||||
modpack.task.install.will=Install the modpack:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Install relative files to Curse modpack
|
||||
modpack.type.curse.tolerable_error=But we cannot complete downloading all files of this Curse modpack. You can retry downloading when launching corresponding game version. You may retry for a couple of times due to network problems.
|
||||
modpack.type.curse.error=Unable to complete this Curse modpack. Please retry.
|
||||
modpack.type.curse.not_found=Some of required resources are deleted and cannot be downloaded. Please consider the latest version or other modpacks.
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.curse.completion=Install files related to Curse modpack
|
||||
modpack.type.curse.tolerable_error=We cannot complete the download of all files of this Curse modpack. You can retry the download when starting corresponding game version. You may retry for a couple of times due to network problems.
|
||||
modpack.type.curse.error=Unable to install this Curse modpack. Please retry.
|
||||
modpack.type.curse.not_found=Some of required resources are missing and thus could not be downloaded. Please consider the latest version or other modpacks.
|
||||
modpack.type.hmcl=Hello Minecraft! Launcher
|
||||
modpack.type.hmcl.export=Can be imported by Hello Minecraft! Launcher
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, Curse modpacks are supported.
|
||||
modpack.update=Upgrading game
|
||||
modpack.wizard=Exporting the modpack wizard
|
||||
modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC
|
||||
modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, and Curse modpacks are supported.
|
||||
modpack.update=Game Update
|
||||
modpack.wizard=Exporting modpack wizard
|
||||
modpack.wizard.step.1=Basic options
|
||||
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.initialization.exported_version=The exported game version
|
||||
modpack.wizard.step.1.title=Set the basic options for the modpack.
|
||||
modpack.wizard.step.2=Select files
|
||||
modpack.wizard.step.2.title=Add files to the modpack.
|
||||
modpack.wizard.step.3=Modpack Type
|
||||
modpack.wizard.step.3.title=Choose the format of the modpack.
|
||||
modpack.wizard.step.initialization.exported_version=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
|
||||
modpack.wizard.step.initialization.warning=Before making modpack, you should ensure that your game can launch successfully,\nand that your Minecraft is release, not snapshot.\nand that it is not allowed to add mods which is not allowed to distribute to the modpack.
|
||||
modpack.wizard.step.initialization.save=Export to...
|
||||
modpack.wizard.step.initialization.warning=Before creating a modpack, you should ensure that the game can launch successfully,\nand that your Minecraft is a release version.\nDo NOT add mods which cannot be redistributed.
|
||||
|
||||
mods=Mods
|
||||
mods.add=Add mods
|
||||
mods.add.failed=Failed to add mods %s.
|
||||
mods.add.success=Successfully added mods %s.
|
||||
mods.add=Install mods
|
||||
mods.add.failed=Failed to install mods %s.
|
||||
mods.add.success=Successfully installed mods %s.
|
||||
mods.choose_mod=Choose your mods
|
||||
mods.enable=Enable
|
||||
mods.disable=Disable
|
||||
mods.name=Name
|
||||
mods.remove=Remove
|
||||
mods.not_modded=You should install a mod loader first (Forge, or LiteLoader)
|
||||
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
|
||||
|
||||
datapack=Data packs
|
||||
datapack.add=Add data pack
|
||||
datapack.choose_datapack=Choose the datapack zip to be imported
|
||||
datapack=Datapacks
|
||||
datapack.add=Install datapack
|
||||
datapack.choose_datapack=Choose the datapack zip to import
|
||||
datapack.extension=Datapack
|
||||
datapack.title=World %s - Datapacks
|
||||
datapack.remove=Remove
|
||||
@@ -285,15 +291,15 @@ world.datapack=Manage data packs
|
||||
world.datapack.1_13=Only Minecraft 1.13 and later versions support data packs.
|
||||
world.description=%s. Last played time: %s. Game version: %s.
|
||||
world.export=Export this world
|
||||
world.export.title=Choose a file location to hold your world
|
||||
world.export.title=Choose a location to store your world
|
||||
world.export.location=Export to
|
||||
world.export.wizard=Export world %s
|
||||
world.extension=World zip
|
||||
world.game_version=Game Version
|
||||
world.import.already_exists=This world already exists.
|
||||
world.import.choose=Choose the save zip to be imported
|
||||
world.import.choose=Choose the zip file to import
|
||||
world.import.failed=Unable to import this world: %s
|
||||
world.import.invalid=Not a valid world zip
|
||||
world.import.invalid=Invalid world zip file
|
||||
world.name=World Name
|
||||
world.name.enter=Enter the world name
|
||||
world.reveal=Reveal in Explorer
|
||||
@@ -301,9 +307,9 @@ world.show_all=Show all
|
||||
world.time=EEE, MMM d, yyyy HH:mm:ss
|
||||
|
||||
profile=Game Directories
|
||||
profile.already_exists=This name already exists, please consider another name.
|
||||
profile.already_exists=This name already exists, please use a different name.
|
||||
profile.default=Current directory
|
||||
profile.home=User home
|
||||
profile.home=Standard
|
||||
profile.instance_directory=Game Directory
|
||||
profile.instance_directory.choose=Choose Game Directory
|
||||
profile.manage=Game Directory List
|
||||
@@ -320,11 +326,11 @@ selector.custom=Custom
|
||||
settings=Game Settings
|
||||
|
||||
settings.advanced=Advanced Settings
|
||||
settings.advanced.dont_check_game_completeness=Don't check game completeness
|
||||
settings.advanced.dont_check_game_completeness=Do not scan game files
|
||||
settings.advanced.dont_check_jvm_validity=Don't check whether JVM can launch the game or not
|
||||
settings.advanced.game_dir.default=Default (.minecraft/)
|
||||
settings.advanced.game_dir.independent=Independent (.minecraft/versions/<version name>/, except assets,libraries)
|
||||
settings.advanced.java_args_default=Default java args: -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
|
||||
settings.advanced.game_dir.default=Standard (.minecraft/)
|
||||
settings.advanced.game_dir.independent=Independent (.minecraft/versions/<version name>/, except for assets and libraries)
|
||||
settings.advanced.java_args_default=Stadnard java args: -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
|
||||
settings.advanced.java_permanent_generation_space=PermGen Space/MB
|
||||
settings.advanced.jvm_args=Java VM Arguments
|
||||
settings.advanced.launcher_visibility.close=Close the launcher when the game launched.
|
||||
@@ -334,9 +340,9 @@ settings.advanced.launcher_visibility.keep=Keep the launcher visible.
|
||||
settings.advanced.launcher_visible=Launcher Visibility
|
||||
settings.advanced.minecraft_arguments=Minecraft Arguments
|
||||
settings.advanced.no_jvm_args=No Default JVM Args
|
||||
settings.advanced.precall_command=Pre-Launch command(will be executed before game launching)
|
||||
settings.advanced.server_ip=Server Host
|
||||
settings.advanced.wrapper_launcher=Wrapper Launcher(i.e. optirun...)
|
||||
settings.advanced.precall_command=Pre-Launch command (will be executed before game starts)
|
||||
settings.advanced.server_ip=Server Address
|
||||
settings.advanced.wrapper_launcher=Wrapper Launcher (i.e. optirun...)
|
||||
|
||||
settings.custom=Custom
|
||||
|
||||
@@ -354,9 +360,9 @@ settings.game.working_directory.choose=Choose Working Directory
|
||||
settings.icon=Game Icon
|
||||
|
||||
settings.launcher=Settings
|
||||
settings.launcher.common_path.tooltip=This app will save all game libraries and assets here unless there are existant files in game folder.
|
||||
settings.launcher.common_path.tooltip=This app will cache all downloads here.
|
||||
settings.launcher.download_source=Download Source
|
||||
settings.launcher.enable_game_list=Display game list in main page
|
||||
settings.launcher.enable_game_list=Show version list in main page
|
||||
settings.launcher.language=Language
|
||||
settings.launcher.launcher_log.export=Export launcher logs
|
||||
settings.launcher.launcher_log.export.failed=Failed to export logs
|
||||
@@ -379,18 +385,18 @@ settings.physical_memory=Physical Memory Size
|
||||
settings.show_log=Show Logs
|
||||
settings.tabs.installers=Installers
|
||||
settings.type=Version setting type
|
||||
settings.type.global=Global global settings(all shared)
|
||||
settings.type.global=Global game settings (all settings shared among games)
|
||||
settings.type.global.manage=Global Game Settings
|
||||
settings.type.global.edit=Configure global game settings
|
||||
settings.type.special.enable=Enable specialized settings for this game
|
||||
|
||||
sponsor.bmclapi=To obtain a stable download service, please consider support BMCLAPI. Click here for more information
|
||||
sponsor.hmcl=HMCL is a free, open source Minecraft launcher the enables users to easily manage multiple, separate installations of Minecraft. We're using Afdian so that we can continue to pay for our hosting and project development. Click here for more information.
|
||||
sponsor.bmclapi=To obtain a stable download service, please consider supporting BMCLAPI. Click here for more information
|
||||
sponsor.hmcl=Hello Minecraft! Launcher is a free and open source Minecraft launcher which allows users to easily manage multiple, individual Minecraft installations. We use Afdian to continue to pay for hosting and project development. Click here for more information.
|
||||
|
||||
update=Update
|
||||
update.accept=Update
|
||||
update.changelog=Changelog
|
||||
update.channel.dev=Update to development version
|
||||
update.changelog=Changes
|
||||
update.channel.dev=Update to beta version
|
||||
update.channel.stable=Update to stable version
|
||||
update.checking=Checking for updates
|
||||
update.failed=Failed to perform upgrade
|
||||
@@ -398,39 +404,39 @@ update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
update.bubble.title=Update Available: %s
|
||||
update.bubble.subtitle=Click here to upgrade
|
||||
update.note=Development version contains more functionality and bug fixes as well as more possible bugs.
|
||||
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.note=Warning: Beta versions may have more functionality and bug fixes but also more possible errors.
|
||||
update.latest=This is the latest version.
|
||||
update.no_browser=Cannot open a browser. The link has been copied to the clipboard. Paste it into the address bar of browser to update.
|
||||
update.tooltip=Update
|
||||
|
||||
version=Games
|
||||
version.cannot_read=Unable to gather the game version. Cannot continue auto-installing.
|
||||
version.cannot_read=Unable to find the game version. Cannot continue automatic installation.
|
||||
version.empty=No game
|
||||
version.empty.add=Install game in game list
|
||||
version.empty.launch=No game to launch, please install new game via game list page.
|
||||
version.forbidden_name=Forbidden name, do not use this.
|
||||
version.empty.add=Install new version
|
||||
version.empty.launch=No version to launch, please install a version via version list page.
|
||||
version.forbidden_name=Forbidden name please do not use that name.
|
||||
version.game.old=Old
|
||||
version.game.release=Release
|
||||
version.game.snapshot=Snapshot
|
||||
version.game.release=Releases
|
||||
version.game.snapshot=Snapshots
|
||||
version.launch=Play
|
||||
version.launch.test=Test Game
|
||||
version.launch_script=Make Launching Script
|
||||
version.launch_script=Make a Launching Script
|
||||
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=Game List
|
||||
version.manage.clean=Clean game directory
|
||||
version.manage.clean.tooltip=Clean logs, crash-reports
|
||||
version.manage.manage=Manage Game
|
||||
version.launch_script.save=Save launch script
|
||||
version.launch_script.success=Created script %s.
|
||||
version.manage=All Versions
|
||||
version.manage.clean=Clear game directory
|
||||
version.manage.clean.tooltip=Clear logs, crash-reports
|
||||
version.manage.manage=Manage Version
|
||||
version.manage.redownload_assets_index=Update Game Asset Files
|
||||
version.manage.remove=Delete this game
|
||||
version.manage.remove.confirm=Sure to remove game %s? You cannot restore this game again!
|
||||
version.manage.remove.confirm.trash=Sure to remove game %s? You can restore the game with the name of %s in your system trash.
|
||||
version.manage.remove.confirm.independent=Since this game is in independent mode, deleting this game will result in all saved worlds belonging to this game removed simultaneously. Sure to remove game %s?
|
||||
version.manage.remove=Delete this version
|
||||
version.manage.remove.confirm=Are you sure you want to remove this version %s? You cannot restore this version again!
|
||||
version.manage.remove.confirm.trash=Are you sure you want to remove this version %s? You can restore this version with the name of %s in your system trash.
|
||||
version.manage.remove.confirm.independent=Since this version is in independent mode, deleting this version will also delete all saved worlds belonging to this version. Do you want to remove this version %s?
|
||||
version.manage.remove_libraries=Delete library files
|
||||
version.manage.rename=Rename this game
|
||||
version.manage.rename.message=Please enter the new name
|
||||
version.manage.rename.fail=Failed to rename this game.
|
||||
version.manage.rename=Rename this version
|
||||
version.manage.rename.message=Please enter the new name for this version
|
||||
version.manage.rename.fail=Failed to rename the version.
|
||||
version.settings=Settings
|
||||
version.update=Update modpack
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Contributors: dxNeil
|
||||
# Contributors: gitfamhub
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright (c) 2019 huangyuhui.
|
||||
about.author=Autor
|
||||
@@ -87,7 +87,7 @@ button.yes=Sí
|
||||
color.recent=Recomendado
|
||||
color.custom=Color personalizado
|
||||
|
||||
crash.NoClassDefFound=Favor verificar que el software "HMCL" esté completo.
|
||||
crash.NoClassDefFound=Favor verificar que el software "Hello Minecraft! Launcher" esté completo.
|
||||
crash.user_fault=Su SO o ambiente de Java podría estar mal instalado resultando en fallos de este software, ¡por favor verifique su ambiente de Java o computadora!
|
||||
|
||||
download=Descargar
|
||||
@@ -104,10 +104,10 @@ extension.png=Archivo de imagen
|
||||
extension.sh=Bash shell
|
||||
|
||||
fatal.missing_javafx=JavaFX no existe.\nSi usas Java 11 o después, por favor descargue Java 8 o 10.\nSi usas OpenJDK, asegúrese que OpenJFX esté incluido.
|
||||
fatal.missing_dst_root_ca_x3=El certificado DST Root CA X3 no existe en su plataforma Java.\nTodavía podrás usar HMCL, pero HMCL no podrá conectarse a algunos sites (como los que usen certificados de Let's Encrypt), lo cual podrá causar mala función.\nActualize su Java a 8u101 o luego para resolver el problema.
|
||||
fatal.config_loading_failure=La configuración no es accesible.\nAsegúrese que HMCL tenga permisos read and write a "%s" y los archivos en ello.
|
||||
fatal.migration_requires_manual_reboot=La actualización está a punto de terminar. Favor reiniciar HMCL.
|
||||
fatal.apply_update_failure=Lamentamos que HMCL no pudo terminar la actualización por un error.\nPero todavía puedes terminarlo manualmente descargando HMCL por %s.\nConsidere informándonos sobre el error.
|
||||
fatal.missing_dst_root_ca_x3=El certificado DST Root CA X3 no existe en su plataforma Java.\nTodavía podrás usar Hello Minecraft! Launcher, pero Hello Minecraft! Launcher no podrá conectarse a algunos sites (como los que usen certificados de Let's Encrypt), lo cual podrá causar mala función.\nActualize su Java a 8u101 o luego para resolver el problema.
|
||||
fatal.config_loading_failure=La configuración no es accesible.\nAsegúrese que Hello Minecraft! Launcher tenga permisos read and write a "%s" y los archivos en ello.
|
||||
fatal.migration_requires_manual_reboot=La actualización está a punto de terminar. Favor reiniciar Hello Minecraft! Launcher.
|
||||
fatal.apply_update_failure=Lamentamos que Hello Minecraft! Launcher no pudo terminar la actualización por un error.\nPero todavía puedes terminarlo manualmente descargando Hello Minecraft! Launcher por %s.\nConsidere informándonos sobre el error.
|
||||
|
||||
folder.config=Configs
|
||||
folder.game=Directorio de juego
|
||||
@@ -276,7 +276,7 @@ mods.enable=Habilitar
|
||||
mods.disable=Deshabilitar
|
||||
mods.name=Nombre
|
||||
mods.remove=Borrar
|
||||
mods.not_modded=Debes instalar un modloader primero (Forge, or LiteLoader)
|
||||
mods.not_modded=Debes instalar un modloader primero (Fabric, Forge or LiteLoader)
|
||||
|
||||
datapack=Data packs
|
||||
datapack.add=Añadir data pack
|
||||
|
||||
446
HMCL/src/main/resources/assets/lang/I18N_ru.properties
Normal file
446
HMCL/src/main/resources/assets/lang/I18N_ru.properties
Normal file
@@ -0,0 +1,446 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# Contributors: Botylyov Yury
|
||||
about.copyright=Авторское право
|
||||
about.copyright.statement=Создано (c) 2019 huangyuhui.
|
||||
about.author=Автор
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=Благодарность
|
||||
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=Зависимости
|
||||
# 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)\nConstant Pool Scanner (jenkins-ci, CDDL, GPL 2)\nOpenNBT (Steveice10, BSD 3-Clause)
|
||||
about.claim=Соглашение
|
||||
about.claim.statement=Авторские права на Minecraft принадлежат Mojang AB. Мы не несем ответственности за любые нарушения авторских прав, возникающие в результате использования данного программного обеспечения.
|
||||
about.open_source=Open Source
|
||||
about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
|
||||
|
||||
account=Аккаунты
|
||||
account.character=герой
|
||||
account.choose=Выберите героя
|
||||
account.create=Создать новый аккаунт
|
||||
account.email=Почта
|
||||
account.failed.character_deleted=Персонаж был удален.
|
||||
account.failed.connect_authentication_server=Не удается подключиться к серверу аутентификации. Проверьте подключение к интернету.
|
||||
account.failed.connect_injector_server=Не удается подключиться к серверу аутентификации. Проверьте свою сеть и убедитесь, что URL-адрес правильный.
|
||||
account.failed.injector_download_failure=Не удалось загрузить authlib-injector. Проверьте свою сеть и попробуйте переключиться на другой источник загрузки.
|
||||
account.failed.invalid_credentials=Неверный пароль или вам временно запрещен вход.
|
||||
account.failed.invalid_password=Неправильный пароль
|
||||
account.failed.invalid_token=Пожалуйста, выйдите и повторно введите свой пароль для входа.
|
||||
account.failed.no_character=Нет героев в этом аккаунте.
|
||||
account.failed.server_response_malformed=Неверный ответ сервера. Возможно, на сервере аутентификации произошел сбой.
|
||||
account.injector.add=Добавить сервер аутентификации
|
||||
account.injector.empty=Пусто (нажмите кнопку «плюс» справа, чтобы добавить)
|
||||
account.injector.manage=Управление серверами аутентификации
|
||||
account.injector.manage.title=Серверы аутентификации
|
||||
account.injector.http=Предупреждение\: этот сервер использует HTTP, поэтому ваш пароль будет передан в виде открытого текста.
|
||||
account.injector.link.register=Зарегистрироваться
|
||||
account.injector.server=Сервер авторизации
|
||||
account.injector.server_url=URL сервера
|
||||
account.injector.server_name=Название сервера
|
||||
account.manage=Список аккаунтов
|
||||
account.methods=Тип аккаунта
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
account.methods.offline=Пиратка
|
||||
account.methods.yggdrasil=Лицензия
|
||||
account.missing=Без аккаунта
|
||||
account.missing.add=Нажмите сюда, чтобы добавить
|
||||
account.password=Пароль
|
||||
account.username=Никнейм
|
||||
|
||||
archive.author=Авторы
|
||||
archive.game_version=Игра
|
||||
archive.name=Никнейм
|
||||
archive.version=Версия
|
||||
|
||||
assets.download=Загрузка библиотек
|
||||
assets.download_all=Проверка целостности библиотек
|
||||
|
||||
button.cancel=Отмена
|
||||
button.clear=Очистить
|
||||
button.delete=Удалить
|
||||
button.edit=Редактировать
|
||||
button.install=Установить
|
||||
button.export=Экспорт
|
||||
button.no=Нет
|
||||
button.ok=Ок
|
||||
button.refresh=Обновить
|
||||
button.save=Сохранить
|
||||
button.yes=Да
|
||||
|
||||
color.recent=Рекомендуется
|
||||
color.custom=Свой цвет
|
||||
|
||||
crash.NoClassDefFound=Пожалуйста, проверьте, что программное обеспечение "Hello Minecraft! Launcher" не повреждено.
|
||||
crash.user_fault=Ваша ОС или среда Java могут быть неправильно установлены, что приведет к сбою этого программного обеспечения, пожалуйста, проверьте свою среду Java или свой компьютер\!
|
||||
|
||||
download=Загрузка
|
||||
download.code.404=Файл не найден на удаленном сервере
|
||||
download.failed=Не удалось загрузить
|
||||
download.failed.empty=Нет вариантов. Нажмите здесь, чтобы вернуться.
|
||||
download.failed.refresh=Невозможно загрузить список версий. Нажмите здесь, чтобы повторить попытку.
|
||||
download.provider.bmclapi=BMCLAPI (Резервный)
|
||||
download.provider.mojang=Mojang (Официальный)
|
||||
|
||||
extension.bat=Windows Bat файл
|
||||
extension.mod=Mod файл
|
||||
extension.png=Файл картинки
|
||||
extension.sh=Командная оболочка bash
|
||||
|
||||
fatal.missing_javafx=JavaFX отсутствует.\nЕсли вы используете Java 11 или более позднюю версию, пожалуйста, перейдите на Java 8 или 10.\nЕсли вы используете OpenJDK, убедитесь, что OpenJFX включен.
|
||||
fatal.missing_dst_root_ca_x3=The DST Root CA X3 certificate is missing on the current Java platform.\nYou can still use Hello Minecraft! Launcher, but Hello Minecraft! Launcher will be unable to connect to some sites (such as sites that use certificates issued by Let's Encrypt), which may cause Hello Minecraft! Launcher not to function properly.\nPlease upgrade your Java to 8u101 or later to resolve the problem.
|
||||
fatal.config_loading_failure=Конфигурация недоступна.\nУбедитесь, что у Hello Minecraft! Launcher есть права на чтение и запись для "%s" и файлов в нем.
|
||||
fatal.migration_requires_manual_reboot=Обновление скоро будет завершено. Пожалуйста, перезапустите Hello Minecraft! Launcher.
|
||||
fatal.apply_update_failure=Мы сожалеем, что Hello Minecraft! Launcher не смог завершить обновление, потому что что-то пошло не так.\nНо вы все же можете вручную завершить обновление, загрузив Hello Minecraft! Launcher с %s.\nПожалуйста, расскажите об этой проблеме нам.
|
||||
|
||||
folder.config=Конфиги
|
||||
folder.game=Директория игры
|
||||
folder.mod=Мод
|
||||
folder.resourcepacks=Ресурс паки
|
||||
folder.saves=Сохранения
|
||||
folder.screenshots=Скриншоты
|
||||
|
||||
help=Документация
|
||||
help.detail=Для производителей пакетов данных, модпаков и т.д.
|
||||
|
||||
input.email=Имя пользователя должно быть электронной почтой.
|
||||
input.number=Должно быть числом.
|
||||
input.not_empty=Обязательное поле
|
||||
input.url=Должен быть действующим URL.
|
||||
|
||||
install=Установить версию
|
||||
install.change_version=Изменить версию
|
||||
install.change_version.confirm=Уверены, что хотите обновить %s с версии %s до %s?
|
||||
install.failed=Не удалось установить
|
||||
install.failed.downloading=Не удалось установить из-за некоторых файлов, которые не были успешно загружены
|
||||
install.failed.downloading.detail=Не удалось загрузить файл\: %s
|
||||
install.failed.downloading.timeout=Превышено время ожидания при загрузке файла: %s
|
||||
install.failed.install_online=Невозможно распознать предоставленный файл установщика
|
||||
install.failed.optifine_conflict=OptiFine и Forge установлены одновременно на Minecraft 1.13
|
||||
install.failed.version_mismatch=Для библиотеки требуется версия игры %s, но фактическая версия - %s.
|
||||
install.installer.choose=Выберите %s версию
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Версия
|
||||
install.installer.install=Установка %s
|
||||
install.installer.install_offline=Установить/Обновить из файла
|
||||
install.installer.install_offline.extension=Forge/OptiFine установщик
|
||||
install.installer.install_offline.tooltip=Моддерживается импорт файлов установки Forge/OptiFine
|
||||
install.installer.install_online=Установить онлайн
|
||||
install.installer.install_online.tooltip=Поддерживается установка Forge, OptiFine, LiteLoader.
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=%s не установлен
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s Версия
|
||||
install.modpack=Новый модпак
|
||||
install.new_game=Новая версия
|
||||
install.new_game.already_exists=Эта версия уже существует.
|
||||
install.new_game.current_game_version=Текущая версия игры
|
||||
install.new_game.malformed=Неверное название
|
||||
install.select=Выберите операцию
|
||||
install.success=Успешно установлено
|
||||
|
||||
lang=Русский
|
||||
lang.default=Использовать язык системы
|
||||
|
||||
launch.advice.corrected=Мы уже исправили выбор JVM, и теперь игра может быть запущена. Если вы хотите сохранить свой выбор JVM, вы можете отключить проверку Java VM в настройках игры.
|
||||
launch.advice.uncorrected=Если вы уверены, что игра может быть запущена нормально, вы можете отключить проверку Java VM в настройках игры.
|
||||
launch.advice.different_platform=Ваша ОС 64-битная, а ваша Java 32-битная. Рекомендуется 64-битная Java.
|
||||
launch.advice.forge2760_liteloader=Forge 2760 и выше не совместимы с LiteLoader, рассмотрите возможность обновления Forge до 2773 или более поздней.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 и более поздние версии могут работать только на Java 8 или более поздней версии.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 может сломаться на Java 8 ранее, чем 1.8.0_51. Вы должны установить последнюю версию Java 8.
|
||||
launch.advice.java9=Вы не можете запустить Minecraft, пока версия игры не станет выше 1.13 с Java 9 или более поздними версиями Java.
|
||||
launch.advice.newer_java=Предлагается Java 8, которая может сделать игру более стабильной. А для многих модов и Minecraft 1.12 и более новых версий требуется Java 8.
|
||||
launch.advice.not_enough_space=Вы выделили слишком много памяти, поскольку объем физической памяти составляет %dMB, возможно, ваша игра будет вылетать. Лаунчер попробует это запустить.
|
||||
launch.advice.too_large_memory_for_32bit=Вы выделили слишком много памяти, из-за вашей 32-битной Java игра, вероятно, вылетает. Максимальный объем памяти составляет 1024 Мб. Лаунчер попробует с таким объемом памяти.
|
||||
launch.failed=Невозможно запустить
|
||||
launch.failed.cannot_create_jvm=Виртуальная машина Java не может быть создана. У аргументов Java могут быть проблемы. Вы можете включить режим без аргументов в настройках.
|
||||
launch.failed.creating_process=Не удалось создать процесс, возможно, ваш путь Java неверен, пожалуйста, измените путь Java.
|
||||
launch.failed.decompressing_natives=Невозможно распаковать нативные библиотеки.
|
||||
launch.failed.download_library=Невозможно загрузить библиотеку %s.
|
||||
launch.failed.executable_permission=Невозможно добавить разрешение в скрипт запуска
|
||||
launch.failed.exited_abnormally=Игра закрылась ненормально, зайдите в журнал или попросите кого-нибудь помочь.
|
||||
launch.state.dependencies=Зависимости
|
||||
launch.state.done=Готово
|
||||
launch.state.logging_in=Вход в систему
|
||||
launch.state.modpack=Приготовление к модпаку
|
||||
launch.state.waiting_launching=В ожидании запуска игры
|
||||
launch.wrong_javadir=Неверный каталог Java, будет установлен каталог Java по умолчанию.
|
||||
|
||||
launcher=Лаунчер
|
||||
launcher.background=Изображение на заднем плане
|
||||
launcher.background.choose=Выберите путь до изображения на заднем плане
|
||||
launcher.background.default=Стандартное
|
||||
launcher.cache_directory=Каталог для скачки библиотек
|
||||
launcher.cache_directory.choose=Выбор каталога для скачки кеша
|
||||
launcher.cache_directory.default=По умолчанию
|
||||
launcher.cache_directory.disabled=Отключено
|
||||
launcher.cache_directory.invalid=Неправильный пусть до каталога для скачки кеша. Будет установлен стандартный каталог.
|
||||
launcher.contact=Связаться с нами
|
||||
launcher.crash=Hello Minecraft! Launcher вылетел
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher вылетел! Ваш лаунчер устарел! Обновите его!
|
||||
launcher.update_java=Пожалуйста, обновите Java!
|
||||
|
||||
login.empty_username=Вы не установили имя пользователя!
|
||||
login.enter_password=Пожалуйста, введите пароль.
|
||||
|
||||
logwindow.show_lines=Показать линии
|
||||
logwindow.terminate_game=Завершить игру
|
||||
logwindow.title=Лог
|
||||
|
||||
main_page=Главная
|
||||
|
||||
message.confirm=Подтвердить
|
||||
message.doing=Пожалуйста, подождите
|
||||
message.downloading=Загрузка ...
|
||||
message.error=Ошибка
|
||||
message.info=Информация
|
||||
message.success=Задание успешно завершено
|
||||
message.unknown=Неизвестно
|
||||
message.warning=Предупреждение
|
||||
|
||||
modpack=Модпак
|
||||
modpack.choose=Выберите zip-файл модпака, который вы хотите установить.
|
||||
modpack.desc=Опишите свой модпак, включая меры предосторожности, журнал изменений, поддерживая Markdown (также поддерживая онлайн-изображения).
|
||||
modpack.enter_name=Введите желаемое имя для этой версии.
|
||||
modpack.export=Экспортировать модпак
|
||||
modpack.export.as=Экспорт модпака как...
|
||||
modpack.files.blueprints=BuildCraft чертежи
|
||||
modpack.files.config=Настройки мода
|
||||
modpack.files.dumps=Отладочный вывод NEI
|
||||
modpack.files.liteconfig=Мод конфигурации
|
||||
modpack.files.mods=Моды
|
||||
modpack.files.mods.voxelmods=Опции VoxelMods (включая VoxelMap)
|
||||
modpack.files.options_txt=Опции игры
|
||||
modpack.files.optionsshaders_txt=Опции шейдеров
|
||||
modpack.files.resourcepacks=Ресурсные (текстурные) паки
|
||||
modpack.files.saves=Сохраненные игры
|
||||
modpack.files.scripts=Конфигурация MineTweaker
|
||||
modpack.files.servers_dat=Список серверов
|
||||
modpack.install=Установка %s модпака
|
||||
modpack.installing=Установка модпака
|
||||
modpack.introduction=Curse, MultiMC, HMCL модпаки поддерживаются.
|
||||
modpack.invalid=Неверный файл модпака.
|
||||
modpack.mismatched_type=Несоответствующий тип модпака, ваша текущая игра %s модпак, но ваш файл обновления %s модпак.
|
||||
modpack.name=Название модпака
|
||||
modpack.not_a_valid_name=Неверное имя модпака
|
||||
modpack.scan=Сканирование этого модпака
|
||||
modpack.task.install=Импортировать модпак
|
||||
modpack.task.install.error=Этот файл мода не может быть распознан. Поддерживаются только Curse, MultiMC модпаки.
|
||||
modpack.task.install.will=Установите модпак\:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Установить файлы относительно модпака Curse
|
||||
modpack.type.curse.tolerable_error=Но мы не можем завершить загрузку всех файлов этого модпака Curse. Вы можете повторить загрузку при запуске соответствующей версии игры. Вы можете повторить попытку несколько раз из-за проблем с сетью.
|
||||
modpack.type.curse.error=Невозможно завершить этот модпак Curse. Пожалуйста, повторите.
|
||||
modpack.type.curse.not_found=Некоторые из необходимых ресурсов удалены и не могут быть загружены. Пожалуйста, рассмотрите последнюю версию или другие модпаки.
|
||||
modpack.type.hmcl=Hello Minecraft! Launcher
|
||||
modpack.type.hmcl.export=Can be imported by Hello Minecraft! Launcher
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC
|
||||
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.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Так же, запрещено добавлять моды, которые нельзя распространять в модпаке.
|
||||
|
||||
mods=Моды
|
||||
mods.add=Установить моды
|
||||
mods.add.failed=Не удалось установить моды %s.
|
||||
mods.add.success=Успешно установлены моды %s.
|
||||
mods.choose_mod=Выберите свои моды
|
||||
mods.enable=Включить
|
||||
mods.disable=Отключить
|
||||
mods.name=Название
|
||||
mods.remove=Удалить
|
||||
mods.not_modded=Сначала необходимо установить загрузчик модов (Forge, LiteLoader)
|
||||
|
||||
datapack=Пакеты данных
|
||||
datapack.add=Добавить пакет данных
|
||||
datapack.choose_datapack=Выберите zip-файл для импорта
|
||||
datapack.extension=Пакет данных
|
||||
datapack.title=Мир %s - Пакеты данных
|
||||
datapack.remove=Удалить
|
||||
|
||||
world=Сохранения
|
||||
world.add=Добавить мир (.zip)
|
||||
world.datapack=Управление данными
|
||||
world.datapack.1_13=Только Minecraft 1.13 и более поздние версии поддерживают пакеты данных.
|
||||
world.description=%s. Время последней игры\: %s. Версия игры\: %s.
|
||||
world.export=Экспортирт этого мира
|
||||
world.export.title=Выберите местоположение файла для хранения вашего мира
|
||||
world.export.location=Экспортировать в
|
||||
world.export.wizard=Экспорт мира %s
|
||||
world.extension=Zip файл мира
|
||||
world.game_version=Версия игры
|
||||
world.import.already_exists=Этот мир уже существует.
|
||||
world.import.choose=Выберите zip-файл для импорта
|
||||
world.import.failed=Невозможно импортировать этот мир\: %s
|
||||
world.import.invalid=Недопустимый zip мира
|
||||
world.name=Имя мира
|
||||
world.name.enter=Введите имя мира
|
||||
world.reveal=Показать в проводнике
|
||||
world.show_all=Показать все
|
||||
world.time=EEE, MMM d, yyyy HH\:mm\:ss
|
||||
|
||||
profile=Каталоги игры
|
||||
profile.already_exists=Это имя уже существует, пожалуйста, рассмотрите другое имя.
|
||||
profile.default=Текущий каталог
|
||||
profile.home=Стандартная
|
||||
profile.instance_directory=Каталог игры
|
||||
profile.instance_directory.choose=Выберите каталог игры
|
||||
profile.manage=Список каталогов игры
|
||||
profile.name=Имя
|
||||
profile.new=Новый конфиг
|
||||
profile.title=Каталоги игры
|
||||
profile.selected=Выбрано
|
||||
profile.use_relative_path=Используйте относительный путь к каталогу игры, если это возможно
|
||||
|
||||
selector.choose=Выбрать
|
||||
selector.choose_file=Выберите файл
|
||||
selector.custom=Настраиваемый
|
||||
|
||||
settings=Настройки игры
|
||||
|
||||
settings.advanced=Расширенные настройки
|
||||
settings.advanced.dont_check_game_completeness=Не проверять файлы игры
|
||||
settings.advanced.dont_check_jvm_validity=Не проверять, может ли JVM запустить игру или нет
|
||||
settings.advanced.game_dir.default=Стандарт (.minecraft/)
|
||||
settings.advanced.game_dir.independent=Независимая (.minecraft/versions/<версия>/, кроме ассетов и библиотек)
|
||||
settings.advanced.java_args_default=Стандартные аргументы java\: -XX\:+UseConcMarkSweepGC -XX\:+CMSIncrementalMode -XX\:-UseAdaptiveSizePolicy -XX\:MaxPermSize\=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates\=true -Dfml.ignorePatchDiscrepancies\=true
|
||||
settings.advanced.java_permanent_generation_space=PermGen Space/MB
|
||||
settings.advanced.jvm_args=Java VM Аргументы
|
||||
settings.advanced.launcher_visibility.close=Закрывать лаунчер после запуска игры.
|
||||
settings.advanced.launcher_visibility.hide=Скрывать лаунчер при запуске игры.
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=Скрывать лаунчер и снова открывать, когда игра закроется.
|
||||
settings.advanced.launcher_visibility.keep=Держать лаунчер видимым.
|
||||
settings.advanced.launcher_visible=Видимость лаунчера
|
||||
settings.advanced.minecraft_arguments=Аргументы Minecraft
|
||||
settings.advanced.no_jvm_args=Аргументы JVM по умолчанию отсутствуют
|
||||
settings.advanced.precall_command=Команда перед запуском (будет выполнена до запуска игры)
|
||||
settings.advanced.server_ip=Адрес сервера
|
||||
settings.advanced.wrapper_launcher=Wrapper Launcher(optirun...)
|
||||
|
||||
settings.custom=Своя
|
||||
|
||||
settings.game=Игры
|
||||
settings.game.dimension=Размер игрового окна
|
||||
settings.game.exploration=Изменить
|
||||
settings.game.fullscreen=На весь экран
|
||||
settings.game.java_directory=Каталог Java
|
||||
settings.game.java_directory.bit=, %s-бит
|
||||
settings.game.java_directory.choose=Выберите каталог Java.
|
||||
settings.game.management=Управление
|
||||
settings.game.working_directory=Рабочий каталог
|
||||
settings.game.working_directory.choose=Выберите рабочий каталог
|
||||
|
||||
settings.icon=Иконка игры
|
||||
|
||||
settings.launcher=Настройки
|
||||
settings.launcher.common_path.tooltip=Это приложение сохранит все игровые библиотеки и ресурсы здесь, если в папке с игрой нет существующих файлов.
|
||||
settings.launcher.download_source=Источник загрузки файлов
|
||||
settings.launcher.enable_game_list=Показать список версий на главной странице
|
||||
settings.launcher.language=Язык
|
||||
settings.launcher.launcher_log.export=Экспорт логов лаунчера
|
||||
settings.launcher.launcher_log.export.failed=Не удалось экспортировать лог
|
||||
settings.launcher.launcher_log.export.success=Логи успешно экспортированы в %s
|
||||
settings.launcher.log=Лог
|
||||
settings.launcher.log.font=Шрифт логов
|
||||
settings.launcher.proxy=Прокси
|
||||
settings.launcher.proxy.authentication=Аутентификация прокси
|
||||
settings.launcher.proxy.disable=Использовать системные прокси
|
||||
settings.launcher.proxy.host=Хост
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=Пароль
|
||||
settings.launcher.proxy.port=Порт
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.username=Пользователь
|
||||
settings.launcher.theme=Тема
|
||||
|
||||
settings.max_memory=Макс. Память / МБ
|
||||
settings.physical_memory=Размер физической памяти
|
||||
settings.show_log=Показать логи
|
||||
settings.tabs.installers=Установщики
|
||||
settings.type=Тип настройки версии
|
||||
settings.type.global=Общие глобальные настройки(для всех версий)
|
||||
settings.type.global.manage=Глобальные настройки игры
|
||||
settings.type.global.edit=Глобальные настройки
|
||||
settings.type.special.enable=Независимые настройки
|
||||
|
||||
sponsor.bmclapi=Для получения стабильной загрузки службы, пожалуйста, рассмотреть возможность поддержки BMCLAPI. Нажмите здесь для получения дополнительной информации
|
||||
sponsor.hmcl=Hello Minecraft! Launcher - это бесплатная программа Minecraft с открытым исходным кодом, позволяющая пользователям легко управлять несколькими отдельными установками Minecraft. Мы используем Afdian, чтобы продолжать оплачивать хостинг и разработку проектов. Для получения дополнительной информации нажмите здесь.
|
||||
|
||||
update=Обновление
|
||||
update.accept=Обновиться
|
||||
update.changelog=Изменения
|
||||
update.channel.dev=Обновляться до бета-версий
|
||||
update.channel.stable=Обновляться до стабильных версий
|
||||
update.checking=Проверка обновлений
|
||||
update.failed=Не удалось выполнить обновление
|
||||
update.found=Доступно обновление\!
|
||||
update.newest_version=Последняя версия\: %s
|
||||
update.bubble.title=Доступно обновление\: %s
|
||||
update.bubble.subtitle=Нажмите здесь, чтобы обновить
|
||||
update.note=Бета-версия содержит больше функций и исправлений ошибок, а также больше возможных ошибок.
|
||||
update.latest=Это последняя версия.
|
||||
update.no_browser=Не удается открыть любой браузер. Ссылка была скопирована в буфер обмена. Вставьте его в адресную строку браузера для обновления.
|
||||
update.tooltip=Обновить
|
||||
|
||||
version=Версии
|
||||
version.cannot_read=Невозможно узнать версию игры. Не удается продолжить автоматическую установку.
|
||||
version.empty=Нет версий
|
||||
version.empty.add=Установить версию
|
||||
version.empty.launch=Нет версии для запуска, пожалуйста, установите новую версию через страницу со списком версий.
|
||||
version.forbidden_name=Запрещенное имя, не используйте его.
|
||||
version.game.old=Старые
|
||||
version.game.release=Релизы
|
||||
version.game.snapshot=Снапшоты
|
||||
version.launch=Играть
|
||||
version.launch.test=Тест версии
|
||||
version.launch_script=Сделать стартовый скрипт
|
||||
version.launch_script.failed=Невозможно сделать скрипт запуска.
|
||||
version.launch_script.save=Сохраните скрипт запуска
|
||||
version.launch_script.success=Завершено создание скрипта, %s.
|
||||
version.manage=Все версии
|
||||
version.manage.clean=Очистить каталог версии
|
||||
version.manage.clean.tooltip=Очистить журналы, отчеты об ошибках
|
||||
version.manage.manage=Управление версией
|
||||
version.manage.redownload_assets_index=Перескачать ассеты
|
||||
version.manage.remove=Удалить эту версию
|
||||
version.manage.remove.confirm=Обязательно удалить версию %s? Вы не можете восстановить эту версию снова\!
|
||||
version.manage.remove.confirm.trash=Обязательно удалить версию %s? Вы можете восстановить игру с именем %s в системной корзине.
|
||||
version.manage.remove.confirm.independent=Поскольку эта версия находится в независимом режиме, удаление этой версии приведет к удалению всех сохраненных миров, принадлежащих этой игре. Намерены удалить версию %s?
|
||||
version.manage.remove_libraries=Удалить файлы библиотеки
|
||||
version.manage.rename=Переименовать эту версию
|
||||
version.manage.rename.message=Пожалуйста, введите имя для версии
|
||||
version.manage.rename.fail=Не удалось переименовать эту версию.
|
||||
version.settings=Настройки
|
||||
version.update=Обновление модпака
|
||||
|
||||
wizard.prev=< Пред
|
||||
wizard.failed=Не удалось
|
||||
wizard.finish=Закончено
|
||||
wizard.next=След >
|
||||
@@ -86,7 +86,7 @@ button.yes=是
|
||||
color.recent=推薦
|
||||
color.custom=自訂顏色
|
||||
|
||||
crash.NoClassDefFound=請確認 HMCL 本體是否完整,或更新您的 Java。
|
||||
crash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整,或更新您的 Java。
|
||||
crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體崩潰,請檢查您的 Java 環境或您的電腦!可以嘗試重新安裝 Java。
|
||||
|
||||
download=下載
|
||||
@@ -103,10 +103,10 @@ extension.png=圖片檔案
|
||||
extension.sh=Bash 腳本
|
||||
|
||||
fatal.missing_javafx=找不到 JavaFX。\n如果您使用的是 Java 11 或更高版本,請降級到 Java 8 或 10。\n如果您使用的是 OpenJDK,請確保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=目前的 Java 平台缺少 DST Root CA X3 憑證。\n您依然可以使用 HMCL,但將無法連線到部分網站(如使用 Lets Encrypt 證書的站點),這可能會使 HMCL 無法正常運作。\n請將您的 Java 升級到 8u101 以上以解決此問題。
|
||||
fatal.config_loading_failure=HMCL 無法載入設定檔案。\n請確保 HMCL 對 "%s" 目錄及該目錄下的檔案擁有讀寫權限。
|
||||
fatal.migration_requires_manual_reboot=HMCL 即將升級完成,請重新打開 HMCL。
|
||||
fatal.apply_update_failure=我們很抱歉 HMCL 無法自動完成升級程序,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 HMCL 來完成升級。\n請考慮向我們回報該問題。
|
||||
fatal.missing_dst_root_ca_x3=目前的 Java 平台缺少 DST Root CA X3 憑證。\n您依然可以使用 Hello Minecraft! Launcher,但將無法連線到部分網站(如使用 Lets Encrypt 證書的站點),這可能會使 Hello Minecraft! Launcher 無法正常運作。\n請將您的 Java 升級到 8u101 以上以解決此問題。
|
||||
fatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案。\n請確保 Hello Minecraft! Launcher 對 "%s" 目錄及該目錄下的檔案擁有讀寫權限。
|
||||
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新打開 Hello Minecraft! Launcher。
|
||||
fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程序,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 Hello Minecraft! Launcher 來完成升級。\n請考慮向我們回報該問題。
|
||||
|
||||
folder.config=設定資料夾
|
||||
folder.game=遊戲資料夾
|
||||
@@ -115,7 +115,7 @@ folder.resourcepacks=資源包資料夾
|
||||
folder.saves=遊戲存檔資料夾
|
||||
folder.screenshots=截圖資料夾
|
||||
|
||||
help=HMCL 幫助文檔
|
||||
help=Hello Minecraft! Launcher 幫助文檔
|
||||
help.detail=可查閱資料包、整合包製作指南等內容。
|
||||
|
||||
input.email=「使用者名稱」必須是電子信箱格式
|
||||
@@ -141,7 +141,7 @@ install.installer.install_offline=從本地檔案安裝/升級
|
||||
install.installer.install_offline.extension=Forge/OptiFine 安裝器
|
||||
install.installer.install_offline.tooltip=支持導入已經下載好的 Forge/OptiFine 安裝器
|
||||
install.installer.install_online=在線安裝
|
||||
install.installer.install_online.tooltip=支持安裝 Forge、OptiFine、LiteLoader
|
||||
install.installer.install_online.tooltip=支持安裝 Fabric、Forge、OptiFine、LiteLoader
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=暫時不安裝 %s,可以點擊此處安裝
|
||||
install.installer.optifine=OptiFine
|
||||
@@ -150,6 +150,7 @@ install.modpack=安裝整合包
|
||||
install.new_game=安裝新遊戲版本
|
||||
install.new_game.already_exists=此版本已經存在,請換一個名字
|
||||
install.new_game.current_game_version=目前遊戲版本
|
||||
install.new_game.malformed=名字不合法
|
||||
install.select=請選擇安裝方式
|
||||
install.success=安裝成功
|
||||
|
||||
@@ -217,6 +218,7 @@ modpack.choose=選擇要安裝的遊戲整合包檔案
|
||||
modpack.desc=描述你要製作的整合包,比如整合包注意事項和更新記錄,支援 Markdown(圖片請上傳至網路)。
|
||||
modpack.enter_name=給遊戲取個你喜歡的名字
|
||||
modpack.export=匯出整合包
|
||||
modpack.export.as=請選擇整合包類型。若你無法決定,請選擇 HMCL 類型。
|
||||
modpack.files.blueprints=BuildCraft 藍圖
|
||||
modpack.files.config=Mod 模組設定檔案
|
||||
modpack.files.dumps=NEI 調校輸出
|
||||
@@ -246,7 +248,9 @@ modpack.type.curse.tolerable_error=但未能完成 Curse 整合包文件的下
|
||||
modpack.type.curse.error=無法完成 Curse 整合包的下載,請多次重試或設定代理
|
||||
modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該整合包的最新版本或者安裝其他整合包。
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.hmcl.export=可以被 Hello Minecraft! Launcher (HMCL) 導入
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 導入
|
||||
modpack.unsupported=不支援該整合包。僅 HMCL、MultiMC、Curse 整合包受支援。
|
||||
modpack.update=正在升級整合包
|
||||
modpack.wizard=匯出整合包引導
|
||||
@@ -254,7 +258,8 @@ modpack.wizard.step.1=基本設定
|
||||
modpack.wizard.step.1.title=設定整合包的主要訊息
|
||||
modpack.wizard.step.2=檔案選擇
|
||||
modpack.wizard.step.2.title=選中你想加到整合包中的檔案或資料夾
|
||||
modpack.wizard.step.3=整合包描述
|
||||
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=選擇要匯出到的遊戲整合包位置
|
||||
@@ -267,8 +272,9 @@ mods.add.success=成功新增模組 %s。
|
||||
mods.choose_mod=選擇模組
|
||||
mods.enable=啟用
|
||||
mods.disable=禁用
|
||||
mods.name=名稱
|
||||
mods.remove=刪除
|
||||
mods.not_modded=你需要先在自動安裝頁面安裝 Forge 或 LiteLoader 才能進行模組管理。
|
||||
mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。
|
||||
|
||||
datapack=資料包
|
||||
datapack.add=添加資料包
|
||||
@@ -383,7 +389,7 @@ settings.type.global.edit=編輯全域遊戲設定
|
||||
settings.type.special.enable=啟用遊戲特別設定(不影響其他遊戲版本)
|
||||
|
||||
sponsor.bmclapi=請贊助 BMCLAPI 從而獲得穩定高速的下載服務,點擊此處查閱詳細訊息。
|
||||
sponsor.hmcl=HMCL 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、運行遊戲。您的贊助將幫助 HMCL 獲得更好的發展、支持穩定高速的遊戲安裝與文件下載服務。點擊此處查閱更多詳細訊息。
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、運行遊戲。您的贊助將幫助 Hello Minecraft! Launcher 獲得更好的發展、支持穩定高速的遊戲安裝與文件下載服務。點擊此處查閱更多詳細訊息。
|
||||
|
||||
update=啟動器更新
|
||||
update.accept=更新
|
||||
|
||||
@@ -86,7 +86,7 @@ button.yes=是
|
||||
color.recent=推荐
|
||||
color.custom=自定义颜色
|
||||
|
||||
crash.NoClassDefFound=请确认 HMCL 本体是否完整,或更新您的 Java。
|
||||
crash.NoClassDefFound=请确认 Hello Minecraft! Launcher 本体是否完整,或更新您的 Java。
|
||||
crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,请检查您的 Java 环境或您的电脑!可以尝试重新安装 Java。
|
||||
|
||||
download=下载
|
||||
@@ -103,10 +103,10 @@ extension.png=图片文件
|
||||
extension.sh=Bash 脚本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,请降级到 Java 8 或 10。\n如果您使用的是 OpenJDK,请确保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 HMCL,但将无法连接到部分站点(如使用 Lets Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
fatal.config_loading_failure=HMCL 无法加载配置文件。\n请确保 HMCL 对 "%s" 目录及该目录下的文件拥有读写权限。
|
||||
fatal.migration_requires_manual_reboot=HMCL 即将完成升级,请重新打开 HMCL。
|
||||
fatal.apply_update_failure=我们很抱歉 HMCL 无法自动完成升级,因为出现了一些问题。\n但你依然可以从 %s 处手动下载 HMCL 来完成升级。\n请考虑向我们反馈该问题。
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 Hello Minecraft! Launcher,但将无法连接到部分站点(如使用 Lets Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
fatal.config_loading_failure=Hello Minecraft! Launcher 无法加载配置文件。\n请确保 Hello Minecraft! Launcher 对 "%s" 目录及该目录下的文件拥有读写权限。
|
||||
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 Hello Minecraft! Launcher。
|
||||
fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依然可以从 %s 处手动下载 Hello Minecraft! Launcher 来完成升级。\n请考虑向我们反馈该问题。
|
||||
|
||||
folder.config=配置文件夹
|
||||
folder.game=游戏文件夹
|
||||
@@ -115,7 +115,7 @@ folder.resourcepacks=资源包文件夹
|
||||
folder.saves=存档文件夹
|
||||
folder.screenshots=截图文件夹
|
||||
|
||||
help=HMCL 帮助文档
|
||||
help=Hello Minecraft! Launcher 帮助文档
|
||||
help.detail=可查阅数据包、整合包制作指南等内容。
|
||||
|
||||
input.email=用户名必须是邮箱
|
||||
@@ -131,9 +131,10 @@ install.failed.downloading=安装失败,部分文件未能完成下载
|
||||
install.failed.downloading.detail=未能下载文件:%s
|
||||
install.failed.downloading.timeout=下载超时:%s
|
||||
install.failed.install_online=无法识别要安装的软件
|
||||
install.failed.optifine_conflict=暂不支持 OptiFine 与 Forge 同时安装在 Minecraft 1.13 上
|
||||
install.failed.optifine_conflict=暂不支持 OptiFine, Fabric 与 Forge 同时安装在 Minecraft 1.13 及以上版本
|
||||
install.failed.version_mismatch=该软件需要的游戏版本为 %s,但实际的游戏版本为 %s。
|
||||
install.installer.choose=选择 %s 版本
|
||||
install.installer.fabric=Fabric
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=游戏
|
||||
install.installer.install=安装 %s
|
||||
@@ -141,7 +142,7 @@ install.installer.install_offline=从本地文件安装/升级
|
||||
install.installer.install_offline.extension=Forge/OptiFine 安装器
|
||||
install.installer.install_offline.tooltip=支持导入已经下载好的 Forge/OptiFine 安装器
|
||||
install.installer.install_online=在线安装
|
||||
install.installer.install_online.tooltip=支持安装 Forge、OptiFine、LiteLoader
|
||||
install.installer.install_online.tooltip=支持安装 Fabric、Forge、OptiFine、LiteLoader
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=暂不安装 %s,可以点击此处安装
|
||||
install.installer.optifine=OptiFine
|
||||
@@ -150,6 +151,7 @@ install.modpack=安装整合包
|
||||
install.new_game=安装新游戏版本
|
||||
install.new_game.already_exists=此版本已经存在,请换一个名字
|
||||
install.new_game.current_game_version=当前游戏版本
|
||||
install.new_game.malformed=名字不合法
|
||||
install.select=请选择安装方式
|
||||
install.success=安装成功
|
||||
|
||||
@@ -205,7 +207,7 @@ main_page=主页
|
||||
|
||||
message.confirm=提示
|
||||
message.doing=请耐心等待
|
||||
message.downloading=正在下载...
|
||||
message.downloading=正在下载
|
||||
message.error=错误
|
||||
message.info=提示
|
||||
message.success=已完成
|
||||
@@ -217,6 +219,7 @@ modpack.choose=选择要安装的游戏整合包文件
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown(图片请用网络图)。
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
modpack.export=导出整合包
|
||||
modpack.export.as=请选择整合包类型。若你无法决定,请选择 HMCL 类型。
|
||||
modpack.files.blueprints=BuildCraft 蓝图
|
||||
modpack.files.config=Mod 配置文件
|
||||
modpack.files.dumps=NEI 调试输出
|
||||
@@ -246,7 +249,9 @@ modpack.type.curse.tolerable_error=但未能完成 Curse 整合包文件的下
|
||||
modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理
|
||||
modpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载,请尝试该整合包的最新版本或者安装其他整合包。
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.hmcl.export=可以被 Hello Minecraft! Launcher (HMCL) 导入
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 导入
|
||||
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。
|
||||
modpack.update=正在升级整合包
|
||||
modpack.wizard=导出整合包向导
|
||||
@@ -254,7 +259,8 @@ modpack.wizard.step.1=基本设置
|
||||
modpack.wizard.step.1.title=设置整合包的主要信息
|
||||
modpack.wizard.step.2=文件选择
|
||||
modpack.wizard.step.2.title=选中你想加到整合包中的文件或文件夹
|
||||
modpack.wizard.step.3=整合包描述
|
||||
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=选择要导出到的游戏整合包位置
|
||||
@@ -267,8 +273,9 @@ mods.add.success=成功添加模组 %s。
|
||||
mods.choose_mod=选择模组
|
||||
mods.enable=启用
|
||||
mods.disable=禁用
|
||||
mods.name=名称
|
||||
mods.remove=删除
|
||||
mods.not_modded=你需要先在自动安装页面安装 Forge 或 LiteLoader 才能进行模组管理。
|
||||
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
|
||||
|
||||
datapack=数据包
|
||||
datapack.add=添加数据包
|
||||
@@ -383,7 +390,7 @@ settings.type.global.edit=编辑全局版本设置
|
||||
settings.type.special.enable=启用游戏特定设置(不影响其他游戏版本)
|
||||
|
||||
sponsor.bmclapi=请赞助 BMCLAPI 从而获得稳定高速的下载服务,点击此处查阅详细信息。
|
||||
sponsor.hmcl=HMCL 是一个免费、开源的 Minecraft 启动器,允许玩家方便快捷地安装、管理、运行游戏。您的赞助将帮助 HMCL 获得更好的发展、支持稳定高速的游戏安装与文件下载服务。点击此处查阅更多详细信息。
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、开源的 Minecraft 启动器,允许玩家方便快捷地安装、管理、运行游戏。您的赞助将帮助 Hello Minecraft! Launcher 获得更好的发展、支持稳定高速的游戏安装与文件下载服务。点击此处查阅更多详细信息。
|
||||
|
||||
update=启动器更新
|
||||
update.accept=更新
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
.trace { background-color: blue; }
|
||||
</style>
|
||||
<script>
|
||||
var colors = ["fatal", "error", "warn", "info", "debug", "trace"]
|
||||
var colors = ["fatal", "error", "warn", "info", "debug", "trace"];
|
||||
var limitedLogs = 100;
|
||||
function appendLog(log, level) {
|
||||
var e = document.createElement("div");
|
||||
@@ -78,13 +78,13 @@
|
||||
redisplay(c[i]);
|
||||
}
|
||||
function redisplay(div) {
|
||||
var flag = false
|
||||
var flag = false;
|
||||
for (var j = 0; j < colors.length; ++j) {
|
||||
if (div.className == colors[j]) {
|
||||
if (div.className === colors[j]) {
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
div.hidden = div.className != colors[j];
|
||||
div.hidden = div.className !== colors[j];
|
||||
}
|
||||
div.hidden = !flag;
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide
|
||||
|
||||
private static final String LATEST_BUILD_URL = "https://authlib-injector.yushi.moe/artifact/latest.json";
|
||||
|
||||
private Path artifactLocation;
|
||||
private Supplier<DownloadProvider> downloadProvider;
|
||||
private final Path artifactLocation;
|
||||
private final Supplier<DownloadProvider> downloadProvider;
|
||||
|
||||
/**
|
||||
* The flag will be reset after application restart.
|
||||
|
||||
@@ -25,7 +25,7 @@ public enum TextureModel {
|
||||
|
||||
public final String modelName;
|
||||
|
||||
private TextureModel(String modelName) {
|
||||
TextureModel(String modelName) {
|
||||
this.modelName = modelName;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.download.fabric.FabricVersionList;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList;
|
||||
import org.jackhuang.hmcl.download.game.GameVersionList;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderBMCLVersionList;
|
||||
@@ -43,6 +44,8 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
switch (id) {
|
||||
case "game":
|
||||
return GameVersionList.INSTANCE;
|
||||
case "fabric":
|
||||
return FabricVersionList.INSTANCE;
|
||||
case "forge":
|
||||
return ForgeBMCLVersionList.INSTANCE;
|
||||
case "liteloader":
|
||||
|
||||
@@ -23,8 +23,11 @@ import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -60,7 +63,7 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
if (Files.isRegularFile(indexFile))
|
||||
index = JsonUtils.GSON.fromJson(FileUtils.readText(indexFile.toFile()), Index.class);
|
||||
index = JsonUtils.fromNonNullJson(FileUtils.readText(indexFile.toFile()), Index.class);
|
||||
else
|
||||
index = new Index();
|
||||
} catch (IOException | JsonParseException e) {
|
||||
@@ -214,7 +217,7 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
* // assets and versions will not be included in index.
|
||||
* }
|
||||
*/
|
||||
private class Index {
|
||||
private class Index implements Validation {
|
||||
private final Set<LibraryIndex> libraries;
|
||||
|
||||
public Index() {
|
||||
@@ -222,21 +225,28 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
}
|
||||
|
||||
public Index(Set<LibraryIndex> libraries) {
|
||||
this.libraries = libraries;
|
||||
this.libraries = Objects.requireNonNull(libraries);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<LibraryIndex> getLibraries() {
|
||||
return libraries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
if (libraries == null)
|
||||
throw new JsonParseException("Index.libraries cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
private class LibraryIndex {
|
||||
private class LibraryIndex implements Validation {
|
||||
private final String name;
|
||||
private final String hash;
|
||||
private final String type;
|
||||
|
||||
public LibraryIndex() {
|
||||
this(null, null, null);
|
||||
this("", "", "");
|
||||
}
|
||||
|
||||
public LibraryIndex(String name, String hash, String type) {
|
||||
@@ -245,18 +255,27 @@ public class DefaultCacheRepository extends CacheRepository {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
if (name == null || hash == null || type == null)
|
||||
throw new JsonParseException("Index.LibraryIndex.* cannot be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
@@ -18,17 +18,13 @@
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.*;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderInstallTask;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.ParallelTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -72,9 +68,10 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task checkGameCompletionAsync(Version version) {
|
||||
return new ParallelTask(
|
||||
Task.ofThen(() -> {
|
||||
public Task<?> checkGameCompletionAsync(Version original) {
|
||||
Version version = original.resolve(repository);
|
||||
return Task.allOf(
|
||||
Task.composeAsync(() -> {
|
||||
if (!repository.getVersionJar(version).exists())
|
||||
return new GameDownloadTask(this, null, version);
|
||||
else
|
||||
@@ -86,44 +83,39 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task checkLibraryCompletionAsync(Version version) {
|
||||
return new GameLibrariesTask(this, version);
|
||||
public Task<?> checkLibraryCompletionAsync(Version version) {
|
||||
return new GameLibrariesTask(this, version, version.getLibraries());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskResult<Version> installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion) {
|
||||
public Task<Version> installLibraryAsync(String gameVersion, Version baseVersion, String libraryId, String libraryVersion) {
|
||||
if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
|
||||
|
||||
VersionList<?> versionList = getVersionList(libraryId);
|
||||
return versionList.loadAsync(gameVersion, getDownloadProvider())
|
||||
.thenCompose(() -> installLibraryAsync(version, versionList.getVersion(gameVersion, libraryVersion)
|
||||
.orElseThrow(() -> new IllegalStateException("Remote library " + libraryId + " has no version " + libraryVersion))));
|
||||
.thenComposeAsync(() -> installLibraryAsync(baseVersion, versionList.getVersion(gameVersion, libraryVersion)
|
||||
.orElseThrow(() -> new IOException("Remote library " + libraryId + " has no version " + libraryVersion))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskResult<Version> installLibraryAsync(Version oldVersion, RemoteVersion libraryVersion) {
|
||||
TaskResult<Version> task;
|
||||
if (libraryVersion instanceof ForgeRemoteVersion)
|
||||
task = new ForgeInstallTask(this, oldVersion, (ForgeRemoteVersion) libraryVersion);
|
||||
else if (libraryVersion instanceof LiteLoaderRemoteVersion)
|
||||
task = new LiteLoaderInstallTask(this, oldVersion, (LiteLoaderRemoteVersion) libraryVersion);
|
||||
else if (libraryVersion instanceof OptiFineRemoteVersion)
|
||||
task = new OptiFineInstallTask(this, oldVersion, (OptiFineRemoteVersion) libraryVersion);
|
||||
else
|
||||
throw new IllegalArgumentException("Remote library " + libraryVersion + " is unrecognized.");
|
||||
return task
|
||||
.thenCompose(LibrariesUniqueTask::new)
|
||||
.thenCompose(MaintainTask::new)
|
||||
.thenCompose(newVersion -> new VersionJsonSaveTask(repository, newVersion));
|
||||
public Task<Version> installLibraryAsync(Version baseVersion, RemoteVersion libraryVersion) {
|
||||
if (baseVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
|
||||
|
||||
return removeLibraryAsync(baseVersion.resolvePreservingPatches(repository), libraryVersion.getLibraryId())
|
||||
.thenComposeAsync(version -> libraryVersion.getInstallTask(this, version))
|
||||
.thenApplyAsync(baseVersion::addPatch)
|
||||
.thenComposeAsync(repository::save);
|
||||
}
|
||||
|
||||
public ExceptionalFunction<Version, TaskResult<Version>, ?> installLibraryAsync(RemoteVersion libraryVersion) {
|
||||
public ExceptionalFunction<Version, Task<Version>, ?> installLibraryAsync(RemoteVersion libraryVersion) {
|
||||
return version -> installLibraryAsync(version, libraryVersion);
|
||||
}
|
||||
|
||||
public Task installLibraryAsync(Version oldVersion, Path installer) {
|
||||
public Task<Version> installLibraryAsync(Version oldVersion, Path installer) {
|
||||
if (oldVersion.isResolved()) throw new IllegalArgumentException("Version should not be resolved");
|
||||
|
||||
return Task
|
||||
.of(() -> {
|
||||
})
|
||||
.thenCompose(() -> {
|
||||
.composeAsync(() -> {
|
||||
try {
|
||||
return ForgeInstallTask.install(this, oldVersion, installer);
|
||||
} catch (IOException ignore) {
|
||||
@@ -134,10 +126,32 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Library cannot be recognized");
|
||||
throw new UnsupportedLibraryInstallerException();
|
||||
})
|
||||
.thenCompose(LibrariesUniqueTask::new)
|
||||
.thenCompose(MaintainTask::new)
|
||||
.thenCompose(newVersion -> new VersionJsonSaveTask(repository, newVersion));
|
||||
.thenApplyAsync(oldVersion::addPatch)
|
||||
.thenComposeAsync(repository::save);
|
||||
}
|
||||
|
||||
public static class UnsupportedLibraryInstallerException extends Exception {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove installed library.
|
||||
* Will try to remove libraries and patches.
|
||||
*
|
||||
* @param version not resolved version
|
||||
* @param libraryId forge/liteloader/optifine/fabric
|
||||
* @return task to remove the specified library
|
||||
*/
|
||||
public Task<Version> removeLibraryAsync(Version version, String libraryId) {
|
||||
// MaintainTask requires version that does not inherits from any version.
|
||||
// If we want to remove a library in dependent version, we should keep the dependents not changed
|
||||
// So resolving this game version to preserve all information in this version.json is necessary.
|
||||
if (version.isResolved())
|
||||
throw new IllegalArgumentException("removeLibraryWithoutSavingAsync requires non-resolved version");
|
||||
Version independentVersion = version.resolvePreservingPatches(repository);
|
||||
|
||||
return Task.supplyAsync(() -> LibraryAnalyzer.analyze(independentVersion).removeLibrary(libraryId).build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,13 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.download.game.*;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.ParallelTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -48,40 +46,23 @@ public class DefaultGameBuilder extends GameBuilder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task buildAsync() {
|
||||
return new VersionJsonDownloadTask(gameVersion, dependencyManager).thenCompose(rawJson -> {
|
||||
Version original = JsonUtils.GSON.fromJson(rawJson, Version.class);
|
||||
Version version = original.setId(name).setJar(null);
|
||||
Task vanillaTask = downloadGameAsync(gameVersion, version).then(new ParallelTask(
|
||||
new GameAssetDownloadTask(dependencyManager, version, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY),
|
||||
new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
|
||||
).with(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version))); // using [with] because download failure here are tolerant.
|
||||
public Task<?> buildAsync() {
|
||||
Task<Version> libraryTask = Task.supplyAsync(() -> new Version(name));
|
||||
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, "game", gameVersion));
|
||||
|
||||
TaskResult<Version> libraryTask = vanillaTask.thenSupply(() -> version);
|
||||
for (Map.Entry<String, String> entry : toolVersions.entrySet())
|
||||
libraryTask = libraryTask.thenComposeAsync(libraryTaskHelper(gameVersion, entry.getKey(), entry.getValue()));
|
||||
|
||||
if (toolVersions.containsKey("forge"))
|
||||
libraryTask = libraryTask.thenCompose(libraryTaskHelper(gameVersion, "forge"));
|
||||
if (toolVersions.containsKey("liteloader"))
|
||||
libraryTask = libraryTask.thenCompose(libraryTaskHelper(gameVersion, "liteloader"));
|
||||
if (toolVersions.containsKey("optifine"))
|
||||
libraryTask = libraryTask.thenCompose(libraryTaskHelper(gameVersion, "optifine"));
|
||||
for (RemoteVersion remoteVersion : remoteVersions)
|
||||
libraryTask = libraryTask.thenComposeAsync(dependencyManager.installLibraryAsync(remoteVersion));
|
||||
|
||||
for (RemoteVersion remoteVersion : remoteVersions)
|
||||
libraryTask = libraryTask.thenCompose(dependencyManager.installLibraryAsync(remoteVersion));
|
||||
|
||||
return libraryTask;
|
||||
}).whenComplete((isDependentSucceeded, exception) -> {
|
||||
if (!isDependentSucceeded)
|
||||
return libraryTask.whenComplete(exception -> {
|
||||
if (exception != null)
|
||||
dependencyManager.getGameRepository().removeVersionFromDisk(name);
|
||||
});
|
||||
}
|
||||
|
||||
private ExceptionalFunction<Version, TaskResult<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId) {
|
||||
return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, toolVersions.get(libraryId));
|
||||
private ExceptionalFunction<Version, Task<Version>, ?> libraryTaskHelper(String gameVersion, String libraryId, String libraryVersion) {
|
||||
return version -> dependencyManager.installLibraryAsync(gameVersion, version, libraryId, libraryVersion);
|
||||
}
|
||||
|
||||
protected Task downloadGameAsync(String gameVersion, Version version) {
|
||||
return new GameDownloadTask(dependencyManager, gameVersion, version);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public interface DependencyManager {
|
||||
*
|
||||
* @return the task to check game completion.
|
||||
*/
|
||||
Task checkGameCompletionAsync(Version version);
|
||||
Task<?> checkGameCompletionAsync(Version version);
|
||||
|
||||
/**
|
||||
* Check if the game is complete.
|
||||
@@ -54,7 +54,7 @@ public interface DependencyManager {
|
||||
*
|
||||
* @return the task to check game completion.
|
||||
*/
|
||||
Task checkLibraryCompletionAsync(Version version);
|
||||
Task<?> checkLibraryCompletionAsync(Version version);
|
||||
|
||||
/**
|
||||
* The builder to build a brand new game then libraries such as Forge, LiteLoader and OptiFine.
|
||||
@@ -66,22 +66,22 @@ public interface DependencyManager {
|
||||
* **Note**: Installing a library may change the version.json.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that the library relies on.
|
||||
* @param version the version.json.
|
||||
* @param baseVersion the version.json.
|
||||
* @param libraryId the type of being installed library. i.e. "forge", "liteloader", "optifine"
|
||||
* @param libraryVersion the version of being installed library.
|
||||
* @return the task to install the specific library.
|
||||
*/
|
||||
Task installLibraryAsync(String gameVersion, Version version, String libraryId, String libraryVersion);
|
||||
Task<?> installLibraryAsync(String gameVersion, Version baseVersion, String libraryId, String libraryVersion);
|
||||
|
||||
/**
|
||||
* Install a library to a version.
|
||||
* **Note**: Installing a library may change the version.json.
|
||||
*
|
||||
* @param version the version.json.\
|
||||
* @param baseVersion the version.json.
|
||||
* @param libraryVersion the remote version of being installed library.
|
||||
* @return the task to install the specific library.
|
||||
*/
|
||||
Task installLibraryAsync(Version version, RemoteVersion libraryVersion);
|
||||
Task<?> installLibraryAsync(Version baseVersion, RemoteVersion libraryVersion);
|
||||
|
||||
/**
|
||||
* Get registered version list.
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The service provider that provides Minecraft online file downloads.
|
||||
*
|
||||
@@ -39,11 +42,18 @@ public interface DownloadProvider {
|
||||
*/
|
||||
String injectURL(String baseURL);
|
||||
|
||||
default Stream<String> injectURLs(String[] baseURLs) {
|
||||
Stream<String> urls = Arrays.stream(baseURLs);
|
||||
Stream<String> jsonURLs = Arrays.stream(baseURLs).map(this::injectURL);
|
||||
return Stream.concat(jsonURLs, urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* the specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
|
||||
*
|
||||
* @param id the id of specific version list that this download provider provides. i.e. "forge", "liteloader", "game", "optifine"
|
||||
* @return the version list
|
||||
* @throws IllegalArgumentException if the version list does not exist
|
||||
*/
|
||||
VersionList<?> getVersionListById(String id);
|
||||
}
|
||||
|
||||
@@ -72,5 +72,5 @@ public abstract class GameBuilder {
|
||||
/**
|
||||
* @return the task that can build thw whole Minecraft environment
|
||||
*/
|
||||
public abstract Task buildAsync();
|
||||
public abstract Task<?> buildAsync();
|
||||
}
|
||||
|
||||
@@ -19,63 +19,149 @@ package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class LibraryAnalyzer {
|
||||
private final Map<LibraryType, Library> libraries;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
private LibraryAnalyzer(Map<LibraryType, Library> libraries) {
|
||||
public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMark> {
|
||||
private Version version;
|
||||
private final Map<String, Pair<Library, String>> libraries;
|
||||
|
||||
private LibraryAnalyzer(Version version, Map<String, Pair<Library, String>> libraries) {
|
||||
this.version = version;
|
||||
this.libraries = libraries;
|
||||
}
|
||||
|
||||
public Optional<Library> get(LibraryType type) {
|
||||
return Optional.ofNullable(libraries.get(type));
|
||||
public Optional<String> getVersion(LibraryType type) {
|
||||
return getVersion(type.getPatchId());
|
||||
}
|
||||
|
||||
public Optional<String> getVersion(String type) {
|
||||
return Optional.ofNullable(libraries.get(type)).map(Pair::getValue);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<LibraryMark> iterator() {
|
||||
return new Iterator<LibraryMark>() {
|
||||
Iterator<Map.Entry<String, Pair<Library, String>>> impl = libraries.entrySet().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return impl.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LibraryMark next() {
|
||||
Map.Entry<String, Pair<Library, String>> entry = impl.next();
|
||||
return new LibraryMark(entry.getKey(), entry.getValue().getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean has(LibraryType type) {
|
||||
return has(type.getPatchId());
|
||||
}
|
||||
|
||||
public boolean has(String type) {
|
||||
return libraries.containsKey(type);
|
||||
}
|
||||
|
||||
public boolean hasModLoader() {
|
||||
return Arrays.stream(LibraryType.values())
|
||||
.filter(LibraryType::isModLoader)
|
||||
.anyMatch(this::has);
|
||||
return libraries.keySet().stream().map(LibraryType::fromPatchId)
|
||||
.filter(Objects::nonNull)
|
||||
.anyMatch(LibraryType::isModLoader);
|
||||
}
|
||||
|
||||
public boolean hasModLauncher() {
|
||||
final String modLauncher = "cpw.mods.modlauncher.Launcher";
|
||||
return modLauncher.equals(version.getMainClass()) || version.getPatches().stream().anyMatch(patch -> modLauncher.equals(patch.getMainClass()));
|
||||
}
|
||||
|
||||
private Version removingMatchedLibrary(Version version, String libraryId) {
|
||||
LibraryType type = LibraryType.fromPatchId(libraryId);
|
||||
if (type == null) return version;
|
||||
|
||||
List<Library> libraries = new ArrayList<>();
|
||||
for (Library library : version.getLibraries()) {
|
||||
String groupId = library.getGroupId();
|
||||
String artifactId = library.getArtifactId();
|
||||
|
||||
if (type.group.matcher(groupId).matches() && type.artifact.matcher(artifactId).matches()) {
|
||||
// skip
|
||||
} else {
|
||||
libraries.add(library);
|
||||
}
|
||||
}
|
||||
return version.setLibraries(libraries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove library by library id
|
||||
* @param libraryId patch id or "forge"/"optifine"/"liteloader"/"fabric"
|
||||
* @return this
|
||||
*/
|
||||
public LibraryAnalyzer removeLibrary(String libraryId) {
|
||||
if (!has(libraryId)) return this;
|
||||
version = removingMatchedLibrary(version, libraryId)
|
||||
.setPatches(version.getPatches().stream()
|
||||
.filter(patch -> !libraryId.equals(patch.getId()))
|
||||
.map(patch -> removingMatchedLibrary(patch, libraryId))
|
||||
.collect(Collectors.toList()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Version build() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public static LibraryAnalyzer analyze(Version version) {
|
||||
Map<LibraryType, Library> libraries = new EnumMap<>(LibraryType.class);
|
||||
if (version.getInheritsFrom() != null)
|
||||
throw new IllegalArgumentException("LibraryAnalyzer can only analyze independent game version");
|
||||
|
||||
for (Library library : version.getLibraries()) {
|
||||
Map<String, Pair<Library, String>> libraries = new HashMap<>();
|
||||
|
||||
for (Library library : version.resolve(null).getLibraries()) {
|
||||
String groupId = library.getGroupId();
|
||||
String artifactId = library.getArtifactId();
|
||||
|
||||
for (LibraryType type : LibraryType.values()) {
|
||||
if (type.group.matcher(groupId).matches() && type.artifact.matcher(artifactId).matches()) {
|
||||
libraries.put(type, library);
|
||||
libraries.put(type.getPatchId(), pair(library, library.getVersion()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LibraryAnalyzer(libraries);
|
||||
for (Version patch : version.getPatches()) {
|
||||
if (patch.isHidden()) continue;
|
||||
libraries.put(patch.getId(), pair(null, patch.getVersion()));
|
||||
}
|
||||
|
||||
return new LibraryAnalyzer(version, libraries);
|
||||
}
|
||||
|
||||
public enum LibraryType {
|
||||
FORGE(true, Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")),
|
||||
LITELOADER(true, Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")),
|
||||
OPTIFINE(false, Pattern.compile("(net\\.)?optifine"), Pattern.compile(".*")),
|
||||
FABRIC(true, Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader"));
|
||||
MINECRAFT(true, "game", Pattern.compile("^$"), Pattern.compile("^$")),
|
||||
FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")),
|
||||
FORGE(true, "forge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")),
|
||||
LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")),
|
||||
OPTIFINE(false, "optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile("^(?!.*launchwrapper).*$"));
|
||||
|
||||
private final Pattern group, artifact;
|
||||
private final boolean modLoader;
|
||||
private final String patchId;
|
||||
private final Pattern group, artifact;
|
||||
|
||||
LibraryType(boolean modLoader, Pattern group, Pattern artifact) {
|
||||
LibraryType(boolean modLoader, String patchId, Pattern group, Pattern artifact) {
|
||||
this.modLoader = modLoader;
|
||||
this.patchId = patchId;
|
||||
this.group = group;
|
||||
this.artifact = artifact;
|
||||
}
|
||||
@@ -83,5 +169,36 @@ public final class LibraryAnalyzer {
|
||||
public boolean isModLoader() {
|
||||
return modLoader;
|
||||
}
|
||||
|
||||
public String getPatchId() {
|
||||
return patchId;
|
||||
}
|
||||
|
||||
public static LibraryType fromPatchId(String patchId) {
|
||||
for (LibraryType type : values())
|
||||
if (type.getPatchId().equals(patchId))
|
||||
return type;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LibraryMark {
|
||||
private final String libraryId;
|
||||
private final String libraryVersion;
|
||||
|
||||
public LibraryMark(@NotNull String libraryId, @Nullable String libraryVersion) {
|
||||
this.libraryId = libraryId;
|
||||
this.libraryVersion = libraryVersion;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getLibraryId() {
|
||||
return libraryId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getLibraryVersion() {
|
||||
return libraryVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,37 +17,63 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.CompatibilityRule;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.VersionLibraryBuilder;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
|
||||
public class MaintainTask extends TaskResult<Version> {
|
||||
|
||||
public class MaintainTask extends Task<Version> {
|
||||
private final GameRepository repository;
|
||||
private final Version version;
|
||||
|
||||
public MaintainTask(Version version) {
|
||||
public MaintainTask(GameRepository repository, Version version) {
|
||||
this.repository = repository;
|
||||
this.version = version;
|
||||
|
||||
if (version.getInheritsFrom() != null)
|
||||
throw new IllegalArgumentException("MaintainTask requires independent game version");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
setResult(maintain(version));
|
||||
setResult(maintain(repository, version));
|
||||
}
|
||||
|
||||
public static Version maintain(Version version) {
|
||||
if (version.getMainClass().contains("launchwrapper")) {
|
||||
return maintainGameWithLaunchWrapper(version);
|
||||
public static Version maintain(GameRepository repository, Version version) {
|
||||
if (version.getInheritsFrom() != null)
|
||||
throw new IllegalArgumentException("MaintainTask requires independent game version");
|
||||
|
||||
String mainClass = version.resolve(null).getMainClass();
|
||||
|
||||
if (mainClass != null && mainClass.contains("launchwrapper")) {
|
||||
return maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(unique(version)));
|
||||
} else {
|
||||
// Vanilla Minecraft does not need maintain
|
||||
// Forge 1.13 support not implemented, not compatible with OptiFine currently.
|
||||
return version;
|
||||
// Fabric does not need maintain, nothing compatible with fabric now.
|
||||
return maintainOptiFineLibrary(repository, unique(version));
|
||||
}
|
||||
}
|
||||
|
||||
private static Version maintainGameWithLaunchWrapper(Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
String mainClass = null;
|
||||
|
||||
if (!libraryAnalyzer.has(FORGE)) {
|
||||
builder.removeTweakClass("forge");
|
||||
@@ -56,7 +82,7 @@ public class MaintainTask extends TaskResult<Version> {
|
||||
// Installing Forge will override the Minecraft arguments in json, so LiteLoader and OptiFine Tweaker are being re-added.
|
||||
|
||||
builder.removeTweakClass("liteloader");
|
||||
if (libraryAnalyzer.has(LITELOADER)) {
|
||||
if (libraryAnalyzer.has(LITELOADER) && !libraryAnalyzer.hasModLauncher()) {
|
||||
builder.addArgument("--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker");
|
||||
}
|
||||
|
||||
@@ -65,11 +91,101 @@ public class MaintainTask extends TaskResult<Version> {
|
||||
if (!libraryAnalyzer.has(LITELOADER) && !libraryAnalyzer.has(FORGE)) {
|
||||
builder.addArgument("--tweakClass", "optifine.OptiFineTweaker");
|
||||
} else {
|
||||
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
|
||||
builder.addArgument("--tweakClass", "optifine.OptiFineForgeTweaker");
|
||||
if (libraryAnalyzer.hasModLauncher()) {
|
||||
// If ModLauncher installed, we use ModLauncher in place of LaunchWrapper.
|
||||
mainClass = "cpw.mods.modlauncher.Launcher";
|
||||
} else {
|
||||
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
|
||||
builder.addArgument("--tweakClass", "optifine.OptiFineForgeTweaker");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
Version ret = builder.build();
|
||||
return mainClass == null ? ret : ret.setMainClass(mainClass);
|
||||
}
|
||||
|
||||
private static Version maintainOptiFineLibrary(GameRepository repository, Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
List<Library> libraries = new ArrayList<>(version.getLibraries());
|
||||
|
||||
if (libraryAnalyzer.has(OPTIFINE)) {
|
||||
if (libraryAnalyzer.has(LITELOADER) || libraryAnalyzer.has(FORGE)) {
|
||||
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
|
||||
// And we should load the installer jar instead of patch jar.
|
||||
if (repository != null)
|
||||
for (int i = 0; i < version.getLibraries().size(); ++i) {
|
||||
Library library = libraries.get(i);
|
||||
if (library.is("optifine", "OptiFine")) {
|
||||
Library newLibrary = new Library(new Artifact("optifine", "OptiFine", library.getVersion(), "installer"));
|
||||
if (repository.getLibraryFile(version, newLibrary).exists()) {
|
||||
libraries.set(i, null);
|
||||
// OptiFine should be loaded after Forge in classpath.
|
||||
// Although we have altered priority of OptiFine higher than Forge,
|
||||
// there still exists a situation that Forge is installed without patch.
|
||||
// Here we manually alter the position of OptiFine library in classpath.
|
||||
libraries.add(newLibrary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return version.setLibraries(libraries.stream().filter(Objects::nonNull).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static Version unique(Version version) {
|
||||
List<Library> libraries = new ArrayList<>();
|
||||
|
||||
SimpleMultimap<String, Integer> multimap = new SimpleMultimap<String, Integer>(HashMap::new, LinkedList::new);
|
||||
|
||||
for (Library library : version.getLibraries()) {
|
||||
String id = library.getGroupId() + ":" + library.getArtifactId();
|
||||
VersionNumber number = VersionNumber.asVersion(library.getVersion());
|
||||
String serialized = JsonUtils.GSON.toJson(library);
|
||||
|
||||
if (multimap.containsKey(id)) {
|
||||
boolean duplicate = false;
|
||||
for (int otherLibraryIndex : multimap.get(id)) {
|
||||
Library otherLibrary = libraries.get(otherLibraryIndex);
|
||||
VersionNumber otherNumber = VersionNumber.asVersion(otherLibrary.getVersion());
|
||||
if (CompatibilityRule.equals(library.getRules(), otherLibrary.getRules())) { // rules equal, ignore older version.
|
||||
boolean flag = true;
|
||||
if (number.compareTo(otherNumber) > 0) { // if this library is newer
|
||||
// replace [otherLibrary] with [library]
|
||||
libraries.set(otherLibraryIndex, library);
|
||||
} else if (number.compareTo(otherNumber) == 0) { // same library id.
|
||||
// prevent from duplicated libraries
|
||||
if (library.equals(otherLibrary)) {
|
||||
String otherSerialized = JsonUtils.GSON.toJson(otherLibrary);
|
||||
// A trick, the library that has more information is better, which can be
|
||||
// considered whose serialized JSON text will be longer.
|
||||
if (serialized.length() > otherSerialized.length()) {
|
||||
libraries.set(otherLibraryIndex, library);
|
||||
}
|
||||
} else {
|
||||
// for text2speech, which have same library id as well as version number,
|
||||
// but its library and native library does not equal
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!duplicate) {
|
||||
multimap.put(id, libraries.size());
|
||||
libraries.add(library);
|
||||
}
|
||||
} else {
|
||||
multimap.put(id, libraries.size());
|
||||
libraries.add(library);
|
||||
}
|
||||
}
|
||||
|
||||
return version.setLibraries(libraries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.download.fabric.FabricVersionList;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeBMCLVersionList;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeVersionList;
|
||||
import org.jackhuang.hmcl.download.game.GameVersionList;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
|
||||
@@ -44,6 +44,8 @@ public class MojangDownloadProvider implements DownloadProvider {
|
||||
switch (id) {
|
||||
case "game":
|
||||
return GameVersionList.INSTANCE;
|
||||
case "fabric":
|
||||
return FabricVersionList.INSTANCE;
|
||||
case "forge":
|
||||
return ForgeBMCLVersionList.INSTANCE;
|
||||
case "liteloader":
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -29,9 +32,10 @@ import java.util.Objects;
|
||||
*/
|
||||
public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
|
||||
private final String libraryId;
|
||||
private final String gameVersion;
|
||||
private final String selfVersion;
|
||||
private final String url;
|
||||
private final String[] url;
|
||||
private final Type type;
|
||||
|
||||
/**
|
||||
@@ -41,8 +45,8 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
public RemoteVersion(String gameVersion, String selfVersion, String url) {
|
||||
this(gameVersion, selfVersion, url, Type.UNCATEGORIZED);
|
||||
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, String... url) {
|
||||
this(libraryId, gameVersion, selfVersion, Type.UNCATEGORIZED, url);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,13 +56,18 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
public RemoteVersion(String gameVersion, String selfVersion, String url, Type type) {
|
||||
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Type type, String... url) {
|
||||
this.libraryId = Objects.requireNonNull(libraryId);
|
||||
this.gameVersion = Objects.requireNonNull(gameVersion);
|
||||
this.selfVersion = Objects.requireNonNull(selfVersion);
|
||||
this.url = Objects.requireNonNull(url);
|
||||
this.type = Objects.requireNonNull(type);
|
||||
}
|
||||
|
||||
public String getLibraryId() {
|
||||
return libraryId;
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
@@ -67,7 +76,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
return selfVersion;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
public String[] getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -75,6 +84,10 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
|
||||
throw new UnsupportedOperationException(toString() + " cannot be installed yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof RemoteVersion && Objects.equals(selfVersion, ((RemoteVersion) obj).selfVersion);
|
||||
|
||||
@@ -62,19 +62,19 @@ public abstract class VersionList<T extends RemoteVersion> {
|
||||
* @param downloadProvider DownloadProvider
|
||||
* @return the task to reload the remote version list.
|
||||
*/
|
||||
public abstract Task refreshAsync(DownloadProvider downloadProvider);
|
||||
public abstract Task<?> refreshAsync(DownloadProvider downloadProvider);
|
||||
|
||||
/**
|
||||
* @param gameVersion the remote version depends on
|
||||
* @param downloadProvider DownloadProvider
|
||||
* @return the task to reload the remote version list.
|
||||
*/
|
||||
public Task refreshAsync(String gameVersion, DownloadProvider downloadProvider) {
|
||||
public Task<?> refreshAsync(String gameVersion, DownloadProvider downloadProvider) {
|
||||
return refreshAsync(downloadProvider);
|
||||
}
|
||||
|
||||
public Task loadAsync(DownloadProvider downloadProvider) {
|
||||
return Task.ofThen(() -> {
|
||||
public Task<?> loadAsync(DownloadProvider downloadProvider) {
|
||||
return Task.composeAsync(() -> {
|
||||
lock.readLock().lock();
|
||||
boolean loaded;
|
||||
|
||||
@@ -87,8 +87,8 @@ public abstract class VersionList<T extends RemoteVersion> {
|
||||
});
|
||||
}
|
||||
|
||||
public Task loadAsync(String gameVersion, DownloadProvider downloadProvider) {
|
||||
return Task.ofThen(() -> {
|
||||
public Task<?> loadAsync(String gameVersion, DownloadProvider downloadProvider) {
|
||||
return Task.composeAsync(() -> {
|
||||
lock.readLock().lock();
|
||||
boolean loaded;
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.fabric;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <b>Note</b>: Fabric should be installed first.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class FabricInstallTask extends Task<Version> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final FabricRemoteVersion remote;
|
||||
private final GetTask launchMetaTask;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
public FabricInstallTask(DefaultDependencyManager dependencyManager, Version version, FabricRemoteVersion remoteVersion) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.remote = remoteVersion;
|
||||
|
||||
launchMetaTask = new GetTask(NetworkUtils.toURL(getLaunchMetaUrl(remote.getSelfVersion())))
|
||||
.setCacheRepository(dependencyManager.getCacheRepository());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
if (!Objects.equals("net.minecraft.client.main.Main", version.resolve(dependencyManager.getGameRepository()).getMainClass()))
|
||||
throw new UnsupportedFabricInstallationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return Collections.singleton(launchMetaTask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependencies() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
setResult(getPatch(JsonUtils.GSON.fromJson(launchMetaTask.getResult(), JsonObject.class), remote.getGameVersion(), remote.getSelfVersion()));
|
||||
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(getResult()));
|
||||
}
|
||||
|
||||
private static String getLaunchMetaUrl(String loaderVersion) {
|
||||
return String.format("%s/%s/%s/%s/%3$s-%4$s.json", "https://maven.fabricmc.net/", "net/fabricmc", "fabric-loader", loaderVersion);
|
||||
}
|
||||
|
||||
private Version getPatch(JsonObject jsonObject, String gameVersion, String loaderVersion) {
|
||||
Arguments arguments = new Arguments();
|
||||
|
||||
String mainClass;
|
||||
if (!jsonObject.get("mainClass").isJsonObject()) {
|
||||
mainClass = jsonObject.get("mainClass").getAsString();
|
||||
} else {
|
||||
mainClass = jsonObject.get("mainClass").getAsJsonObject().get("client").getAsString();
|
||||
}
|
||||
|
||||
if (jsonObject.has("launchwrapper")) {
|
||||
String clientTweaker = jsonObject.get("launchwrapper").getAsJsonObject().get("tweakers").getAsJsonObject().get("client").getAsJsonArray().get(0).getAsString();
|
||||
arguments = arguments.addGameArguments("--tweakClass", clientTweaker);
|
||||
}
|
||||
|
||||
JsonObject librariesObject = jsonObject.getAsJsonObject("libraries");
|
||||
List<Library> libraries = new ArrayList<>();
|
||||
|
||||
// "common, server" is hard coded in fabric installer.
|
||||
// Don't know the purpose of ignoring client libraries.
|
||||
for (String side : new String[]{"common", "server"}) {
|
||||
for (JsonElement element : librariesObject.getAsJsonArray(side)) {
|
||||
libraries.add(JsonUtils.GSON.fromJson(element, Library.class));
|
||||
}
|
||||
}
|
||||
|
||||
libraries.add(new Library(new Artifact("net.fabricmc", "intermediary", gameVersion), "https://maven.fabricmc.net/", null));
|
||||
libraries.add(new Library(new Artifact("net.fabricmc", "fabric-loader", loaderVersion), "https://maven.fabricmc.net/", null));
|
||||
|
||||
return new Version("net.fabricmc", loaderVersion, 30000, arguments, mainClass, libraries);
|
||||
}
|
||||
|
||||
public static class UnsupportedFabricInstallationException extends Exception {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.fabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
public class FabricRemoteVersion extends RemoteVersion {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param gameVersion the Minecraft version that this remote version suits.
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
FabricRemoteVersion(String gameVersion, String selfVersion, String url) {
|
||||
super(LibraryAnalyzer.LibraryType.FABRIC.getPatchId(), gameVersion, selfVersion, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
|
||||
return new FabricInstallTask(dependencyManager, baseVersion, this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.fabric;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class FabricVersionList extends VersionList<FabricRemoteVersion> {
|
||||
|
||||
public static final FabricVersionList INSTANCE = new FabricVersionList();
|
||||
|
||||
private FabricVersionList() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> refreshAsync(DownloadProvider downloadProvider) {
|
||||
return new Task<Void>() {
|
||||
@Override
|
||||
public void execute() throws IOException, XMLStreamException {
|
||||
List<String> gameVersions = getGameVersions(META_URL);
|
||||
List<String> loaderVersions = getVersions(FABRIC_MAVEN_URL, FABRIC_PACKAGE_NAME, FABRIC_JAR_NAME);
|
||||
|
||||
lock.writeLock().lock();
|
||||
|
||||
try {
|
||||
for (String gameVersion : gameVersions)
|
||||
for (String loaderVersion : loaderVersions)
|
||||
versions.put(gameVersion, new FabricRemoteVersion(gameVersion, loaderVersion, ""));
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static final String META_URL = "https://meta.fabricmc.net/v2/versions/game";
|
||||
private static final String FABRIC_MAVEN_URL = "https://maven.fabricmc.net/";
|
||||
private static final String FABRIC_PACKAGE_NAME = "net/fabricmc";
|
||||
private static final String FABRIC_JAR_NAME = "fabric-loader";
|
||||
|
||||
private List<String> getVersions(String mavenServerURL, String packageName, String jarName) throws IOException, XMLStreamException {
|
||||
List<String> versions = new ArrayList<>();
|
||||
URL url = new URL(mavenServerURL + packageName + "/" + jarName + "/maven-metadata.xml");
|
||||
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(url.openStream());
|
||||
|
||||
while(reader.hasNext()) {
|
||||
if (reader.next() == 1 && reader.getLocalName().equals("version")) {
|
||||
String text = reader.getElementText();
|
||||
versions.add(text);
|
||||
}
|
||||
}
|
||||
|
||||
reader.close();
|
||||
Collections.reverse(versions);
|
||||
return versions;
|
||||
}
|
||||
|
||||
private List<String> getGameVersions(String metaUrl) throws IOException {
|
||||
String json = NetworkUtils.doGet(NetworkUtils.toURL(metaUrl));
|
||||
return JsonUtils.GSON.<ArrayList<GameVersion>>fromJson(json, new TypeToken<ArrayList<GameVersion>>() {
|
||||
}.getType()).stream().map(GameVersion::getVersion).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static class GameVersion {
|
||||
private final String version;
|
||||
private final boolean stable;
|
||||
|
||||
public GameVersion() {
|
||||
this("", false);
|
||||
}
|
||||
|
||||
public GameVersion(String version, boolean stable) {
|
||||
this.version = version;
|
||||
this.stable = stable;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public boolean isStable() {
|
||||
return stable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,17 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
|
||||
public static final ForgeBMCLVersionList INSTANCE = new ForgeBMCLVersionList();
|
||||
@@ -46,21 +52,21 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task loadAsync(DownloadProvider downloadProvider) {
|
||||
public Task<?> loadAsync(DownloadProvider downloadProvider) {
|
||||
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
public Task<?> refreshAsync(DownloadProvider downloadProvider) {
|
||||
throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(String gameVersion, DownloadProvider downloadProvider) {
|
||||
public Task<?> refreshAsync(String gameVersion, DownloadProvider downloadProvider) {
|
||||
final GetTask task = new GetTask(NetworkUtils.toURL("https://bmclapi2.bangbang93.com/forge/minecraft/" + gameVersion));
|
||||
return new Task() {
|
||||
return new Task<Void>() {
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return Collections.singleton(task);
|
||||
}
|
||||
|
||||
@@ -76,19 +82,28 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
for (ForgeVersion version : forgeVersions) {
|
||||
if (version == null)
|
||||
continue;
|
||||
String jar = null;
|
||||
List<String> urls = new ArrayList<>();
|
||||
for (ForgeVersion.File file : version.getFiles())
|
||||
if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) {
|
||||
String classifier = gameVersion + "-" + version.getVersion()
|
||||
+ (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : "");
|
||||
String fileName = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat();
|
||||
jar = "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" + classifier + "/" + fileName;
|
||||
String fileName1 = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat();
|
||||
String fileName2 = "forge-" + classifier + "-" + gameVersion + "-" + file.getCategory() + "." + file.getFormat();
|
||||
urls.add(NetworkUtils.withQuery("https://bmclapi2.bangbang93.com/forge/download", mapOf(
|
||||
pair("mcversion", version.getGameVersion()),
|
||||
pair("version", version.getVersion()),
|
||||
pair("branch", version.getBranch()),
|
||||
pair("category", file.getCategory()),
|
||||
pair("format", file.getFormat())
|
||||
)));
|
||||
urls.add("https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1);
|
||||
urls.add("https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/" + classifier + "-" + gameVersion + "/" + fileName2);
|
||||
}
|
||||
|
||||
if (jar == null)
|
||||
if (urls.isEmpty())
|
||||
continue;
|
||||
versions.put(gameVersion, new ForgeRemoteVersion(
|
||||
version.getGameVersion(), version.getVersion(), jar
|
||||
version.getGameVersion(), version.getVersion(), urls.toArray(new String[0])
|
||||
));
|
||||
}
|
||||
} finally {
|
||||
@@ -121,18 +136,22 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getBranch() {
|
||||
return branch;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getGameVersion() {
|
||||
return mcversion;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<File> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
/**
|
||||
@@ -28,7 +29,7 @@ public final class ForgeInstall {
|
||||
|
||||
private final String profileName;
|
||||
private final String target;
|
||||
private final String path;
|
||||
private final Artifact path;
|
||||
private final String version;
|
||||
private final String filePath;
|
||||
private final String welcome;
|
||||
@@ -40,7 +41,7 @@ public final class ForgeInstall {
|
||||
this(null, null, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public ForgeInstall(String profileName, String target, String path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) {
|
||||
public ForgeInstall(String profileName, String target, Artifact path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) {
|
||||
this.profileName = profileName;
|
||||
this.target = target;
|
||||
this.path = path;
|
||||
@@ -60,7 +61,7 @@ public final class ForgeInstall {
|
||||
return target;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
public Artifact getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
@@ -34,23 +33,24 @@ import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class ForgeInstallTask extends TaskResult<Version> {
|
||||
public final class ForgeInstallTask extends Task<Version> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private Path installer;
|
||||
private final ForgeRemoteVersion remote;
|
||||
private Task dependent;
|
||||
private TaskResult<Version> dependency;
|
||||
private Task<Void> dependent;
|
||||
private Task<Version> dependency;
|
||||
|
||||
public ForgeInstallTask(DefaultDependencyManager dependencyManager, Version version, ForgeRemoteVersion remoteVersion) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
@@ -67,7 +67,12 @@ public final class ForgeInstallTask extends TaskResult<Version> {
|
||||
public void preExecute() throws Exception {
|
||||
installer = Files.createTempFile("forge-installer", ".jar");
|
||||
|
||||
dependent = new FileDownloadTask(NetworkUtils.toURL(remote.getUrl()), installer.toFile())
|
||||
dependent = new FileDownloadTask(
|
||||
Arrays.stream(remote.getUrl())
|
||||
.map(NetworkUtils::toURL)
|
||||
.collect(Collectors.toList()),
|
||||
installer.toFile(), null)
|
||||
.setCacheRepository(dependencyManager.getCacheRepository())
|
||||
.setCaching(true);
|
||||
}
|
||||
|
||||
@@ -83,25 +88,26 @@ public final class ForgeInstallTask extends TaskResult<Version> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return Collections.singleton(dependent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependencies() {
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return Collections.singleton(dependency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
if (VersionNumber.VERSION_COMPARATOR.compare("1.13", remote.getGameVersion()) <= 0)
|
||||
dependency = new ForgeNewInstallTask(dependencyManager, version, installer);
|
||||
dependency = new ForgeNewInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);
|
||||
else
|
||||
dependency = new ForgeOldInstallTask(dependencyManager, version, installer);
|
||||
dependency = new ForgeOldInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install Forge library from existing local file.
|
||||
* This method will try to identify this installer whether it is in old or new format.
|
||||
*
|
||||
* @param dependencyManager game repository
|
||||
* @param version version.json
|
||||
@@ -110,7 +116,7 @@ public final class ForgeInstallTask extends TaskResult<Version> {
|
||||
* @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.
|
||||
* @throws VersionMismatchException if required game version of installer does not match the actual one.
|
||||
*/
|
||||
public static TaskResult<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
|
||||
public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(dependencyManager.getGameRepository().getVersionJar(version));
|
||||
if (!gameVersion.isPresent()) throw new IOException();
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
|
||||
@@ -120,15 +126,19 @@ public final class ForgeInstallTask extends TaskResult<Version> {
|
||||
ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);
|
||||
if (!gameVersion.get().equals(profile.getMinecraft()))
|
||||
throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());
|
||||
return new ForgeNewInstallTask(dependencyManager, version, installer);
|
||||
return new ForgeNewInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getPath().getVersion().replaceAll("(?i)forge", "")), installer);
|
||||
} else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) {
|
||||
ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class);
|
||||
if (!gameVersion.get().equals(profile.getInstall().getMinecraft()))
|
||||
throw new VersionMismatchException(profile.getInstall().getMinecraft(), gameVersion.get());
|
||||
return new ForgeOldInstallTask(dependencyManager, version, installer);
|
||||
return new ForgeOldInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getInstall().getPath().getVersion().replaceAll("(?i)forge", "")), installer);
|
||||
} else {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String modifyVersion(String gameVersion, String version) {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,14 @@
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
@@ -32,16 +35,23 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -50,23 +60,25 @@ import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class ForgeNewInstallTask extends TaskResult<Version> {
|
||||
public class ForgeNewInstallTask extends Task<Version> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final DefaultGameRepository gameRepository;
|
||||
private final Version version;
|
||||
private final Path installer;
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
private ForgeNewInstallProfile profile;
|
||||
private Version forgeVersion;
|
||||
private final String selfVersion;
|
||||
|
||||
public ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
|
||||
ForgeNewInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.gameRepository = dependencyManager.getGameRepository();
|
||||
this.version = version;
|
||||
this.installer = installer;
|
||||
this.selfVersion = selfVersion;
|
||||
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
}
|
||||
@@ -77,18 +89,18 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
|
||||
else if (StringUtils.isSurrounded(literal, "'", "'"))
|
||||
return StringUtils.removeSurrounding(literal, "'");
|
||||
else if (StringUtils.isSurrounded(literal, "[", "]"))
|
||||
return gameRepository.getArtifactFile(version, new Artifact(StringUtils.removeSurrounding(literal, "[", "]"))).toString();
|
||||
return gameRepository.getArtifactFile(version, Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, "[", "]"))).toString();
|
||||
else
|
||||
return plainConverter.apply(literal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return dependents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task> getDependencies() {
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@@ -125,7 +137,7 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass()))
|
||||
if ("net.minecraft.launchwrapper.Launch".equals(version.resolve(dependencyManager.getGameRepository()).getMainClass()))
|
||||
throw new OptiFineInstallTask.UnsupportedOptiFineInstallationException();
|
||||
|
||||
Path temp = Files.createTempDirectory("forge_installer");
|
||||
@@ -219,25 +231,20 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
|
||||
|
||||
command.add(mainClass);
|
||||
|
||||
List<String> args = processor.getArgs().stream().map(arg -> {
|
||||
List<String> args = new ArrayList<>(processor.getArgs().size());
|
||||
for (String arg : processor.getArgs()) {
|
||||
String parsed = parseLiteral(arg, data, ExceptionalFunction.identity());
|
||||
if (parsed == null)
|
||||
throw new IllegalStateException("Invalid forge installation configuration");
|
||||
return parsed;
|
||||
}).collect(Collectors.toList());
|
||||
throw new IOException("Invalid forge installation configuration");
|
||||
args.add(parsed);
|
||||
}
|
||||
|
||||
command.addAll(args);
|
||||
|
||||
LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString());
|
||||
Process process = new ProcessBuilder(command).start();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
for (String line; (line = reader.readLine()) != null;) {
|
||||
System.out.println(line);
|
||||
}
|
||||
}
|
||||
int exitCode = process.waitFor();
|
||||
int exitCode = SystemUtils.callExternalProcess(command);
|
||||
if (exitCode != 0)
|
||||
throw new IllegalStateException("Game processor exited abnormally");
|
||||
throw new IOException("Game processor exited abnormally");
|
||||
|
||||
for (Map.Entry<String, String> entry : outputs.entrySet()) {
|
||||
Path artifact = Paths.get(entry.getKey());
|
||||
@@ -259,15 +266,10 @@ public class ForgeNewInstallTask extends TaskResult<Version> {
|
||||
}
|
||||
}
|
||||
|
||||
// resolve the version
|
||||
SimpleVersionProvider provider = new SimpleVersionProvider();
|
||||
provider.addVersion(version);
|
||||
|
||||
setResult(forgeVersion
|
||||
.setInheritsFrom(version.getId())
|
||||
.resolve(provider).setJar(null)
|
||||
.setId(version.getId()).setLogging(Collections.emptyMap()));
|
||||
|
||||
.setPriority(30000)
|
||||
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
|
||||
.setVersion(selfVersion));
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(forgeVersion));
|
||||
|
||||
FileUtils.deleteDirectory(temp.toFile());
|
||||
|
||||
@@ -18,36 +18,44 @@
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class ForgeOldInstallTask extends TaskResult<Version> {
|
||||
public class ForgeOldInstallTask extends Task<Version> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final Path installer;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
private final String selfVersion;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
public ForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, Path installer) {
|
||||
ForgeOldInstallTask(DefaultDependencyManager dependencyManager, Version version, String selfVersion, Path installer) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.installer = installer;
|
||||
this.selfVersion = selfVersion;
|
||||
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task> getDependencies() {
|
||||
public List<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@@ -66,7 +74,7 @@ public class ForgeOldInstallTask extends TaskResult<Version> {
|
||||
ForgeInstallProfile installProfile = JsonUtils.fromNonNullJson(json, ForgeInstallProfile.class);
|
||||
|
||||
// unpack the universal jar in the installer file.
|
||||
Library forgeLibrary = Library.fromName(installProfile.getInstall().getPath());
|
||||
Library forgeLibrary = new Library(installProfile.getInstall().getPath());
|
||||
File forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary);
|
||||
if (!FileUtils.makeFile(forgeFile))
|
||||
throw new IOException("Cannot make directory " + forgeFile.getParent());
|
||||
@@ -76,15 +84,10 @@ public class ForgeOldInstallTask extends TaskResult<Version> {
|
||||
IOUtils.copyTo(is, os);
|
||||
}
|
||||
|
||||
// resolve the version
|
||||
SimpleVersionProvider provider = new SimpleVersionProvider();
|
||||
provider.addVersion(version);
|
||||
|
||||
setResult(installProfile.getVersionInfo()
|
||||
.setInheritsFrom(version.getId())
|
||||
.resolve(provider).setJar(null)
|
||||
.setId(version.getId()).setLogging(Collections.emptyMap()));
|
||||
|
||||
.setPriority(30000)
|
||||
.setId(LibraryAnalyzer.LibraryType.FORGE.getPatchId())
|
||||
.setVersion(selfVersion));
|
||||
dependencies.add(dependencyManager.checkLibraryCompletionAsync(installProfile.getVersionInfo()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.forge;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
public class ForgeRemoteVersion extends RemoteVersion {
|
||||
/**
|
||||
@@ -27,7 +31,12 @@ public class ForgeRemoteVersion extends RemoteVersion {
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
public ForgeRemoteVersion(String gameVersion, String selfVersion, String url) {
|
||||
super(gameVersion, selfVersion, url);
|
||||
public ForgeRemoteVersion(String gameVersion, String selfVersion, String... url) {
|
||||
super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
|
||||
return new ForgeInstallTask(dependencyManager, baseVersion, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
public Task<?> refreshAsync(DownloadProvider downloadProvider) {
|
||||
final GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(FORGE_LIST)));
|
||||
return new Task() {
|
||||
return new Task<Void>() {
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return Collections.singleton(task);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,38 +38,38 @@ import java.util.List;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameAssetDownloadTask extends Task {
|
||||
public final class GameAssetDownloadTask extends Task<Void> {
|
||||
|
||||
private final AbstractDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final AssetIndexInfo assetIndexInfo;
|
||||
private final File assetIndexFile;
|
||||
private final List<Task> dependents = new LinkedList<>();
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
* @param version the game version
|
||||
*/
|
||||
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version, boolean forceDownloadingIndex) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.assetIndexInfo = version.getAssetIndex();
|
||||
this.version = version.resolve(dependencyManager.getGameRepository());
|
||||
this.assetIndexInfo = this.version.getAssetIndex();
|
||||
this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
|
||||
|
||||
if (!assetIndexFile.exists() || forceDownloadingIndex)
|
||||
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, version));
|
||||
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, this.version));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return dependents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependencies() {
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ public final class GameAssetDownloadTask extends Task {
|
||||
String url = dependencyManager.getDownloadProvider().getAssetBaseURL() + assetObject.getLocation();
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
task.setName(assetObject.getHash());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task
|
||||
.setCacheRepository(dependencyManager.getCacheRepository())
|
||||
.setCaching(true)
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.jackhuang.hmcl.game.AssetIndexInfo;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -34,11 +33,11 @@ import java.util.List;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameAssetIndexDownloadTask extends Task {
|
||||
public final class GameAssetIndexDownloadTask extends Task<Void> {
|
||||
|
||||
private final AbstractDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -53,7 +52,7 @@ public final class GameAssetIndexDownloadTask extends Task {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task> getDependencies() {
|
||||
public List<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user