feat(modpack): support installing Modrinth modpack.

This commit is contained in:
huanghongxun
2022-05-22 00:39:33 +08:00
parent c0df55ee62
commit 9684ce4a1b
45 changed files with 1522 additions and 661 deletions

View File

@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.game;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.download.LibraryAnalyzer;
@@ -28,8 +27,7 @@ import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackLocalInstallTask;
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;
import org.jackhuang.hmcl.mod.ModpackProvider;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.ProxyManager;
import org.jackhuang.hmcl.setting.VersionIconType;
@@ -374,11 +372,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
try {
String jsonText = FileUtils.readText(json);
ModpackConfiguration<?> modpackConfiguration = JsonUtils.GSON.fromJson(jsonText, ModpackConfiguration.class);
if (McbbsModpackLocalInstallTask.MODPACK_TYPE.equals(modpackConfiguration.getType())) {
ModpackConfiguration<McbbsModpackManifest> config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<McbbsModpackManifest>>() {
}.getType());
config.getManifest().injectLaunchOptions(builder);
}
ModpackProvider provider = ModpackHelper.getProviderByType(modpackConfiguration.getType());
if (provider != null) provider.injectLaunchOptions(jsonText, builder);
} catch (IOException | JsonParseException e) {
e.printStackTrace();
}

View File

@@ -34,6 +34,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class HMCLModpackInstallTask extends Task<Void> {
@@ -69,13 +70,13 @@ public final class HMCLModpackInstallTask extends Task<Void> {
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>() {
}.getType());
if (!MODPACK_TYPE.equals(config.getType()))
if (!HMCLModpackProvider.INSTANCE.getName().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, modpack.getEncoding(), "/minecraft", it -> !"pack.json".equals(it), config));
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList("/minecraft"), it -> !"pack.json".equals(it), config));
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/minecraft"), modpack, HMCLModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
}
@Override
@@ -104,6 +105,4 @@ public final class HMCLModpackInstallTask extends Task<Void> {
dependencies.add(libraryTask.thenComposeAsync(repository::saveAsync));
}
public static final String MODPACK_TYPE = "HMCL";
}

View File

@@ -17,8 +17,16 @@
*/
package org.jackhuang.hmcl.game;
public final class HMCLModpackManifest {
import org.jackhuang.hmcl.mod.ModpackManifest;
import org.jackhuang.hmcl.mod.ModpackProvider;
public final class HMCLModpackManifest implements ModpackManifest {
public static final HMCLModpackManifest INSTANCE = new HMCLModpackManifest();
private HMCLModpackManifest() {}
@Override
public ModpackProvider getProvider() {
return HMCLModpackProvider.INSTANCE;
}
}

View File

@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
* Copyright (C) 2022 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
@@ -20,7 +20,11 @@ package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackProvider;
import org.jackhuang.hmcl.mod.ModpackUpdateTask;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
@@ -29,24 +33,38 @@ import org.jackhuang.hmcl.util.io.CompressingUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
/**
* @author huangyuhui
*/
public final class HMCLModpackManager {
private HMCLModpackManager() {
public final class HMCLModpackProvider implements ModpackProvider {
public static final HMCLModpackProvider INSTANCE = new HMCLModpackProvider();
@Override
public String getName() {
return "HMCL";
}
/**
* Read the manifest in a HMCL modpack.
*
* @param file a HMCL modpack file.
* @param encoding encoding of modpack zip file.
* @return the manifest of HMCL modpack.
* @throws IOException if the file is not a valid zip file.
* @throws JsonParseException if the manifest.json is missing or malformed.
*/
public static Modpack readHMCLModpackManifest(ZipFile file, Charset encoding) throws IOException, JsonParseException {
@Override
public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {
return null;
}
@Override
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack) throws MismatchedModpackTypeException {
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
if (!(dependencyManager.getGameRepository() instanceof HMCLGameRepository)) {
throw new IllegalArgumentException("HMCLModpackProvider requires HMCLGameRepository");
}
HMCLGameRepository repository = (HMCLGameRepository) dependencyManager.getGameRepository();
Profile profile = repository.getProfile();
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));
}
@Override
public Modpack readManifest(ZipFile file, Path path, Charset encoding) throws IOException, JsonParseException {
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json");
Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, HMCLModpack.class).setEncoding(encoding);
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json");
@@ -67,4 +85,5 @@ public final class HMCLModpackManager {
return new HMCLModpackInstallTask(((HMCLGameRepository) dependencyManager.getGameRepository()).getProfile(), zipFile, this, name);
}
}
}

