From 1a572be52ebd93d3241de4eb944732fccd8e90bb Mon Sep 17 00:00:00 2001 From: huangyuhui Date: Sat, 27 Jan 2018 12:56:45 +0800 Subject: [PATCH] Modpack updates UI --- .../hmcl/game/HMCLDependencyManager.java | 47 +++++++++++ .../jackhuang/hmcl/game/HMCLGameBuilder.java | 41 ++++++++++ .../hmcl/game/HMCLGameDownloadTask.java | 78 +++++++++++++++++++ .../hmcl/game/HMCLGameRepository.java | 16 +++- .../hmcl/game/HMCLModpackInstallTask.java | 15 +++- .../jackhuang/hmcl/game/ModpackHelper.java | 65 ++++++++++++++-- .../MultiMCInstallVersionSettingTask.java | 5 +- .../org/jackhuang/hmcl/setting/Profile.java | 5 +- .../org/jackhuang/hmcl/ui/Controllers.java | 15 +++- .../java/org/jackhuang/hmcl/ui/MainPage.java | 34 ++++++++ .../main/java/org/jackhuang/hmcl/ui/SVG.java | 24 ++++++ .../org/jackhuang/hmcl/ui/VersionItem.java | 14 +++- .../org/jackhuang/hmcl/ui/VersionPage.java | 4 +- .../hmcl/ui/construct/MessageBox.java | 4 + .../hmcl/ui/construct/MessageDialogPane.java | 47 ++++++++++- .../ui/construct/TaskExecutorDialogPane.java | 3 + .../TaskExecutorDialogWizardDisplayer.java | 8 +- .../hmcl/ui/construct/TaskListPane.java | 4 +- .../hmcl/ui/download/ModpackPage.java | 5 +- .../hmcl/ui/export/ExportWizardProvider.java | 5 +- .../assets/fxml/download/modpack.fxml | 2 +- .../resources/assets/fxml/message-dialog.fxml | 29 ++++--- .../resources/assets/fxml/version-item.fxml | 1 + .../resources/assets/lang/I18N.properties | 17 ++-- .../assets/lang/I18N_zh_CN.properties | 13 +++- HMCLCore/gradlew | 4 +- HMCLCore/gradlew.bat | 4 +- .../hmcl/download/DefaultGameBuilder.java | 14 +++- .../hmcl/game/DefaultGameRepository.java | 8 ++ .../jackhuang/hmcl/mod/CurseInstallTask.java | 16 ++-- .../hmcl/mod/MinecraftInstanceTask.java | 16 ++-- .../mod/MismatchedModpackTypeException.java | 30 +++++++ .../hmcl/mod/ModpackConfiguration.java | 52 ++++++++----- .../hmcl/mod/ModpackInstallTask.java | 26 ++++--- .../hmcl/mod/MultiMCModpackInstallTask.java | 16 ++-- .../hmcl/mod/UnsupportedModpackException.java | 35 +++++++++ .../jackhuang/hmcl/task/FileDownloadTask.java | 4 +- gradlew | 4 +- gradlew.bat | 4 +- 39 files changed, 617 insertions(+), 117 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLDependencyManager.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameBuilder.java create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameDownloadTask.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MismatchedModpackTypeException.java create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/mod/UnsupportedModpackException.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLDependencyManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLDependencyManager.java new file mode 100644 index 000000000..62f9281b9 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLDependencyManager.java @@ -0,0 +1,47 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.DownloadProvider; +import org.jackhuang.hmcl.download.GameBuilder; +import org.jackhuang.hmcl.setting.Profile; + +import java.net.Proxy; + +/** + * @author huangyuhui + */ +public class HMCLDependencyManager extends DefaultDependencyManager { + private final Profile profile; + + public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider) { + this(profile, downloadProvider, Proxy.NO_PROXY); + } + + public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider, Proxy proxy) { + super(profile.getRepository(), downloadProvider, proxy); + + this.profile = profile; + } + + @Override + public GameBuilder gameBuilder() { + return new HMCLGameBuilder(profile); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameBuilder.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameBuilder.java new file mode 100644 index 000000000..b0ecd61ba --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameBuilder.java @@ -0,0 +1,41 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.DefaultGameBuilder; +import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.task.Task; + +/** + * @author huangyuhui + */ +public class HMCLGameBuilder extends DefaultGameBuilder { + private final Profile profile; + + public HMCLGameBuilder(Profile profile) { + super(profile.getDependency()); + + this.profile = profile; + } + + @Override + protected Task downloadGameAsync(String gameVersion, Version version) { + return new HMCLGameDownloadTask(profile, gameVersion, version); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameDownloadTask.java new file mode 100644 index 000000000..7e93a6609 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameDownloadTask.java @@ -0,0 +1,78 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.game; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.setting.Settings; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.FileUtils; +import org.jackhuang.hmcl.util.Logging; +import org.jackhuang.hmcl.util.NetworkUtils; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; + +/** + * @author huangyuhui + */ +public class HMCLGameDownloadTask extends Task { + private final Profile profile; + private final String gameVersion; + private final Version version; + private final List dependencies = new LinkedList<>(); + + public HMCLGameDownloadTask(Profile profile, String gameVersion, Version version) { + this.profile = profile; + this.gameVersion = gameVersion; + this.version = version; + + setSignificance(TaskSignificance.MINOR); + } + + @Override + public List getDependencies() { + return dependencies; + } + + @Override + public void execute() throws IOException { + File jar = profile.getRepository().getVersionJar(version); + + File cache = new File(Settings.INSTANCE.getCommonPath(), "jars/" + gameVersion + ".jar"); + if (cache.exists()) + try { + FileUtils.copyFile(cache, jar); + return; + } catch (IOException e) { + Logging.LOG.log(Level.SEVERE, "Unable to copy cached Minecraft jar from " + cache + " to " + jar, e); + } + + dependencies.add(new FileDownloadTask( + NetworkUtils.toURL(profile.getDependency().getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())), + jar, + profile.getDependency().getProxy(), + version.getDownloadInfo().getSha1() + )); + } + +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index 3917d6f64..c4610753f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -33,10 +33,7 @@ import org.jackhuang.hmcl.util.Logging; import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.logging.Level; public class HMCLGameRepository extends DefaultGameRepository { @@ -192,6 +189,15 @@ public class HMCLGameRepository extends DefaultGameRepository { Lang.invoke(() -> FileUtils.writeText(getVersionSettingFile(id), GSON.toJson(versionSettings.get(id)))); } + public boolean forbidsVersion(String id) { + return FORBIDDEN.contains(id); + } + + @Override + public File getModpackConfiguration(String version) { + return new File(getRunDirectory(version), "modpack.cfg"); + } + public void markVersionAsModpack(String id) { beingModpackVersions.add(id); } @@ -204,5 +210,7 @@ public class HMCLGameRepository extends DefaultGameRepository { .registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE) .create(); + private static final HashSet FORBIDDEN = new HashSet<>(Arrays.asList("modpack", "minecraftinstance", "manifest")); + private static final String PROFILE = "{\"selectedProfile\": \"(Default)\",\"profiles\": {\"(Default)\": {\"name\": \"(Default)\"}},\"clientToken\": \"88888888-8888-8888-8888-888888888888\"}"; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java index e15c36355..1d766e126 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackInstallTask.java @@ -52,7 +52,7 @@ public final class HMCLModpackInstallTask extends Task { this.modpack = modpack; this.run = repository.getRunDirectory(name); - File json = new File(run, "modpack.json"); + File json = repository.getModpackConfiguration(name); if (repository.hasVersion(name) && !json.exists()) throw new IllegalArgumentException("Version " + name + " already exists"); @@ -64,8 +64,13 @@ public final class HMCLModpackInstallTask extends Task { ModpackConfiguration config = null; try { - if (json.exists()) - config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken>(){}.getType()); + if (json.exists()) { + config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { + }.getType()); + + if (!MODPACK_TYPE.equals(config.getType())) + throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version."); + } } catch (JsonParseException | IOException ignore) { } dependents.add(new ModpackInstallTask<>(zipFile, run, "minecraft/", it -> !Objects.equals(it, "minecraft/pack.json"), config)); @@ -86,6 +91,8 @@ public final class HMCLModpackInstallTask extends Task { String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json"); Version version = Constants.GSON.fromJson(json, Version.class).setJar(null); dependencies.add(new VersionJsonSaveTask(repository, version)); - dependencies.add(new MinecraftInstanceTask<>(zipFile, "minecraft/", modpack, new File(run, "modpack.json"))); + dependencies.add(new MinecraftInstanceTask<>(zipFile, "minecraft/", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name))); } + + public static final String MODPACK_TYPE = "HMCL"; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java index dd8864856..393892415 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/ModpackHelper.java @@ -17,20 +17,26 @@ */ package org.jackhuang.hmcl.game; -import org.jackhuang.hmcl.mod.CurseManifest; -import org.jackhuang.hmcl.mod.Modpack; -import org.jackhuang.hmcl.mod.MultiMCInstanceConfiguration; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.mod.*; import org.jackhuang.hmcl.setting.EnumGameDirectory; +import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.VersionSetting; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.FileUtils; import org.jackhuang.hmcl.util.Lang; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; import java.util.Optional; public final class ModpackHelper { private ModpackHelper() {} - public static Modpack readModpackManifest(File file) { + public static Modpack readModpackManifest(File file) throws UnsupportedModpackException { try { return CurseManifest.readCurseForgeModpackManifest(file); } catch (Exception e) { @@ -49,7 +55,54 @@ public final class ModpackHelper { // ignore it, not a valid MultiMC modpack. } - throw new IllegalArgumentException("Modpack file " + file + " is not supported."); + throw new UnsupportedModpackException(file.toString()); + } + + public static ModpackConfiguration readModpackConfiguration(File file) throws IOException { + if (!file.exists()) + throw new FileNotFoundException(file.getPath()); + else + try { + return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken>() { + }.getType()); + } catch (JsonParseException e) { + throw new IOException("Malformed modpack configuration"); + } + } + + private static String getManifestType(Object manifest) throws UnsupportedModpackException { + if (manifest instanceof HMCLModpackManifest) + return HMCLModpackInstallTask.MODPACK_TYPE; + else if (manifest instanceof MultiMCInstanceConfiguration) + return MultiMCModpackInstallTask.MODPACK_TYPE; + else if (manifest instanceof CurseManifest) + return CurseInstallTask.MODPACK_TYPE; + else + throw new UnsupportedModpackException(); + } + + public static Task getUpdateTask(Profile profile, File zipFile, String name, ModpackConfiguration configuration) throws UnsupportedModpackException, MismatchedModpackTypeException, IOException { + Modpack modpack = ModpackHelper.readModpackManifest(zipFile); + + switch (configuration.getType()) { + case CurseInstallTask.MODPACK_TYPE: + if (!(modpack.getManifest() instanceof CurseManifest)) + throw new MismatchedModpackTypeException(CurseInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest())); + + return new CurseInstallTask(profile.getDependency(), zipFile, (CurseManifest) modpack.getManifest(), name); + case MultiMCModpackInstallTask.MODPACK_TYPE: + if (!(modpack.getManifest() instanceof MultiMCInstanceConfiguration)) + throw new MismatchedModpackTypeException(MultiMCModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest())); + + return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, (MultiMCInstanceConfiguration) modpack.getManifest(), name); + case HMCLModpackInstallTask.MODPACK_TYPE: + if (!(modpack.getManifest() instanceof HMCLModpackManifest)) + throw new MismatchedModpackTypeException(HMCLModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest())); + + return new HMCLModpackInstallTask(profile, zipFile, modpack, name); + default: + throw new UnsupportedModpackException(); + } } public static void toVersionSetting(MultiMCInstanceConfiguration c, VersionSetting vs) { @@ -88,4 +141,6 @@ public final class ModpackHelper { vs.setHeight(c.getHeight()); } } + + } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/MultiMCInstallVersionSettingTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/MultiMCInstallVersionSettingTask.java index 4d48a6e4a..240873f24 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/MultiMCInstallVersionSettingTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/MultiMCInstallVersionSettingTask.java @@ -24,6 +24,8 @@ 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 { private final Profile profile; private final MultiMCInstanceConfiguration manifest; @@ -42,7 +44,8 @@ public final class MultiMCInstallVersionSettingTask extends Task { @Override public void execute() { - VersionSetting vs = profile.specializeVersionSetting(version); + profile.getRepository().refreshVersions(); + VersionSetting vs = Objects.requireNonNull(profile.specializeVersionSetting(version)); ModpackHelper.toVersionSetting(manifest, vs); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java index f48107f46..7304c418f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Profile.java @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.setting; import com.google.gson.*; import javafx.beans.InvalidationListener; import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.game.HMCLDependencyManager; import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.mod.ModManager; import org.jackhuang.hmcl.util.ImmediateObjectProperty; @@ -105,8 +106,8 @@ public final class Profile { return modManager; } - public DefaultDependencyManager getDependency() { - return new DefaultDependencyManager(repository, Settings.INSTANCE.getDownloadProvider(), Settings.INSTANCE.getProxy()); + public HMCLDependencyManager getDependency() { + return new HMCLDependencyManager(this, Settings.INSTANCE.getDownloadProvider(), Settings.INSTANCE.getProxy()); } public VersionSetting getVersionSetting(String id) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index ec54448df..d36f4d3b6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -27,6 +27,7 @@ import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.construct.InputDialogPane; +import org.jackhuang.hmcl.ui.construct.MessageBox; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.util.JavaVersion; @@ -112,11 +113,19 @@ public final class Controllers { } public static void dialog(String text, String title) { - dialog(text, title, null); + dialog(text, title, MessageBox.INFORMATION_MESSAGE); } - public static void dialog(String text, String title, Runnable onAccept) { - dialog(new MessageDialogPane(text, title, decorator.getDialog(), onAccept)); + public static void dialog(String text, String title, int type) { + dialog(text, title, type, null); + } + + public static void dialog(String text, String title, int type, Runnable onAccept) { + dialog(new MessageDialogPane(text, title, decorator.getDialog(), MessageBox.INFORMATION_MESSAGE, onAccept)); + } + + public static void confirmDialog(String text, String title, Runnable onAccept, Runnable onCacnel) { + dialog(new MessageDialogPane(text, title, decorator.getDialog(), onAccept, onCacnel)); } public static void inputDialog(String text, Consumer onResult) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java index cc47c138d..a8324cc07 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/MainPage.java @@ -35,15 +35,25 @@ import org.jackhuang.hmcl.event.ProfileLoadingEvent; import org.jackhuang.hmcl.event.RefreshedVersionsEvent; import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.game.LauncherHelper; +import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.game.Version; +import org.jackhuang.hmcl.mod.MismatchedModpackTypeException; +import org.jackhuang.hmcl.mod.UnsupportedModpackException; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Settings; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.ui.construct.MessageBox; +import org.jackhuang.hmcl.ui.construct.MessageDialogPane; +import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane; import org.jackhuang.hmcl.ui.download.DownloadWizardProvider; import org.jackhuang.hmcl.ui.wizard.DecoratorPage; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.OperatingSystem; import java.io.File; +import java.io.IOException; import java.util.LinkedList; import java.util.List; @@ -75,6 +85,7 @@ public final class MainPage extends StackPane implements DecoratorPage { private Node buildNode(Profile profile, String version, String game) { VersionItem item = new VersionItem(); + item.setUpdate(profile.getRepository().isModpack(version)); item.setGameVersion(game); item.setVersionName(version); item.setOnLaunchButtonClicked(e -> { @@ -102,6 +113,29 @@ public final class MainPage extends StackPane implements DecoratorPage { Controllers.getDecorator().showPage(Controllers.getVersionPage()); Controllers.getVersionPage().load(version, profile); }); + item.setOnUpdateButtonClicked(event -> { + FileChooser chooser = new FileChooser(); + chooser.setTitle(Main.i18n("modpack.choose")); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("modpack"), "*.zip")); + File selectedFile = chooser.showOpenDialog(Controllers.getStage()); + if (selectedFile != null) { + TaskExecutorDialogPane pane = new TaskExecutorDialogPane(null); + try { + TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, version, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(version))) + .then(Task.of(Schedulers.javafx(), Controllers::closeDialog)).executor(); + pane.setExecutor(executor); + pane.setCurrentState(Main.i18n("modpack.update")); + executor.start(); + Controllers.dialog(pane); + } catch (UnsupportedModpackException e) { + Controllers.dialog(Main.i18n("modpack.unsupported"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE); + } catch (MismatchedModpackTypeException e) { + Controllers.dialog(Main.i18n("modpack.mismatched_type"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE); + } catch (IOException e) { + Controllers.dialog(Main.i18n("modpack.invalid"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE); + } + } + }); File iconFile = profile.getRepository().getVersionIcon(version); if (iconFile.exists()) item.setImage(new Image("file:" + iconFile.getAbsolutePath())); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index 20465e770..8646bb551 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -97,4 +97,28 @@ public final class SVG { return createSVGPath("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z", fill, width, height); } + public static Node update(String fill, double width, double height) { + return createSVGPath("M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z", fill, width, height); + } + + public static Node close_circle(String fill, double width, double height) { + return createSVGPath("M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z", fill, width, height); + } + + public static Node check_circle(String fill, double width, double height) { + return createSVGPath("M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z", fill, width, height); + } + + public static Node info_circle(String fill, double width, double height) { + return createSVGPath("M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z", fill, width, height); + } + + public static Node help_circle(String fill, double width, double height) { + return createSVGPath("M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z", fill, width, height); + } + + public static Node alert(String fill, double width, double height) { + return createSVGPath("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z", fill, width, height); + } + } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java index 1ad5262c7..9abe4d49c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionItem.java @@ -49,6 +49,8 @@ public final class VersionItem extends StackPane { @FXML private JFXButton btnSettings; @FXML + private JFXButton btnUpdate; + @FXML private JFXButton btnLaunch; @FXML private JFXButton btnScript; @@ -67,10 +69,12 @@ public final class VersionItem extends StackPane { FXUtils.limitHeight(this, 156); setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0)); btnSettings.setGraphic(SVG.gear("black", 15, 15)); + btnUpdate.setGraphic(SVG.update("black", 15, 15)); btnLaunch.setGraphic(SVG.launch("black", 15, 15)); btnScript.setGraphic(SVG.script("black", 15, 15)); - FXUtils.installTooltip(btnSettings, 0, 5000, 0, new Tooltip(Main.i18n("version.manage.settings"))); + FXUtils.installTooltip(btnSettings, 0, 5000, 0, new Tooltip(Main.i18n("version.settings"))); + FXUtils.installTooltip(btnUpdate, 0, 5000, 0, new Tooltip(Main.i18n("version.update"))); FXUtils.installTooltip(btnLaunch, 0, 5000, 0, new Tooltip(Main.i18n("version.launch"))); FXUtils.installTooltip(btnScript, 0, 5000, 0, new Tooltip(Main.i18n("version.launch_script"))); @@ -84,6 +88,10 @@ public final class VersionItem extends StackPane { }); } + public void setUpdate(boolean update) { + btnUpdate.setVisible(update); + } + public void setVersionName(String versionName) { lblVersionName.setText(versionName); } @@ -108,4 +116,8 @@ public final class VersionItem extends StackPane { launchClickedHandler = handler; btnLaunch.setOnMouseClicked(handler); } + + public void setOnUpdateButtonClicked(EventHandler handler) { + btnUpdate.setOnMouseClicked(handler); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java index 9ccc55bf4..633360722 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/VersionPage.java @@ -112,12 +112,12 @@ public final class VersionPage extends StackPane implements DecoratorPage { } public void onDelete() { - if (FXUtils.alert(Alert.AlertType.CONFIRMATION, "Confirm", Main.i18n("version.manage.remove.confirm") + version)) { + Controllers.confirmDialog(Main.i18n("version.manage.remove.confirm", version), Main.i18n("message.confirm"), () -> { if (profile.getRepository().removeVersionFromDisk(version)) { profile.getRepository().refreshVersionsAsync().start(); Controllers.navigate(null); } - } + }, null); } public void onExport() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageBox.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageBox.java index 0eefed8f8..7f6a4023d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageBox.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageBox.java @@ -85,6 +85,10 @@ public final class MessageBox { * Message Box Type: Question */ public static final int QUESTION_MESSAGE = 3; + /** + * Message Box Type: Fine + */ + public static final int FINE_MESSAGE = 4; /** * Message Box Type: Plain */ diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java index 081c638f3..c59e9c06b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MessageDialogPane.java @@ -1,6 +1,6 @@ /* * Hello Minecraft! Launcher. - * Copyright (C) 2017 huangyuhui + * Copyright (C) 4017 huangyuhui * * 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 @@ -21,8 +21,11 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialog; import javafx.fxml.FXML; import javafx.scene.control.Label; +import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; +import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.SVG; import java.util.Optional; @@ -33,11 +36,17 @@ public final class MessageDialogPane extends StackPane { @FXML private JFXButton acceptButton; @FXML + private JFXButton cancelButton; + @FXML private Label content; @FXML + private Label graphic; + @FXML private Label title; + @FXML + private HBox actions; - public MessageDialogPane(String text, String title, JFXDialog dialog, Runnable onAccept) { + public MessageDialogPane(String text, String title, JFXDialog dialog, int type, Runnable onAccept) { this.text = text; this.dialog = dialog; @@ -51,5 +60,39 @@ public final class MessageDialogPane extends StackPane { dialog.close(); Optional.ofNullable(onAccept).ifPresent(Runnable::run); }); + + actions.getChildren().remove(cancelButton); + + switch (type) { + case MessageBox.INFORMATION_MESSAGE: + graphic.setGraphic(SVG.info_circle("black", 40, 40)); + break; + case MessageBox.ERROR_MESSAGE: + graphic.setGraphic(SVG.close_circle("black", 40, 40)); + break; + case MessageBox.FINE_MESSAGE: + graphic.setGraphic(SVG.check_circle("black", 40, 40)); + break; + case MessageBox.WARNING_MESSAGE: + graphic.setGraphic(SVG.alert("black", 40, 40)); + break; + } + } + + public MessageDialogPane(String text, String title, JFXDialog dialog, Runnable onAccept, Runnable onCancel) { + this(text, title, dialog, -1, onAccept); + + cancelButton.setVisible(true); + cancelButton.setOnMouseClicked(e -> { + dialog.close(); + Optional.ofNullable(onCancel).ifPresent(Runnable::run); + }); + + acceptButton.setText(Main.i18n("button.yes")); + cancelButton.setText(Main.i18n("button.no")); + + actions.getChildren().add(cancelButton); + + graphic.setGraphic(SVG.help_circle("black", 40, 40)); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index 5d09f599f..88c6b6973 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -50,6 +50,9 @@ public class TaskExecutorDialogPane extends StackPane { FXUtils.limitHeight(this, 200); FXUtils.limitWidth(this, 400); + if (cancel == null) + btnCancel.setDisable(true); + btnCancel.setOnMouseClicked(e -> { Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel); cancel.run(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogWizardDisplayer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogWizardDisplayer.java index 8fc9f3acc..4d28fb2fc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogWizardDisplayer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogWizardDisplayer.java @@ -62,18 +62,18 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay @Override public void onSucceed() { if (settings.containsKey("success_message") && settings.get("success_message") instanceof String) - JFXUtilities.runInFX(() -> Controllers.dialog((String) settings.get("success_message"), null, () -> Controllers.navigate(null))); + JFXUtilities.runInFX(() -> Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null))); else if (!settings.containsKey("forbid_success_message")) - JFXUtilities.runInFX(() -> Controllers.dialog(Main.i18n("message.success"), null, () -> Controllers.navigate(null))); + JFXUtilities.runInFX(() -> Controllers.dialog(Main.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null))); } @Override public void onTerminate() { String appendix = StringUtils.getStackTrace(e.getLastException()); if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String) - JFXUtilities.runInFX(() -> Controllers.dialog(appendix, (String) settings.get("failure_message"), () -> Controllers.navigate(null))); + JFXUtilities.runInFX(() -> Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null))); else if (!settings.containsKey("forbid_failure_message")) - JFXUtilities.runInFX(() -> Controllers.dialog(appendix, Main.i18n("wizard.failed"), () -> Controllers.navigate(null))); + JFXUtilities.runInFX(() -> Controllers.dialog(appendix, Main.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null))); } }); pane.setExecutor(executor); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java index 4db86f333..69246ba57 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskListPane.java @@ -83,9 +83,9 @@ public final class TaskListPane extends StackPane { } else if (task instanceof CurseInstallTask) { task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.curse"))); } else if (task instanceof MultiMCModpackInstallTask) { - task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.curse"))); + task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.multimc"))); } else if (task instanceof HMCLModpackInstallTask) { - task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.curse"))); + task.setName(Main.i18n("modpack.install", Main.i18n("modpack.type.hmcl"))); } else if (task instanceof HMCLModpackExportTask) { task.setName(Main.i18n("modpack.export")); } else if (task instanceof MinecraftInstanceTask) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java index fdc4053cd..ba7c45c67 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackPage.java @@ -82,7 +82,10 @@ public final class ModpackPage extends StackPane implements WizardPage { // TODO: original HMCL modpack support. controller.getSettings().put(MODPACK_FILE, selectedFile); lblModpackLocation.setText(selectedFile.getAbsolutePath()); - txtModpackName.getValidators().add(new Validator(Main.i18n("version.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str))); + txtModpackName.getValidators().addAll( + new Validator(Main.i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)), + new Validator(Main.i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str)) + ); txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate())); try { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java index a0b763c70..5fdb9c969 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/export/ExportWizardProvider.java @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.export; import javafx.scene.Node; import org.jackhuang.hmcl.game.HMCLModpackExportTask; +import org.jackhuang.hmcl.game.HMCLModpackManager; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.ui.wizard.WizardController; @@ -28,8 +29,6 @@ import java.io.File; import java.util.List; import java.util.Map; -import static org.jackhuang.hmcl.game.HMCLModpackManager.MODPACK_PREDICATE; - public final class ExportWizardProvider implements WizardProvider { private final Profile profile; private final String version; @@ -60,7 +59,7 @@ public final class ExportWizardProvider implements WizardProvider { public Node createPage(WizardController controller, int step, Map settings) { switch (step) { case 0: return new ModpackInfoPage(controller, version); - case 1: return new ModpackFileSelectionPage(controller, profile, version, MODPACK_PREDICATE); + case 1: return new ModpackFileSelectionPage(controller, profile, version, HMCLModpackManager::suggestMod); default: throw new IllegalArgumentException("step"); } } diff --git a/HMCL/src/main/resources/assets/fxml/download/modpack.fxml b/HMCL/src/main/resources/assets/fxml/download/modpack.fxml index 88d310314..433ea3e24 100644 --- a/HMCL/src/main/resources/assets/fxml/download/modpack.fxml +++ b/HMCL/src/main/resources/assets/fxml/download/modpack.fxml @@ -19,7 +19,7 @@ - + diff --git a/HMCL/src/main/resources/assets/fxml/message-dialog.fxml b/HMCL/src/main/resources/assets/fxml/message-dialog.fxml index f5831e21e..9434049bb 100644 --- a/HMCL/src/main/resources/assets/fxml/message-dialog.fxml +++ b/HMCL/src/main/resources/assets/fxml/message-dialog.fxml @@ -4,18 +4,25 @@ + - - - - - + + diff --git a/HMCL/src/main/resources/assets/fxml/version-item.fxml b/HMCL/src/main/resources/assets/fxml/version-item.fxml index 4f810c4a3..1db00dd31 100644 --- a/HMCL/src/main/resources/assets/fxml/version-item.fxml +++ b/HMCL/src/main/resources/assets/fxml/version-item.fxml @@ -30,6 +30,7 @@ + diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index ac799eba1..ed490171c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -110,7 +110,7 @@ profile.new=New Config button.refresh=Refresh version.launch=Play -version.manage.settings=Settings +version.settings=Settings button.about=About button.others=Others button.download=Download @@ -211,7 +211,7 @@ modpack.wizard.step.1=Basic options modpack.wizard.step.1.title=Set the basic options to the modpack. modpack.wizard.step.initialization.include_launcher=Include the launcher modpack.wizard.step.initialization.exported_version=The exported game version -modpack.wizard.step.initialization.save=Choose a location to export the game files to +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.2=Files selection modpack.wizard.step.2.title=Choose the files you want to put into the modpack. @@ -219,10 +219,8 @@ modpack.wizard.step.3=Description modpack.wizard.step.3.title=Describe your modpack. modpack.desc=Describe your modpack, including precautions, changlog, supporting Markdown(also supporting online pictures). -modpack.incorrect_format.no_json=The format of the modpack is incorrect, pack.json is missing. -modpack.incorrect_format.no_jar=The format of the modpack is incorrect, pack.json does not have attribute 'jar' modpack.cannot_read_version=Failed to gather the game version -modpack.not_a_valid_location=Not a valid modpack location +modpack.not_a_valid_location=Not a valid modpack path modpack.name=Modpack Name modpack.not_a_valid_name=Not a valid modpack name @@ -308,7 +306,7 @@ version.game.old_alpha=Old Alpha version.manage.rename=Rename this version version.manage.rename.message=Please enter the new name version.manage.remove=Delete this version -version.manage.remove.confirm=Sure to remove the version +version.manage.remove.confirm=Sure to remove version %s? version.manage.redownload_json=Redownload Minecraft Configuration(minecraft.json) version.manage.redownload_assets_index=Redownload Assets Index version.manage.remove_libraries=Delete library files @@ -413,3 +411,10 @@ extension.png=Image file message.success=Tasks succeeded message.doing=Please wait modpack.scan=Scanning this modpack +version.update=Update +modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, Curse modpacks are supported. +modpack.mismatched_type=Mismatched modpack type, your current game is %s modpack, but your update file is %s modpack. +modpack.invalid=Invalid modpack file. +modpack.update=Upgrading game +message.confirm=Confirm +version.forbidden_name=Forbidden name, do not use this. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 1da8044e7..ede334374 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -110,7 +110,7 @@ profile.new=新建配置 button.refresh=刷新 version.launch=启动游戏 -version.manage.settings=游戏设置 +version.settings=游戏设置 button.about=关于 button.others=其他 button.download=下载 @@ -219,8 +219,6 @@ modpack.wizard.step.3=整合包描述 modpack.wizard.step.3.title=描述你要制作的整合包 modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持Markdown(图片请用网络图片)。 -modpack.incorrect_format.no_json=整合包格式错误,pack.json丢失 -modpack.incorrect_format.no_jar=整合包格式错误,pack.json丢失jar字段 modpack.cannot_read_version=读取游戏版本失败 modpack.not_a_valid_location=不是一个有效整合包位置 modpack.name=整合包名称 @@ -308,7 +306,7 @@ version.game.old_alpha=远古版 version.manage.rename=重命名该版本 version.manage.rename.message=请输入要改成的名字 version.manage.remove=删除该版本 -version.manage.remove.confirm=真的要删除版本 +version.manage.remove.confirm=真的要删除版本 %s 吗? version.manage.redownload_json=重新下载版本配置(minecraft.json) version.manage.redownload_assets_index=重新下载资源配置(assets_index.json) version.manage.remove_libraries=删除所有库文件 @@ -413,3 +411,10 @@ extension.png=图片文件 message.success=已完成 message.doing=请耐心等待 modpack.scan=解析整合包 +version.update=更新 +modpack.unsupported=该整合包不被支持。仅HMCL、MultiMC、Curse整合包受支持。 +modpack.mismatched_type=整合包类型不匹配,当前游戏是 %s 整合包,但是提供的整合包更新文件是 %s 整合包。 +modpack.invalid=无效的整合包升级文件,可能是下载时出现问题。 +modpack.update=正在升级整合包 +message.confirm=提示 +version.forbidden_name=此版本名称不受支持,请换一个名字 diff --git a/HMCLCore/gradlew b/HMCLCore/gradlew index d9116bf59..55c63b05a 100644 --- a/HMCLCore/gradlew +++ b/HMCLCore/gradlew @@ -78,14 +78,14 @@ if [ -n "$JAVA_HOME" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." +path of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." +path of your Java installation." fi # Increase the maximum file descriptors if we can. diff --git a/HMCLCore/gradlew.bat b/HMCLCore/gradlew.bat index f74b6cd90..986dbdbd3 100644 --- a/HMCLCore/gradlew.bat +++ b/HMCLCore/gradlew.bat @@ -27,7 +27,7 @@ echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo path of your Java installation. goto fail @@ -41,7 +41,7 @@ echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo path of your Java installation. goto fail diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java index 4b9dc67d6..c2a36c219 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DefaultGameBuilder.java @@ -41,6 +41,14 @@ public class DefaultGameBuilder extends GameBuilder { this.downloadProvider = dependencyManager.getDownloadProvider(); } + public DefaultDependencyManager getDependencyManager() { + return dependencyManager; + } + + public DownloadProvider getDownloadProvider() { + return downloadProvider; + } + @Override public Task buildAsync() { return new VersionJsonDownloadTask(gameVersion, dependencyManager).then(variables -> { @@ -50,7 +58,7 @@ public class DefaultGameBuilder extends GameBuilder { Task result = new ParallelTask( new GameAssetDownloadTask(dependencyManager, version), new GameLoggingDownloadTask(dependencyManager, version), - new GameDownloadTask(dependencyManager, version), + downloadGameAsync(gameVersion, version), 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. @@ -68,4 +76,8 @@ public class DefaultGameBuilder extends GameBuilder { return variables -> dependencyManager.installLibraryAsync(gameVersion, variables.get("version"), libraryId, toolVersions.get(libraryId)); } + protected Task downloadGameAsync(String gameVersion, Version version) { + return new GameDownloadTask(dependencyManager, version); + } + } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java index 7cea4e497..c587a6dbb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameRepository.java @@ -310,4 +310,12 @@ public class DefaultGameRepository implements GameRepository { return loaded; } + public File getModpackConfiguration(String version) { + return new File(getRunDirectory(version), "modpack.json"); + } + + public boolean isModpack(String version) { + return getModpackConfiguration(version).exists(); + } + } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseInstallTask.java index 71a62e4b5..3f536725f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseInstallTask.java @@ -23,7 +23,6 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.GameBuilder; import org.jackhuang.hmcl.game.DefaultGameRepository; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.util.CompressingUtils; import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.FileUtils; @@ -32,7 +31,6 @@ import java.io.IOException; import java.util.Collection; import java.util.LinkedList; import java.util.List; -import java.util.function.Predicate; /** * Install a downloaded CurseForge modpack. @@ -68,7 +66,7 @@ public final class CurseInstallTask extends Task { this.repository = dependencyManager.getGameRepository(); this.run = repository.getRunDirectory(name); - File json = new File(run, "modpack.json"); + File json = repository.getModpackConfiguration(name); if (repository.hasVersion(name) && !json.exists()) throw new IllegalArgumentException("Version " + name + " already exists."); @@ -80,8 +78,13 @@ public final class CurseInstallTask extends Task { ModpackConfiguration config = null; try { - if (json.exists()) - config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken>(){}.getType()); + if (json.exists()) { + config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { + }.getType()); + + if (!MODPACK_TYPE.equals(config.getType())) + throw new IllegalArgumentException("Version " + name + " is not a Curse modpack. Cannot update this version."); + } } catch (JsonParseException | IOException ignore) { } this.config = config; @@ -110,7 +113,8 @@ public final class CurseInstallTask extends Task { } dependencies.add(new CurseCompletionTask(dependencyManager, name)); - dependencies.add(new MinecraftInstanceTask<>(zipFile, manifest.getOverrides(), false, manifest, new File(run, "modpack.json"))); + dependencies.add(new MinecraftInstanceTask<>(zipFile, manifest.getOverrides(), manifest, MODPACK_TYPE, repository.getModpackConfiguration(name))); } + public static final String MODPACK_TYPE = "Curse"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java index 7a4b72cfa..a10e768d5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MinecraftInstanceTask.java @@ -23,11 +23,12 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.FileUtils; -import org.jackhuang.hmcl.util.IOUtils; import java.io.File; import java.io.FileInputStream; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; public final class MinecraftInstanceTask extends Task { @@ -36,12 +37,14 @@ public final class MinecraftInstanceTask extends Task { private final String subDirectory; private final File jsonFile; private final T manifest; + private final String type; - public MinecraftInstanceTask(File zipFile, String subDirectory, T manifest, File jsonFile) { + public MinecraftInstanceTask(File zipFile, String subDirectory, T manifest, String type, File jsonFile) { this.zipFile = zipFile; this.subDirectory = subDirectory; this.manifest = manifest; this.jsonFile = jsonFile; + this.type = type; if (!zipFile.exists()) throw new IllegalArgumentException("File " + zipFile + " does not exist. Cannot parse this modpack."); @@ -49,9 +52,8 @@ public final class MinecraftInstanceTask extends Task { @Override public void execute() throws Exception { - Map overrides = new HashMap<>(); + List overrides = new LinkedList<>(); - byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; try (ZipArchiveInputStream zip = new ZipArchiveInputStream(new FileInputStream(zipFile), null, true, true)) { ArchiveEntry entry; while ((entry = zip.getNextEntry()) != null) { @@ -62,12 +64,10 @@ public final class MinecraftInstanceTask extends Task { if (path.startsWith("/") || path.startsWith("\\")) path = path.substring(1); - overrides.put(path, new ModpackConfiguration.FileInformation( - path, DigestUtils.sha1Hex(zip) - )); + overrides.add(new ModpackConfiguration.FileInformation(path, DigestUtils.sha1Hex(zip))); } } - FileUtils.writeText(jsonFile, Constants.GSON.toJson(new ModpackConfiguration<>(manifest, overrides))); + FileUtils.writeText(jsonFile, Constants.GSON.toJson(new ModpackConfiguration<>(manifest, type, overrides))); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MismatchedModpackTypeException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MismatchedModpackTypeException.java new file mode 100644 index 000000000..c2942b1be --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MismatchedModpackTypeException.java @@ -0,0 +1,30 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +public class MismatchedModpackTypeException extends Exception { + private final String required; + private final String found; + + public MismatchedModpackTypeException(String required, String found) { + super("Required " + required + ", but found " + found); + + this.required = required; + this.found = found; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java index 520d0f00a..9141dabb9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackConfiguration.java @@ -21,50 +21,56 @@ import com.google.gson.JsonParseException; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Validation; -import java.util.Collections; -import java.util.Map; +import java.util.*; @Immutable public final class ModpackConfiguration implements Validation { private final T manifest; - - private final Map overrides; + private final String type; + private final List overrides; public ModpackConfiguration() { - this(null, Collections.emptyMap()); + this(null, null, Collections.emptyList()); } - public ModpackConfiguration(T manifest, Map overrides) { + public ModpackConfiguration(T manifest, String type, List overrides) { this.manifest = manifest; - this.overrides = overrides; + this.type = type; + this.overrides = new ArrayList<>(overrides); } public T getManifest() { return manifest; } + public String getType() { + return type; + } + public ModpackConfiguration setManifest(T manifest) { - return new ModpackConfiguration<>(manifest, overrides); + return new ModpackConfiguration<>(manifest, type, overrides); } - public ModpackConfiguration setOverrides(Map overrides) { - return new ModpackConfiguration<>(manifest, overrides); + public ModpackConfiguration setOverrides(List overrides) { + return new ModpackConfiguration<>(manifest, type, overrides); } - public Map getOverrides() { - return Collections.unmodifiableMap(overrides); + public List getOverrides() { + return Collections.unmodifiableList(overrides); } @Override public void validate() throws JsonParseException { if (manifest == null) throw new JsonParseException("MinecraftInstanceConfiguration missing `manifest`"); + if (type == null) + throw new JsonParseException("MinecraftInstanceConfiguration missing `type`"); } @Immutable public static class FileInformation implements Validation { - private final String location; // relative + private final String path; // relative private final String hash; private final String downloadURL; @@ -72,18 +78,22 @@ public final class ModpackConfiguration implements Validation { this(null, null); } - public FileInformation(String location, String hash) { - this(location, hash, null); + public FileInformation(String path, String hash) { + this(path, hash, null); } - public FileInformation(String location, String hash, String downloadURL) { - this.location = location; + public FileInformation(String path, String hash, String downloadURL) { + this.path = path; this.hash = hash; this.downloadURL = downloadURL; } - public String getLocation() { - return location; + /** + * The relative path to Minecraft run directory + * @return + */ + public String getPath() { + return path; } public String getDownloadURL() { @@ -96,8 +106,8 @@ public final class ModpackConfiguration implements Validation { @Override public void validate() throws JsonParseException { - if (location == null) - throw new JsonParseException("FileInformation missing `location`."); + if (path == null) + throw new JsonParseException("FileInformation missing `path`."); if (hash == null) throw new JsonParseException("FileInformation missing file hash code."); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackInstallTask.java index 6e515811d..24350edb7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModpackInstallTask.java @@ -25,10 +25,7 @@ import org.jackhuang.hmcl.util.FileUtils; import org.jackhuang.hmcl.util.IOUtils; import java.io.*; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; public class ModpackInstallTask extends Task { @@ -36,7 +33,7 @@ public class ModpackInstallTask extends Task { private final File modpackFile; private final File dest; private final String subDirectory; - private final Map overrides; + private final List overrides; private final Predicate callback; public ModpackInstallTask(File modpackFile, File dest, String subDirectory, Predicate callback, ModpackConfiguration oldConfiguration) { @@ -46,7 +43,7 @@ public class ModpackInstallTask extends Task { this.callback = callback; if (oldConfiguration == null) - overrides = Collections.emptyMap(); + overrides = Collections.emptyList(); else overrides = oldConfiguration.getOverrides(); } @@ -57,6 +54,11 @@ public class ModpackInstallTask extends Task { byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE]; if (!FileUtils.makeDirectory(dest)) throw new IOException("Unable to make directory " + dest); + + HashSet files = new HashSet<>(); + for (ModpackConfiguration.FileInformation file : overrides) + files.add(file.getPath()); + try (ZipArchiveInputStream zipStream = new ZipArchiveInputStream(new FileInputStream(modpackFile), null, true, true)) { ArchiveEntry entry; while ((entry = zipStream.getNextEntry()) != null) { @@ -86,7 +88,7 @@ public class ModpackInstallTask extends Task { IOUtils.copyTo(zipStream, os, buf); byte[] data = os.toByteArray(); - if (!overrides.containsKey(path) || entryFile.exists()) { + if (files.contains(path) && entryFile.exists()) { String oldHash = DigestUtils.sha1Hex(new FileInputStream(entryFile)); String newHash = DigestUtils.sha1Hex(new ByteArrayInputStream(data)); if (!oldHash.equals(newHash)) { @@ -94,15 +96,19 @@ public class ModpackInstallTask extends Task { IOUtils.copyTo(new ByteArrayInputStream(data), fos, buf); } } + } else if (!files.contains(path)) { + try (FileOutputStream fos = new FileOutputStream(entryFile)) { + IOUtils.copyTo(new ByteArrayInputStream(data), fos, buf); + } } } } } - for (String path : overrides.keySet()) { - File original = new File(dest, path); - if (original.exists() && !entries.contains(path)) + for (ModpackConfiguration.FileInformation file : overrides) { + File original = new File(dest, file.getPath()); + if (original.exists() && !entries.contains(file.getPath())) original.delete(); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java index a7b3c827f..6443d4c1a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/MultiMCModpackInstallTask.java @@ -58,7 +58,7 @@ public final class MultiMCModpackInstallTask extends Task { this.repository = dependencyManager.getGameRepository(); this.run = repository.getRunDirectory(name); - File json = new File(run, "modpack.json"); + File json = repository.getModpackConfiguration(name); if (repository.hasVersion(name) && !json.exists()) throw new IllegalArgumentException("Version " + name + " already exists."); dependents.add(dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion()).buildAsync()); @@ -69,8 +69,13 @@ public final class MultiMCModpackInstallTask extends Task { ModpackConfiguration config = null; try { - if (json.exists()) - config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken>(){}.getType()); + if (json.exists()) { + config = Constants.GSON.fromJson(FileUtils.readText(json), new TypeToken>() { + }.getType()); + + if (!MODPACK_TYPE.equals(config.getType())) + throw new IllegalArgumentException("Version " + name + " is not a MultiMC modpack. Cannot update this version."); + } } catch (JsonParseException | IOException ignore) { } @@ -110,7 +115,8 @@ public final class MultiMCModpackInstallTask extends Task { } dependencies.add(new VersionJsonSaveTask(repository, version)); - dependencies.add(new MinecraftInstanceTask<>(zipFile, manifest.getName() + "/minecraft/", manifest, new File(run, "modpack.json"))); + dependencies.add(new MinecraftInstanceTask<>(zipFile, manifest.getName() + "/minecraft/", manifest, MODPACK_TYPE, repository.getModpackConfiguration(name))); } - + + public static final String MODPACK_TYPE = "MultiMC"; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/UnsupportedModpackException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/UnsupportedModpackException.java new file mode 100644 index 000000000..089898a02 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/UnsupportedModpackException.java @@ -0,0 +1,35 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see {http://www.gnu.org/licenses/}. + */ +package org.jackhuang.hmcl.mod; + +public class UnsupportedModpackException extends Exception { + public UnsupportedModpackException() { + } + + public UnsupportedModpackException(String message) { + super(message); + } + + public UnsupportedModpackException(String message, Throwable cause) { + super(message, cause); + } + + public UnsupportedModpackException(Throwable cause) { + super(cause); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java index d7517170a..ab7de89d5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java @@ -114,7 +114,7 @@ public class FileDownloadTask extends Task { @Override public void execute() throws Exception { URL currentURL = url; - Logging.LOG.log(Level.FINER, "Downloading {0}, to {1}", new Object[] { currentURL, file }); + Logging.LOG.log(Level.FINER, "Downloading {0} to {1}", new Object[] { currentURL, file }); Exception exception = null; for (int repeat = 0; repeat < retry; repeat++) { @@ -193,7 +193,7 @@ public class FileDownloadTask extends Task { Thread.currentThread().interrupt(); break; } else { - if (file.exists() || !file.delete()) + if (file.exists() && !file.delete()) throw new IOException("Unable to delete existent file " + file); if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) throw new IOException("Unable to make parent directory " + file); diff --git a/gradlew b/gradlew index cccdd3d51..8b3531e29 100644 --- a/gradlew +++ b/gradlew @@ -78,14 +78,14 @@ if [ -n "$JAVA_HOME" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." +path of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." +path of your Java installation." fi # Increase the maximum file descriptors if we can. diff --git a/gradlew.bat b/gradlew.bat index f9553162f..2797c4770 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -27,7 +27,7 @@ echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo path of your Java installation. goto fail @@ -41,7 +41,7 @@ echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo path of your Java installation. goto fail