View File

@@ -31,14 +31,9 @@ import org.jackhuang.hmcl.download.game.GameVerificationFixTask;
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
import org.jackhuang.hmcl.download.java.JavaRepository;
import org.jackhuang.hmcl.launch.*;
import org.jackhuang.hmcl.mod.ModpackCompletionException;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
import org.jackhuang.hmcl.mod.curse.CurseCompletionTask;
import org.jackhuang.hmcl.mod.curse.CurseInstallTask;
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackCompletionTask;
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackLocalInstallTask;
import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;
import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;
import org.jackhuang.hmcl.mod.ModpackProvider;
import org.jackhuang.hmcl.setting.DownloadProviders;
import org.jackhuang.hmcl.setting.LauncherVisibility;
import org.jackhuang.hmcl.setting.Profile;
@@ -64,7 +59,10 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
@@ -151,14 +149,9 @@ public final class LauncherHelper {
} else {
try {
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
if (CurseInstallTask.MODPACK_TYPE.equals(configuration.getType()))
return new CurseCompletionTask(dependencyManager, selectedVersion);
else if (ServerModpackLocalInstallTask.MODPACK_TYPE.equals(configuration.getType()))
return new ServerModpackCompletionTask(dependencyManager, selectedVersion);
else if (McbbsModpackLocalInstallTask.MODPACK_TYPE.equals(configuration.getType()))
return new McbbsModpackCompletionTask(dependencyManager, selectedVersion);
else
return null;
ModpackProvider provider = ModpackHelper.getProviderByType(configuration.getType());
if (provider == null) return null;
else return provider.createCompletionTask(dependencyManager, selectedVersion);
} catch (IOException e) {
return null;
}
@@ -237,7 +230,7 @@ public final class LauncherHelper {
Exception ex = executor.getException();
if (!(ex instanceof CancellationException)) {
String message;
if (ex instanceof CurseCompletionException) {
if (ex instanceof ModpackCompletionException) {
if (ex.getCause() instanceof FileNotFoundException)
message = i18n("modpack.type.curse.not_found");
else

View File

@@ -0,0 +1,88 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2022 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.game;
import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.ui.versions.ModTranslations;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public abstract class LocalizedRemoteModRepository implements RemoteModRepository {
protected abstract RemoteModRepository getBackedRemoteModRepository();
@Override
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
String newSearchFilter;
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
ModTranslations modTranslations = ModTranslations.getTranslationsByRepositoryType(getType());
List<ModTranslations.Mod> mods = modTranslations.searchMod(searchFilter);
List<String> searchFilters = new ArrayList<>();
int count = 0;
for (ModTranslations.Mod mod : mods) {
String englishName = mod.getName();
if (StringUtils.isNotBlank(mod.getSubname())) {
englishName = mod.getSubname();
}
searchFilters.add(englishName);
count++;
if (count >= 3) break;
}
newSearchFilter = String.join(" ", searchFilters);
} else {
newSearchFilter = searchFilter;
}
return getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort, sortOrder);
}
@Override
public Stream<Category> getCategories() throws IOException {
return getBackedRemoteModRepository().getCategories();
}
@Override
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
return getBackedRemoteModRepository().getRemoteVersionByLocalFile(localModFile, file);
}
@Override
public RemoteMod getModById(String id) throws IOException {
return getBackedRemoteModRepository().getModById(id);
}
@Override
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
return getBackedRemoteModRepository().getModFile(modId, fileId);
}
@Override
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
return getBackedRemoteModRepository().getRemoteVersionsById(id);
}
}

View File

@@ -21,15 +21,14 @@ import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.compress.archivers.zip.ZipFile;
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.mcbbs.McbbsModpackLocalInstallTask;
import org.jackhuang.hmcl.mod.curse.CurseModpackProvider;
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackProvider;
import org.jackhuang.hmcl.mod.modrinth.ModrinthModpackProvider;
import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackProvider;
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
import org.jackhuang.hmcl.mod.server.ServerModpackProvider;
import org.jackhuang.hmcl.mod.server.ServerModpackRemoteInstallTask;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
@@ -42,6 +41,7 @@ import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
@@ -52,47 +52,53 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.toIterable;
import static org.jackhuang.hmcl.util.Pair.pair;
public final class ModpackHelper {
private ModpackHelper() {}
private static final Map<String, ModpackProvider> providers = mapOf(
pair(CurseModpackProvider.INSTANCE.getName(), CurseModpackProvider.INSTANCE),
pair(McbbsModpackProvider.INSTANCE.getName(), McbbsModpackProvider.INSTANCE),
pair(ModrinthModpackProvider.INSTANCE.getName(), ModrinthModpackProvider.INSTANCE),
pair(MultiMCModpackProvider.INSTANCE.getName(), MultiMCModpackProvider.INSTANCE),
pair(ServerModpackProvider.INSTANCE.getName(), ServerModpackProvider.INSTANCE),
pair(HMCLModpackProvider.INSTANCE.getName(), HMCLModpackProvider.INSTANCE)
);
@Nullable
public static ModpackProvider getProviderByType(String type) {
return providers.get(type);
}
public static boolean isFileModpackByExtension(File file) {
String ext = FileUtils.getExtension(file);
return "zip".equals(ext) || "mrpack".equals(ext);
}
public static Modpack readModpackManifest(Path file, Charset charset) throws UnsupportedModpackException, ManuallyCreatedModpackException {
try (ZipFile zipFile = CompressingUtils.openZipFile(file, charset)) {
try {
return McbbsModpackManifest.readManifest(zipFile, charset);
} catch (Exception ignored) {
// ignore it, not a valid MCBBS modpack.
// Order for trying detecting manifest is necessary here.
// Do not change to iterating providers.
for (ModpackProvider provider : new ModpackProvider[]{
McbbsModpackProvider.INSTANCE,
CurseModpackProvider.INSTANCE,
ModrinthModpackProvider.INSTANCE,
HMCLModpackProvider.INSTANCE,
MultiMCModpackProvider.INSTANCE,
ServerModpackProvider.INSTANCE}) {
try {
return provider.readManifest(zipFile, file, charset);
} catch (Exception ignored) {
}
}
try {
return CurseManifest.readCurseForgeModpackManifest(zipFile, charset);
} catch (Exception e) {
// ignore it, not a valid CurseForge modpack.
}
try {
return HMCLModpackManager.readHMCLModpackManifest(zipFile, charset);
} catch (Exception e) {
// ignore it, not a valid HMCL modpack.
}
try {
return MultiMCInstanceConfiguration.readMultiMCModpackManifest(zipFile, file, charset);
} catch (Exception e) {
// ignore it, not a valid MultiMC modpack.
}
try {
return ServerModpackManifest.readManifest(zipFile, charset);
} catch (Exception e) {
// ignore it, not a valid Server modpack.
}
} catch (IOException ignored) {
}
@@ -142,17 +148,6 @@ public final class ModpackHelper {
}
}
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<?> getInstallTask(Profile profile, ServerModpackManifest manifest, String name, Modpack modpack) {
profile.getRepository().markVersionAsModpack(name);
@@ -166,7 +161,7 @@ public final class ModpackHelper {
};
ExceptionalConsumer<Exception, ?> failure = ex -> {
if (ex instanceof CurseCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
if (ex instanceof ModpackCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
success.run();
// This is tolerable and we will not delete the game
}
@@ -208,7 +203,7 @@ public final class ModpackHelper {
};
ExceptionalConsumer<Exception, ?> failure = ex -> {
if (ex instanceof CurseCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
if (ex instanceof ModpackCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
success.run();
// This is tolerable and we will not delete the game
}
@@ -237,38 +232,13 @@ public final class ModpackHelper {
}
}
public static Task<Void> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
public static Task<?> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
Modpack modpack = ModpackHelper.readModpackManifest(zipFile.toPath(), charset);
switch (configuration.getType()) {
case CurseInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof CurseManifest))
throw new MismatchedModpackTypeException(CurseInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new ModpackUpdateTask(profile.getRepository(), name, new CurseInstallTask(profile.getDependency(), zipFile, modpack, (CurseManifest) modpack.getManifest(), name));
case MultiMCModpackInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof MultiMCInstanceConfiguration))
throw new MismatchedModpackTypeException(MultiMCModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new ModpackUpdateTask(profile.getRepository(), name, new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, (MultiMCInstanceConfiguration) modpack.getManifest(), name));
case HMCLModpackInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
throw new MismatchedModpackTypeException(HMCLModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new ModpackUpdateTask(profile.getRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));
case McbbsModpackLocalInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof McbbsModpackManifest))
throw new MismatchedModpackTypeException(McbbsModpackLocalInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new ModpackUpdateTask(profile.getRepository(), name, new McbbsModpackLocalInstallTask(profile.getDependency(), zipFile, modpack, (McbbsModpackManifest) modpack.getManifest(), name));
case ServerModpackLocalInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof ServerModpackManifest))
throw new MismatchedModpackTypeException(ServerModpackLocalInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new ModpackUpdateTask(profile.getRepository(), name, new ServerModpackLocalInstallTask(profile.getDependency(), zipFile, modpack, (ServerModpackManifest) modpack.getManifest(), name));
default:
throw new UnsupportedModpackException();
ModpackProvider provider = getProviderByType(configuration.getType());
if (provider == null) {
throw new UnsupportedModpackException();
}
return provider.createUpdateTask(profile.getDependency(), name, zipFile, modpack);
}
public static void toVersionSetting(MultiMCInstanceConfiguration c, VersionSetting vs) {

View File

@@ -32,6 +32,7 @@ import javafx.stage.StageStyle;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.download.java.JavaRepository;
import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
import org.jackhuang.hmcl.setting.Profiles;
@@ -75,7 +76,7 @@ public final class Controllers {
GameListPage gameListPage = new GameListPage();
gameListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
gameListPage.profilesProperty().bindContent(Profiles.profilesProperty());
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
FXUtils.applyDragListener(gameListPage, ModpackHelper::isFileModpackByExtension, modpacks -> {
File modpack = modpacks.get(0);
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
});

View File

@@ -42,10 +42,15 @@ 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.mcbbs.McbbsModpackCompletionTask;
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackExportTask;
import org.jackhuang.hmcl.mod.modrinth.ModrinthCompletionTask;
import org.jackhuang.hmcl.mod.modrinth.ModrinthInstallTask;
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackExportTask;
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackInstallTask;
import org.jackhuang.hmcl.mod.server.ServerModpackCompletionTask;
import org.jackhuang.hmcl.mod.server.ServerModpackExportTask;
import org.jackhuang.hmcl.mod.server.ServerModpackLocalInstallTask;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
@@ -123,8 +128,8 @@ public final class TaskListPane extends StackPane {
task.setName(i18n("install.installer.install", i18n("install.installer.fabric")));
} else if (task instanceof FabricAPIInstallTask) {
task.setName(i18n("install.installer.install", i18n("install.installer.fabric-api")));
} else if (task instanceof CurseCompletionTask) {
task.setName(i18n("modpack.type.curse.completion"));
} else if (task instanceof CurseCompletionTask || task instanceof ModrinthCompletionTask || task instanceof ServerModpackCompletionTask || task instanceof McbbsModpackCompletionTask) {
task.setName(i18n("modpack.completion"));
} else if (task instanceof ModpackInstallTask) {
task.setName(i18n("modpack.installing"));
} else if (task instanceof ModpackUpdateTask) {
@@ -133,6 +138,10 @@ public final class TaskListPane extends StackPane {
task.setName(i18n("modpack.install", i18n("modpack.type.curse")));
} else if (task instanceof MultiMCModpackInstallTask) {
task.setName(i18n("modpack.install", i18n("modpack.type.multimc")));
} else if (task instanceof ModrinthInstallTask) {
task.setName(i18n("modpack.install", i18n("modpack.type.modrinth")));
} else if (task instanceof ServerModpackLocalInstallTask) {
task.setName(i18n("modpack.install", i18n("modpack.type.server")));
} else if (task instanceof HMCLModpackInstallTask) {
task.setName(i18n("modpack.install", i18n("modpack.type.hmcl")));
} else if (task instanceof McbbsModpackExportTask || task instanceof MultiMCModpackExportTask || task instanceof ServerModpackExportTask) {

View File

@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.ui.download;
import javafx.scene.Node;
import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.game.ManuallyCreatedModpackException;
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
import org.jackhuang.hmcl.mod.ModpackCompletionException;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
@@ -125,7 +125,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
settings.put("failure_callback", new FailureCallback() {
@Override
public void onFail(Map<String, Object> settings, Exception exception, Runnable next) {
if (exception instanceof CurseCompletionException) {
if (exception instanceof ModpackCompletionException) {
if (exception.getCause() instanceof FileNotFoundException) {
Controllers.dialog(i18n("modpack.type.curse.not_found"), i18n("install.failed"), MessageType.ERROR, next);
} else {

View File

@@ -243,13 +243,19 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
{
int rowIndex = 0;
if (control.versionSelection) {
JFXComboBox<String> versionsComboBox = new JFXComboBox<>();
versionsComboBox.setMaxWidth(Double.MAX_VALUE);
Bindings.bindContent(versionsComboBox.getItems(), control.versions);
selectedItemPropertyFor(versionsComboBox).bindBidirectional(control.selectedVersion);
if (control.versionSelection || !control.downloadSources.isEmpty()) {
searchPane.addRow(rowIndex);
int columns = 0;
Node lastNode = null;
if (control.versionSelection) {
JFXComboBox<String> versionsComboBox = new JFXComboBox<>();
versionsComboBox.setMaxWidth(Double.MAX_VALUE);
Bindings.bindContent(versionsComboBox.getItems(), control.versions);
selectedItemPropertyFor(versionsComboBox).bindBidirectional(control.selectedVersion);
searchPane.addRow(rowIndex, new Label(i18n("version")), versionsComboBox);
searchPane.add(new Label(i18n("version")), columns++, rowIndex);
searchPane.add(lastNode = versionsComboBox, columns++, rowIndex);
}
if (control.downloadSources.getSize() > 1) {
JFXComboBox<String> downloadSourceComboBox = new JFXComboBox<>();
@@ -258,10 +264,12 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
downloadSourceComboBox.setConverter(stringConverter(I18n::i18n));
selectedItemPropertyFor(downloadSourceComboBox).bindBidirectional(control.downloadSource);
searchPane.add(new Label(i18n("settings.launcher.download_source")), 2, rowIndex);
searchPane.add(downloadSourceComboBox, 3, rowIndex);
} else {
GridPane.setColumnSpan(versionsComboBox, 3);
searchPane.add(new Label(i18n("settings.launcher.download_source")), columns++, rowIndex);
searchPane.add(lastNode = downloadSourceComboBox, columns++, rowIndex);
}
if (columns == 2) {
GridPane.setColumnSpan(lastNode, 3);
}
rowIndex++;

View File

@@ -17,19 +17,10 @@
*/
package org.jackhuang.hmcl.ui.versions;
import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.game.LocalizedRemoteModRepository;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -39,17 +30,17 @@ public class ModDownloadListPage extends DownloadListPage {
repository = new Repository();
supportChinese.set(true);
downloadSources.get().setAll("mods.curseforge", "mods.modrinth");
downloadSource.set("mods.curseforge");
}
private class Repository implements RemoteModRepository {
private class Repository extends LocalizedRemoteModRepository {
private RemoteModRepository getBackedRemoteModRepository() {
@Override
protected RemoteModRepository getBackedRemoteModRepository() {
if ("mods.modrinth".equals(downloadSource.get())) {
return ModrinthRemoteModRepository.INSTANCE;
return ModrinthRemoteModRepository.MODS;
} else {
return CurseForgeRemoteModRepository.MODS;
}
@@ -59,57 +50,6 @@ public class ModDownloadListPage extends DownloadListPage {
public Type getType() {
return Type.MOD;
}
@Override
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
String newSearchFilter;
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
List<ModTranslations.Mod> mods = ModTranslations.MOD.searchMod(searchFilter);
List<String> searchFilters = new ArrayList<>();
int count = 0;
for (ModTranslations.Mod mod : mods) {
String englishName = mod.getName();
if (StringUtils.isNotBlank(mod.getSubname())) {
englishName = mod.getSubname();
}
searchFilters.add(englishName);
count++;
if (count >= 3) break;
}
newSearchFilter = String.join(" ", searchFilters);
} else {
newSearchFilter = searchFilter;
}
return getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort, sortOrder);
}
@Override
public Stream<Category> getCategories() throws IOException {
return getBackedRemoteModRepository().getCategories();
}
@Override
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
return getBackedRemoteModRepository().getRemoteVersionByLocalFile(localModFile, file);
}
@Override
public RemoteMod getModById(String id) throws IOException {
return getBackedRemoteModRepository().getModById(id);
}
@Override
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
return getBackedRemoteModRepository().getModFile(modId, fileId);
}
@Override
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
return getBackedRemoteModRepository().getRemoteVersionsById(id);
}
}
@Override

View File

@@ -41,7 +41,6 @@ public final class ModTranslations {
public static ModTranslations MODPACK = new ModTranslations("/assets/modpack_data.txt");
public static ModTranslations EMPTY = new ModTranslations("");
@Nullable
public static ModTranslations getTranslationsByRepositoryType(RemoteModRepository.Type type) {
switch (type) {
case MOD:
@@ -101,7 +100,11 @@ public final class ModTranslations {
}
private boolean loadFromResource() {
if (mods != null || StringUtils.isBlank(resourceName)) return true;
if (mods != null) return true;
if (StringUtils.isBlank(resourceName)) {
mods = Collections.emptyList();
return true;
}
try {
String modData = IOUtils.readFullyAsString(ModTranslations.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8);
mods = Arrays.stream(modData.split("\n")).filter(line -> !line.startsWith("#")).map(Mod::new).collect(Collectors.toList());

View File

@@ -172,7 +172,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
if (item.getCurrentVersion().getSelf() instanceof CurseAddon.LatestFile) {
content.getTags().add("Curseforge");
} else if (item.getCurrentVersion().getSelf() instanceof ModrinthRemoteModRepository.ModVersion) {
} else if (item.getCurrentVersion().getSelf() instanceof ModrinthRemoteModRepository.ProjectVersion) {
content.getTags().add("Modrinth");
}
}

View File

@@ -17,18 +17,10 @@
*/
package org.jackhuang.hmcl.ui.versions;
import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.game.LocalizedRemoteModRepository;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -38,76 +30,43 @@ public class ModpackDownloadListPage extends DownloadListPage {
repository = new Repository();
supportChinese.set(true);
downloadSources.get().setAll("mods.curseforge", "mods.modrinth");
downloadSource.set("mods.curseforge");
}
private class Repository implements RemoteModRepository {
private class Repository extends LocalizedRemoteModRepository {
@Override
protected RemoteModRepository getBackedRemoteModRepository() {
if ("mods.modrinth".equals(downloadSource.get())) {
return ModrinthRemoteModRepository.MODPACKS;
} else {
return CurseForgeRemoteModRepository.MODPACKS;
}
}
@Override
public Type getType() {
return Type.MODPACK;
}
@Override
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
String newSearchFilter;
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
List<ModTranslations.Mod> mods = ModTranslations.MODPACK.searchMod(searchFilter);
List<String> searchFilters = new ArrayList<>();
int count = 0;
for (ModTranslations.Mod mod : mods) {
String englishName = mod.getName();
if (StringUtils.isNotBlank(mod.getSubname())) {
englishName = mod.getSubname();
}
searchFilters.add(englishName);
count++;
if (count >= 3) break;
}
newSearchFilter = String.join(" ", searchFilters);
} else {
newSearchFilter = searchFilter;
}
return CurseForgeRemoteModRepository.MODPACKS.search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort, sortOrder);
}
@Override
public Stream<Category> getCategories() throws IOException {
return CurseForgeRemoteModRepository.MODPACKS.getCategories();
}
@Override
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
return CurseForgeRemoteModRepository.MODPACKS.getRemoteVersionByLocalFile(localModFile, file);
}
@Override
public RemoteMod getModById(String id) throws IOException {
return CurseForgeRemoteModRepository.MODPACKS.getModById(id);
}
@Override
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
return CurseForgeRemoteModRepository.MODPACKS.getModFile(modId, fileId);
}
@Override
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
return CurseForgeRemoteModRepository.MODPACKS.getRemoteVersionsById(id);
}
}
@Override
protected String getLocalizedCategory(String category) {
return i18n("curse.category." + category);
if ("mods.modrinth".equals(downloadSource.get())) {
return i18n("modrinth.category." + category);
} else {
return i18n("curse.category." + category);
}
}
@Override
protected String getLocalizedOfficialPage() {
return i18n("mods.curseforge");
if ("mods.modrinth".equals(downloadSource.get())) {
return i18n("mods.modrinth");
} else {
return i18n("mods.curseforge");
}
}
}

View File

@@ -553,6 +553,7 @@ modpack.choose.local.detail=You can drag the modpack file into this page to inst
modpack.choose.remote=Download modpack from Internet
modpack.choose.remote.detail=Requires a direct download link to the remote modpack file
modpack.choose.remote.tooltip=A direct download link to the remote modpack file
modpack.completion=Install files related to modpack
modpack.desc=Describe your modpack, including precautions and changelog. Markdown and online pictures are supported.
modpack.description=Description
modpack.download=Modpack Downloads
@@ -586,16 +587,16 @@ modpack.origin.mcbbs=MCBBS
modpack.origin.mcbbs.prompt=Thread id
modpack.scan=Scanning this modpack
modpack.task.install=Import Modpack
modpack.task.install.error=This modpack file cannot be recognized. Only Curse and MultiMC modpacks are supported.
modpack.task.install.error=This modpack file cannot be recognized. Only Curse, Modrinth, MCBBS and MultiMC modpacks are supported.
modpack.task.install.will=Install the modpack:
modpack.type.curse=Curse
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.manual.warning=You probably need to directly decompress this modpack file, instead of importing this modpack. And launch the game using bundled launcher. This modpack is manually created by compressing .minecraft directory, not exported by launcher. HMCL can try to import this modpack, continue?
modpack.type.mcbbs=MCBBS Standard
modpack.type.mcbbs.export=Can be imported by Hello Minecraft! Launcher and MultiMC
modpack.type.modrinth=Modrinth
modpack.type.multimc=MultiMC
modpack.type.multimc.export=Can be imported by Hello Minecraft! Launcher and MultiMC
modpack.type.server=Server Auto-Update Modpack

View File

@@ -553,6 +553,7 @@ modpack.choose.local.detail=你可以直接將模組包檔案拖入本頁面以
modpack.choose.remote=從網路下載模組包
modpack.choose.remote.detail=需要提供模組包的下載連結
modpack.choose.remote.tooltip=要下載的模組包的連結
modpack.completion=下載模組包相關檔案
modpack.desc=描述你要製作的模組包,比如模組包注意事項和更新記錄,支援 Markdown圖片請上傳至網路
modpack.description=模組包描述
modpack.download=下載模組包
@@ -586,16 +587,16 @@ modpack.origin.mcbbs=MCBBS
modpack.origin.mcbbs.prompt=貼子 id
modpack.scan=解析模組包
modpack.task.install=匯入模組包
modpack.task.install.error=無法識別該模組包,目前僅支援匯入 Curse、MultiMC、HMCL 模組包。
modpack.task.install.error=無法識別該模組包,目前僅支援匯入 Curse、Modrinth、MultiMC、HMCL 模組包。
modpack.task.install.will=將會安裝模組包:
modpack.type.curse=Curse
modpack.type.curse.completion=下載 Curse 模組包相關檔案
modpack.type.curse.tolerable_error=但未能完成 Curse 模組包檔案的下載,您可以在啟動該遊戲版本時繼續 Curse 模組包檔案的下載。由於網路問題,您可能需要重試多次。
modpack.type.curse.error=無法完成 Curse 模組包的下載,請多次重試或設定代理
modpack.type.curse.not_found=部分必需檔案已經從網路中被刪除並且再也無法下載,請嘗試該模組包的最新版本或者安裝其他模組包。
modpack.type.manual.warning=您大概需要直接解压该模組包,并使用其自带的启动器即可开始游戏,而不需要导入模組包。该模組包不是由启动器导出的模組包,而是人工打包 .minecraft 资料夹而来的这类模組包通常附带游戏启动器。HMCL 可以尝试导入该模組包,但不保证可用性,是否继续?
modpack.type.mcbbs=我的世界中文論壇模組包標準
modpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 匯入
modpack.type.modrinth=Modrinth
modpack.type.multimc=MultiMC
modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 匯入
modpack.type.server=伺服器自動更新模組包

View File

@@ -553,6 +553,7 @@ modpack.choose.local.detail=你可以直接将整合包文件拖入本页面以
modpack.choose.remote=从互联网下载整合包
modpack.choose.remote.detail=需要提供整合包的下载链接
modpack.choose.remote.tooltip=要下载的整合包的链接
modpack.completion=下载整合包相关文件
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 HTML图片请用网络图
modpack.description=整合包描述
modpack.download=下载整合包
@@ -586,16 +587,16 @@ modpack.origin.mcbbs=MCBBS
modpack.origin.mcbbs.prompt=贴子 id
modpack.scan=解析整合包
modpack.task.install=导入整合包
modpack.task.install.error=无法识别该整合包,目前仅支持导入 Curse、MultiMC、HMCL 整合包。
modpack.task.install.error=无法识别该整合包,目前仅支持导入 Curse、Modrinth、MultiMC、HMCL 整合包。
modpack.task.install.will=将会安装整合包:
modpack.type.curse=Curse
modpack.type.curse.completion=下载 Curse 整合包相关文件
modpack.type.curse.tolerable_error=但未能完成 Curse 整合包所需的依赖下载,您可以在启动该游戏版本时继续 Curse 整合包文件的下载。由于网络问题,您可能需要重试多次……
modpack.type.curse.error=未能完成 Curse 整合包所需的依赖下载,请多次重试或设置代理
modpack.type.curse.not_found=部分必需文件已经在网络中被删除并且再也无法下载,请尝试该整合包的最新版本或者安装其他整合包。
modpack.type.manual.warning=您大概需要直接解压该整合包,并使用其自带的启动器即可开始游戏,而不需要导入整合包!该整合包不是由启动器导出的整合包,而是人工打包 .minecraft 文件夹而来的这类整合包通常附带游戏启动器。HMCL 可以尝试导入该整合包,但不保证可用性,是否继续?
modpack.type.mcbbs=我的世界中文论坛整合包标准
modpack.type.mcbbs.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 导入
modpack.type.modrinth=Modrinth
modpack.type.multimc=MultiMC
modpack.type.multimc.export=可以被 Hello Minecraft! Launcher (HMCL) 和 MultiMC 导入
modpack.type.server=服务器自动更新整合包