feat(modpack): support installing Modrinth modpack.
This commit is contained in:
@@ -44,7 +44,7 @@ public class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return CompletableFuture.runAsync(wrap(() -> {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.INSTANCE.getRemoteVersionsById("P7dR8mSH"))) {
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.MODS.getRemoteVersionsById("P7dR8mSH"))) {
|
||||
for (String gameVersion : modVersion.getGameVersions()) {
|
||||
versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
@@ -37,20 +38,20 @@ public final class MinecraftInstanceTask<T> extends Task<ModpackConfiguration<T>
|
||||
|
||||
private final File zipFile;
|
||||
private final Charset encoding;
|
||||
private final String subDirectory;
|
||||
private final List<String> subDirectories;
|
||||
private final File jsonFile;
|
||||
private final T manifest;
|
||||
private final String type;
|
||||
private final String name;
|
||||
private final String version;
|
||||
|
||||
public MinecraftInstanceTask(File zipFile, Charset encoding, String subDirectory, T manifest, String type, String name, String version, File jsonFile) {
|
||||
public MinecraftInstanceTask(File zipFile, Charset encoding, List<String> subDirectories, T manifest, ModpackProvider modpackProvider, String name, String version, File jsonFile) {
|
||||
this.zipFile = zipFile;
|
||||
this.encoding = encoding;
|
||||
this.subDirectory = FileUtils.normalizePath(subDirectory);
|
||||
this.subDirectories = subDirectories.stream().map(FileUtils::normalizePath).collect(Collectors.toList());
|
||||
this.manifest = manifest;
|
||||
this.jsonFile = jsonFile;
|
||||
this.type = type;
|
||||
this.type = modpackProvider.getName();
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
}
|
||||
@@ -60,17 +61,19 @@ public final class MinecraftInstanceTask<T> extends Task<ModpackConfiguration<T>
|
||||
List<ModpackConfiguration.FileInformation> overrides = new ArrayList<>();
|
||||
|
||||
try (FileSystem fs = CompressingUtils.readonly(zipFile.toPath()).setEncoding(encoding).build()) {
|
||||
Path root = fs.getPath(subDirectory);
|
||||
for (String subDirectory : subDirectories) {
|
||||
Path root = fs.getPath(subDirectory);
|
||||
|
||||
if (Files.exists(root))
|
||||
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
String relativePath = root.relativize(file).normalize().toString().replace(File.separatorChar, '/');
|
||||
overrides.add(new ModpackConfiguration.FileInformation(relativePath, encodeHex(digest("SHA-1", file))));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
if (Files.exists(root))
|
||||
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
String relativePath = root.relativize(file).normalize().toString().replace(File.separatorChar, '/');
|
||||
overrides.add(new ModpackConfiguration.FileInformation(relativePath, encodeHex(digest("SHA-1", file))));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ModpackConfiguration<T> configuration = new ModpackConfiguration<>(manifest, type, name, version, overrides);
|
||||
|
||||
@@ -35,13 +35,13 @@ public abstract class Modpack {
|
||||
private String gameVersion;
|
||||
private String description;
|
||||
private transient Charset encoding;
|
||||
private Object manifest;
|
||||
private ModpackManifest manifest;
|
||||
|
||||
public Modpack() {
|
||||
this("", null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public Modpack(String name, String author, String version, String gameVersion, String description, Charset encoding, Object manifest) {
|
||||
public Modpack(String name, String author, String version, String gameVersion, String description, Charset encoding, ModpackManifest manifest) {
|
||||
this.name = name;
|
||||
this.author = author;
|
||||
this.version = version;
|
||||
@@ -105,11 +105,11 @@ public abstract class Modpack {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object getManifest() {
|
||||
public ModpackManifest getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
public Modpack setManifest(Object manifest) {
|
||||
public Modpack setManifest(ModpackManifest manifest) {
|
||||
this.manifest = manifest;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -15,21 +15,21 @@
|
||||
* 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.mod.curse;
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
public class CurseCompletionException extends Exception {
|
||||
public CurseCompletionException() {
|
||||
public class ModpackCompletionException extends Exception {
|
||||
public ModpackCompletionException() {
|
||||
}
|
||||
|
||||
public CurseCompletionException(String message) {
|
||||
public ModpackCompletionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CurseCompletionException(String message, Throwable cause) {
|
||||
public ModpackCompletionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CurseCompletionException(Throwable cause) {
|
||||
public ModpackCompletionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class ModpackInstallTask<T> extends Task<Void> {
|
||||
private final File modpackFile;
|
||||
private final File dest;
|
||||
private final Charset charset;
|
||||
private final String subDirectory;
|
||||
private final List<String> subDirectories;
|
||||
private final List<ModpackConfiguration.FileInformation> overrides;
|
||||
private final Predicate<String> callback;
|
||||
|
||||
@@ -45,15 +45,15 @@ public class ModpackInstallTask<T> extends Task<Void> {
|
||||
* @param modpackFile a zip file
|
||||
* @param dest destination to store unpacked files
|
||||
* @param charset charset of the zip file
|
||||
* @param subDirectory the subdirectory of zip file to unpack
|
||||
* @param subDirectories the subdirectory of zip file to unpack
|
||||
* @param callback test whether the file (given full path) in zip file should be unpacked or not
|
||||
* @param oldConfiguration old modpack information if upgrade
|
||||
*/
|
||||
public ModpackInstallTask(File modpackFile, File dest, Charset charset, String subDirectory, Predicate<String> callback, ModpackConfiguration<T> oldConfiguration) {
|
||||
public ModpackInstallTask(File modpackFile, File dest, Charset charset, List<String> subDirectories, Predicate<String> callback, ModpackConfiguration<T> oldConfiguration) {
|
||||
this.modpackFile = modpackFile;
|
||||
this.dest = dest;
|
||||
this.charset = charset;
|
||||
this.subDirectory = subDirectory;
|
||||
this.subDirectories = subDirectories;
|
||||
this.callback = callback;
|
||||
|
||||
if (oldConfiguration == null)
|
||||
@@ -72,30 +72,33 @@ public class ModpackInstallTask<T> extends Task<Void> {
|
||||
for (ModpackConfiguration.FileInformation file : overrides)
|
||||
files.put(file.getPath(), file);
|
||||
|
||||
new Unzipper(modpackFile, dest)
|
||||
.setSubDirectory(subDirectory)
|
||||
.setTerminateIfSubDirectoryNotExists()
|
||||
.setReplaceExistentFile(true)
|
||||
.setEncoding(charset)
|
||||
.setFilter((destPath, isDirectory, zipEntry, entryPath) -> {
|
||||
if (isDirectory) return true;
|
||||
if (!callback.test(entryPath)) return false;
|
||||
entries.add(entryPath);
|
||||
|
||||
if (!files.containsKey(entryPath)) {
|
||||
// If old modpack does not have this entry, add this entry or override the file that user added.
|
||||
return true;
|
||||
} else if (!Files.exists(destPath)) {
|
||||
// If both old and new modpacks have this entry, but the file is deleted by user, leave it missing.
|
||||
return false;
|
||||
} else {
|
||||
// If both old and new modpacks have this entry, and user has modified this file,
|
||||
// we will not replace it since this modified file is what user expects.
|
||||
String fileHash = encodeHex(digest("SHA-1", destPath));
|
||||
String oldHash = files.get(entryPath).getHash();
|
||||
return Objects.equals(oldHash, fileHash);
|
||||
}
|
||||
}).unzip();
|
||||
for (String subDirectory : subDirectories) {
|
||||
new Unzipper(modpackFile, dest)
|
||||
.setSubDirectory(subDirectory)
|
||||
.setTerminateIfSubDirectoryNotExists()
|
||||
.setReplaceExistentFile(true)
|
||||
.setEncoding(charset)
|
||||
.setFilter((destPath, isDirectory, zipEntry, entryPath) -> {
|
||||
if (isDirectory) return true;
|
||||
if (!callback.test(entryPath)) return false;
|
||||
entries.add(entryPath);
|
||||
|
||||
if (!files.containsKey(entryPath)) {
|
||||
// If old modpack does not have this entry, add this entry or override the file that user added.
|
||||
return true;
|
||||
} else if (!Files.exists(destPath)) {
|
||||
// If both old and new modpacks have this entry, but the file is deleted by user, leave it missing.
|
||||
return false;
|
||||
} else {
|
||||
// If both old and new modpacks have this entry, and user has modified this file,
|
||||
// we will not replace it since this modified file is what user expects.
|
||||
String fileHash = encodeHex(digest("SHA-1", destPath));
|
||||
String oldHash = files.get(entryPath).getHash();
|
||||
return Objects.equals(oldHash, fileHash);
|
||||
}
|
||||
}).unzip();
|
||||
}
|
||||
|
||||
// If old modpack have this entry, and new modpack deleted it. Delete this file.
|
||||
for (ModpackConfiguration.FileInformation file : overrides) {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.mod;
|
||||
|
||||
public interface ModpackManifest {
|
||||
ModpackProvider getProvider();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.mod;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface ModpackProvider {
|
||||
|
||||
String getName();
|
||||
|
||||
Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version);
|
||||
|
||||
Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack) throws MismatchedModpackTypeException;
|
||||
|
||||
/**
|
||||
* @param zipFile the opened modpack zip file.
|
||||
* @param file the modpack zip file path.
|
||||
* @param encoding encoding of zip file.
|
||||
* @throws IOException if the file is not a valid zip file.
|
||||
* @throws JsonParseException if the manifest.json is missing or malformed.
|
||||
* @return the manifest.
|
||||
*/
|
||||
Modpack readManifest(ZipFile zipFile, Path file, Charset encoding) throws IOException, JsonParseException;
|
||||
|
||||
default void injectLaunchOptions(String modpackConfigurationJson, LaunchOptions.Builder builder) {
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.ModpackCompletionException;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -122,11 +123,11 @@ public final class CurseCompletionTask extends Task<Void> {
|
||||
RemoteMod.File remoteFile = CurseForgeRemoteModRepository.MODS.getModFile(Integer.toString(file.getProjectID()), Integer.toString(file.getFileID()));
|
||||
return file.withFileName(remoteFile.getFilename()).withURL(remoteFile.getUrl());
|
||||
} catch (FileNotFoundException fof) {
|
||||
Logging.LOG.log(Level.WARNING, "Could not query api.curseforge.com for deleted mods: " + file.getProjectID() + ", " +file.getFileID(), fof);
|
||||
Logging.LOG.log(Level.WARNING, "Could not query api.curseforge.com for deleted mods: " + file.getProjectID() + ", " + file.getFileID(), fof);
|
||||
notFound.set(true);
|
||||
return file;
|
||||
} catch (IOException | JsonParseException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name projectID=" + file.getProjectID() + ", fileID=" +file.getFileID(), e);
|
||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name projectID=" + file.getProjectID() + ", fileID=" + file.getFileID(), e);
|
||||
allNameKnown.set(false);
|
||||
return file;
|
||||
}
|
||||
@@ -163,8 +164,8 @@ public final class CurseCompletionTask extends Task<Void> {
|
||||
// Let this task fail if the curse manifest has not been completed.
|
||||
// But continue other downloads.
|
||||
if (notFound.get())
|
||||
throw new CurseCompletionException(new FileNotFoundException());
|
||||
throw new ModpackCompletionException(new FileNotFoundException());
|
||||
if (!allNameKnown.get() || !isDependenciesSucceeded())
|
||||
throw new CurseCompletionException();
|
||||
throw new ModpackCompletionException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,7 @@ import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.ModpackInstallTask;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
@@ -35,6 +32,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -78,17 +76,19 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
throw new IllegalArgumentException("Version " + name + " already exists.");
|
||||
|
||||
GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getMinecraft().getGameVersion());
|
||||
for (CurseManifestModLoader modLoader : manifest.getMinecraft().getModLoaders())
|
||||
if (modLoader.getId().startsWith("forge-"))
|
||||
for (CurseManifestModLoader modLoader : manifest.getMinecraft().getModLoaders()) {
|
||||
if (modLoader.getId().startsWith("forge-")) {
|
||||
builder.version("forge", modLoader.getId().substring("forge-".length()));
|
||||
else if (modLoader.getId().startsWith("fabric-"))
|
||||
} else if (modLoader.getId().startsWith("fabric-")) {
|
||||
builder.version("fabric", modLoader.getId().substring("fabric-".length()));
|
||||
}
|
||||
}
|
||||
dependents.add(builder.buildAsync());
|
||||
|
||||
onDone().register(event -> {
|
||||
Exception ex = event.getTask().getException();
|
||||
if (event.isFailed()) {
|
||||
if (!(ex instanceof CurseCompletionException)) {
|
||||
if (!(ex instanceof ModpackCompletionException)) {
|
||||
repository.removeVersionFromDisk(name);
|
||||
}
|
||||
}
|
||||
@@ -100,14 +100,14 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<CurseManifest>>() {
|
||||
}.getType());
|
||||
|
||||
if (!MODPACK_TYPE.equals(config.getType()))
|
||||
if (!CurseModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a Curse modpack. Cannot update this version.");
|
||||
}
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
this.config = config;
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), manifest.getOverrides(), any -> true, config).withStage("hmcl.modpack"));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), manifest.getOverrides(), manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(manifest.getOverrides()), any -> true, config).withStage("hmcl.modpack"));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(manifest.getOverrides()), manifest, CurseModpackProvider.INSTANCE, manifest.getName(), manifest.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
|
||||
dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest));
|
||||
}
|
||||
@@ -139,6 +139,4 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
File root = repository.getVersionRoot(name);
|
||||
FileUtils.writeText(new File(root, "manifest.json"), JsonUtils.GSON.toJson(manifest));
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "Curse";
|
||||
}
|
||||
|
||||
@@ -17,21 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod.curse;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.mod.ModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -40,7 +30,7 @@ import java.util.List;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@Immutable
|
||||
public final class CurseManifest {
|
||||
public final class CurseManifest implements ModpackManifest {
|
||||
|
||||
@SerializedName("manifestType")
|
||||
private final String manifestType;
|
||||
@@ -117,28 +107,9 @@ public final class CurseManifest {
|
||||
return new CurseManifest(manifestType, manifestVersion, name, version, author, overrides, minecraft, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zip the CurseForge modpack file.
|
||||
* @throws IOException if the file is not a valid zip file.
|
||||
* @throws JsonParseException if the manifest.json is missing or malformed.
|
||||
* @return the manifest.
|
||||
*/
|
||||
public static Modpack readCurseForgeModpackManifest(ZipFile zip, Charset encoding) throws IOException, JsonParseException {
|
||||
CurseManifest manifest = JsonUtils.fromNonNullJson(CompressingUtils.readTextZipEntry(zip, "manifest.json"), CurseManifest.class);
|
||||
String description = "No description";
|
||||
try {
|
||||
ZipArchiveEntry modlist = zip.getEntry("modlist.html");
|
||||
if (modlist != null)
|
||||
description = IOUtils.readFullyAsString(zip.getInputStream(modlist));
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), manifest.getMinecraft().getGameVersion(), description, encoding, manifest) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name) {
|
||||
return new CurseInstallTask(dependencyManager, zipFile, this, manifest, name);
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public ModpackProvider getProvider() {
|
||||
return CurseModpackProvider.INSTANCE;
|
||||
}
|
||||
|
||||
public static final String MINECRAFT_MODPACK = "minecraftModpack";
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.mod.curse;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
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.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public final class CurseModpackProvider implements ModpackProvider {
|
||||
public static final CurseModpackProvider INSTANCE = new CurseModpackProvider();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Curse";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {
|
||||
return new CurseCompletionTask(dependencyManager, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack) throws MismatchedModpackTypeException {
|
||||
if (!(modpack.getManifest() instanceof CurseManifest))
|
||||
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new CurseInstallTask(dependencyManager, zipFile, modpack, (CurseManifest) modpack.getManifest(), name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modpack readManifest(ZipFile zip, Path file, Charset encoding) throws IOException, JsonParseException {
|
||||
CurseManifest manifest = JsonUtils.fromNonNullJson(CompressingUtils.readTextZipEntry(zip, "manifest.json"), CurseManifest.class);
|
||||
String description = "No description";
|
||||
try {
|
||||
ZipArchiveEntry modlist = zip.getEntry("modlist.html");
|
||||
if (modlist != null)
|
||||
description = IOUtils.readFullyAsString(zip.getInputStream(modlist));
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), manifest.getMinecraft().getGameVersion(), description, encoding, manifest) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name) {
|
||||
return new CurseInstallTask(dependencyManager, zipFile, this, manifest, name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.ModpackCompletionException;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseMetaMod;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
@@ -269,9 +269,9 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
// Let this task fail if the curse manifest has not been completed.
|
||||
// But continue other downloads.
|
||||
if (notFound.get())
|
||||
throw new CurseCompletionException(new FileNotFoundException());
|
||||
throw new ModpackCompletionException(new FileNotFoundException());
|
||||
if (!allNameKnown.get() || ex != null)
|
||||
throw new CurseCompletionException();
|
||||
throw new ModpackCompletionException();
|
||||
})));
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -82,13 +83,13 @@ public class McbbsModpackLocalInstallTask extends Task<Void> {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<McbbsModpackManifest>>() {
|
||||
}.getType());
|
||||
|
||||
if (!MODPACK_TYPE.equals(config.getType()))
|
||||
if (!McbbsModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version.");
|
||||
}
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/overrides", any -> true, config).withStage("hmcl.modpack"));
|
||||
instanceTask = new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/overrides", manifest, MODPACK_TYPE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name));
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList("/overrides"), any -> true, config).withStage("hmcl.modpack"));
|
||||
instanceTask = new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/overrides"), manifest, McbbsModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name));
|
||||
dependents.add(instanceTask.withStage("hmcl.modpack"));
|
||||
}
|
||||
|
||||
@@ -122,5 +123,4 @@ public class McbbsModpackLocalInstallTask extends Task<Void> {
|
||||
}
|
||||
|
||||
private static final String PATCH_NAME = "mcbbs";
|
||||
public static final String MODPACK_TYPE = "Mcbbs";
|
||||
}
|
||||
|
||||
@@ -19,15 +19,14 @@ package org.jackhuang.hmcl.mod.mcbbs;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.*;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -41,7 +40,7 @@ import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
|
||||
|
||||
public class McbbsModpackManifest implements Validation {
|
||||
public class McbbsModpackManifest implements ModpackManifest, Validation {
|
||||
public static final String MANIFEST_TYPE = "minecraftModpack";
|
||||
|
||||
private final String manifestType;
|
||||
@@ -150,6 +149,11 @@ public class McbbsModpackManifest implements Validation {
|
||||
return new McbbsModpackManifest(manifestType, manifestVersion, name, version, author, description, fileApi, url, forceUpdate, origins, addons, libraries, files, settings, launchInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModpackProvider getProvider() {
|
||||
return McbbsModpackProvider.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
if (!MANIFEST_TYPE.equals(manifestType))
|
||||
@@ -431,27 +435,4 @@ public class McbbsModpackManifest implements Validation {
|
||||
launchOptions.getJavaArguments().addAll(launchInfo.getJavaArguments());
|
||||
}
|
||||
|
||||
private static Modpack fromManifestFile(String json, Charset encoding) throws IOException, JsonParseException {
|
||||
McbbsModpackManifest manifest = JsonUtils.fromNonNullJson(json, McbbsModpackManifest.class);
|
||||
return manifest.toModpack(encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zip the MCBBS modpack file.
|
||||
* @param encoding the modpack zip file encoding.
|
||||
* @throws IOException if the file is not a valid zip file.
|
||||
* @throws JsonParseException if the server-manifest.json is missing or malformed.
|
||||
* @return the manifest.
|
||||
*/
|
||||
public static Modpack readManifest(ZipFile zip, Charset encoding) throws IOException, JsonParseException {
|
||||
ZipArchiveEntry mcbbsPackMeta = zip.getEntry("mcbbs.packmeta");
|
||||
if (mcbbsPackMeta != null) {
|
||||
return fromManifestFile(IOUtils.readFullyAsString(zip.getInputStream(mcbbsPackMeta)), encoding);
|
||||
}
|
||||
ZipArchiveEntry manifestJson = zip.getEntry("manifest.json");
|
||||
if (manifestJson != null) {
|
||||
return fromManifestFile(IOUtils.readFullyAsString(zip.getInputStream(manifestJson)), encoding);
|
||||
}
|
||||
throw new IOException("`mcbbs.packmeta` or `manifest.json` cannot be found");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.mod.mcbbs;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public final class McbbsModpackProvider implements ModpackProvider {
|
||||
public static final McbbsModpackProvider INSTANCE = new McbbsModpackProvider();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Mcbbs";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {
|
||||
return new McbbsModpackCompletionTask(dependencyManager, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack) throws MismatchedModpackTypeException {
|
||||
if (!(modpack.getManifest() instanceof McbbsModpackManifest))
|
||||
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new McbbsModpackLocalInstallTask(dependencyManager, zipFile, modpack, (McbbsModpackManifest) modpack.getManifest(), name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectLaunchOptions(String modpackConfigurationJson, LaunchOptions.Builder builder) {
|
||||
ModpackConfiguration<McbbsModpackManifest> config = JsonUtils.GSON.fromJson(modpackConfigurationJson, new TypeToken<ModpackConfiguration<McbbsModpackManifest>>() {
|
||||
}.getType());
|
||||
|
||||
if (!getName().equals(config.getType())) {
|
||||
throw new IllegalArgumentException("Incorrect manifest type, actual=" + config.getType() + ", expected=" + getName());
|
||||
}
|
||||
|
||||
config.getManifest().injectLaunchOptions(builder);
|
||||
}
|
||||
|
||||
private static Modpack fromManifestFile(String json, Charset encoding) throws IOException, JsonParseException {
|
||||
McbbsModpackManifest manifest = JsonUtils.fromNonNullJson(json, McbbsModpackManifest.class);
|
||||
return manifest.toModpack(encoding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modpack readManifest(ZipFile zip, Path file, Charset encoding) throws IOException, JsonParseException {
|
||||
ZipArchiveEntry mcbbsPackMeta = zip.getEntry("mcbbs.packmeta");
|
||||
if (mcbbsPackMeta != null) {
|
||||
return fromManifestFile(IOUtils.readFullyAsString(zip.getInputStream(mcbbsPackMeta)), encoding);
|
||||
}
|
||||
ZipArchiveEntry manifestJson = zip.getEntry("manifest.json");
|
||||
if (manifestJson != null) {
|
||||
return fromManifestFile(IOUtils.readFullyAsString(zip.getInputStream(manifestJson)), encoding);
|
||||
}
|
||||
throw new IOException("`mcbbs.packmeta` or `manifest.json` cannot be found");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.mod.modrinth;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModpackCompletionException;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class ModrinthCompletionTask extends Task<Void> {
|
||||
|
||||
private final DefaultDependencyManager dependency;
|
||||
private final DefaultGameRepository repository;
|
||||
private final String version;
|
||||
private ModrinthManifest manifest;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>();
|
||||
|
||||
private final AtomicBoolean allNameKnown = new AtomicBoolean(true);
|
||||
private final AtomicInteger finished = new AtomicInteger(0);
|
||||
private final AtomicBoolean notFound = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager.
|
||||
* @param version the existent and physical version.
|
||||
*/
|
||||
public ModrinthCompletionTask(DefaultDependencyManager dependencyManager, String version) {
|
||||
this(dependencyManager, version, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager.
|
||||
* @param version the existent and physical version.
|
||||
* @param manifest the CurseForgeModpack manifest.
|
||||
*/
|
||||
public ModrinthCompletionTask(DefaultDependencyManager dependencyManager, String version, ModrinthManifest manifest) {
|
||||
this.dependency = dependencyManager;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
this.version = version;
|
||||
this.manifest = manifest;
|
||||
|
||||
if (manifest == null)
|
||||
try {
|
||||
File manifestFile = new File(repository.getVersionRoot(version), "modrinth.index.json");
|
||||
if (manifestFile.exists())
|
||||
this.manifest = JsonUtils.GSON.fromJson(FileUtils.readText(manifestFile), ModrinthManifest.class);
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to read Modrinth modpack manifest.json", e);
|
||||
}
|
||||
|
||||
setStage("hmcl.modpack.download");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRelyingOnDependencies() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (manifest == null)
|
||||
return;
|
||||
|
||||
Path runDirectory = repository.getRunDirectory(version).toPath();
|
||||
|
||||
for (ModrinthManifest.File file : manifest.getFiles()) {
|
||||
Path filePath = runDirectory.resolve(file.getPath());
|
||||
if (!Files.exists(filePath) && !file.getDownloads().isEmpty()) {
|
||||
FileDownloadTask task = new FileDownloadTask(file.getDownloads().get(0), filePath.toFile());
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter("hmcl.modpack.download"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!dependencies.isEmpty()) {
|
||||
getProperties().put("total", dependencies.size());
|
||||
notifyPropertiesChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPostExecute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postExecute() throws Exception {
|
||||
// Let this task fail if the curse manifest has not been completed.
|
||||
// But continue other downloads.
|
||||
if (notFound.get())
|
||||
throw new ModpackCompletionException(new FileNotFoundException());
|
||||
if (!allNameKnown.get() || !isDependenciesSucceeded())
|
||||
throw new ModpackCompletionException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.mod.modrinth;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseManifest;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class ModrinthInstallTask extends Task<Void> {
|
||||
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final DefaultGameRepository repository;
|
||||
private final File zipFile;
|
||||
private final Modpack modpack;
|
||||
private final ModrinthManifest manifest;
|
||||
private final String name;
|
||||
private final File run;
|
||||
private final ModpackConfiguration<ModrinthManifest> config;
|
||||
private final List<Task<?>> dependents = new ArrayList<>(4);
|
||||
private final List<Task<?>> dependencies = new ArrayList<>(1);
|
||||
|
||||
public ModrinthInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, ModrinthManifest manifest, String name) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.zipFile = zipFile;
|
||||
this.modpack = modpack;
|
||||
this.manifest = manifest;
|
||||
this.name = name;
|
||||
this.repository = dependencyManager.getGameRepository();
|
||||
this.run = repository.getRunDirectory(name);
|
||||
|
||||
File json = repository.getModpackConfiguration(name);
|
||||
if (repository.hasVersion(name) && !json.exists())
|
||||
throw new IllegalArgumentException("Version " + name + " already exists.");
|
||||
|
||||
GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion());
|
||||
for (Map.Entry<String, String> modLoader : manifest.getDependencies().entrySet()) {
|
||||
switch (modLoader.getKey()) {
|
||||
case "minecraft":
|
||||
break;
|
||||
case "forge":
|
||||
builder.version("forge", modLoader.getValue());
|
||||
break;
|
||||
case "fabric-loader":
|
||||
builder.version("fabric", modLoader.getValue());
|
||||
break;
|
||||
case "quilt-loader":
|
||||
throw new IllegalStateException("Quilt Modloader is not supported");
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported mod loader " + modLoader.getKey());
|
||||
}
|
||||
}
|
||||
dependents.add(builder.buildAsync());
|
||||
|
||||
onDone().register(event -> {
|
||||
Exception ex = event.getTask().getException();
|
||||
if (event.isFailed()) {
|
||||
if (!(ex instanceof ModpackCompletionException)) {
|
||||
repository.removeVersionFromDisk(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ModpackConfiguration<ModrinthManifest> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<CurseManifest>>() {
|
||||
}.getType());
|
||||
|
||||
if (!ModrinthModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a Modrinth modpack. Cannot update this version.");
|
||||
}
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
List<String> subDirectories = Arrays.asList("/client-overrides", "/overrides");
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), subDirectories, any -> true, config).withStage("hmcl.modpack"));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), subDirectories, manifest, ModrinthModpackProvider.INSTANCE, manifest.getName(), manifest.getVersionId(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
|
||||
dependencies.add(new ModrinthCompletionTask(dependencyManager, name, manifest));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return dependents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (config != null) {
|
||||
// For update, remove mods not listed in new manifest
|
||||
for (ModrinthManifest.File oldManifestFile : config.getManifest().getFiles()) {
|
||||
Path oldFile = run.toPath().resolve(oldManifestFile.getPath());
|
||||
if (!Files.exists(oldFile)) continue;
|
||||
if (manifest.getFiles().stream().noneMatch(oldManifestFile::equals)) {
|
||||
Files.deleteIfExists(oldFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File root = repository.getVersionRoot(name);
|
||||
FileUtils.writeText(new File(root, "modrinth.index.json"), JsonUtils.GSON.toJson(manifest));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.mod.modrinth;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.mod.ModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ModrinthManifest implements ModpackManifest, Validation {
|
||||
|
||||
private final String game;
|
||||
private final int formatVersion;
|
||||
private final String versionId;
|
||||
private final String name;
|
||||
@Nullable
|
||||
private final String summary;
|
||||
private final List<File> files;
|
||||
private final Map<String, String> dependencies;
|
||||
|
||||
public ModrinthManifest(String game, int formatVersion, String versionId, String name, @Nullable String summary, List<File> files, Map<String, String> dependencies) {
|
||||
this.game = game;
|
||||
this.formatVersion = formatVersion;
|
||||
this.versionId = versionId;
|
||||
this.name = name;
|
||||
this.summary = summary;
|
||||
this.files = files;
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
public String getGame() {
|
||||
return game;
|
||||
}
|
||||
|
||||
public int getFormatVersion() {
|
||||
return formatVersion;
|
||||
}
|
||||
|
||||
public String getVersionId() {
|
||||
return versionId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
return summary == null ? "" : summary;
|
||||
}
|
||||
|
||||
public List<File> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public Map<String, String> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return dependencies.get("minecraft");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModpackProvider getProvider() {
|
||||
return ModrinthModpackProvider.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
if (dependencies == null || dependencies.get("minecraft") == null) {
|
||||
throw new JsonParseException("missing Modrinth.dependencies.minecraft");
|
||||
}
|
||||
}
|
||||
|
||||
public static class File {
|
||||
private final String path;
|
||||
private final Map<String, String> hashes;
|
||||
private final Map<String, String> env;
|
||||
private final List<URL> downloads;
|
||||
private final int fileSize;
|
||||
|
||||
public File(String path, Map<String, String> hashes, Map<String, String> env, List<URL> downloads, int fileSize) {
|
||||
this.path = path;
|
||||
this.hashes = hashes;
|
||||
this.env = env;
|
||||
this.downloads = downloads;
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public Map<String, String> getHashes() {
|
||||
return hashes;
|
||||
}
|
||||
|
||||
public Map<String, String> getEnv() {
|
||||
return env;
|
||||
}
|
||||
|
||||
public List<URL> getDownloads() {
|
||||
return downloads;
|
||||
}
|
||||
|
||||
public int getFileSize() {
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
File file = (File) o;
|
||||
return fileSize == file.fileSize && path.equals(file.path) && hashes.equals(file.hashes) && env.equals(file.env) && downloads.equals(file.downloads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(path, hashes, env, downloads, fileSize);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.mod.modrinth;
|
||||
|
||||
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.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
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;
|
||||
|
||||
public final class ModrinthModpackProvider implements ModpackProvider {
|
||||
public static final ModrinthModpackProvider INSTANCE = new ModrinthModpackProvider();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Modrinth";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {
|
||||
return new ModrinthCompletionTask(dependencyManager, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack) throws MismatchedModpackTypeException {
|
||||
if (!(modpack.getManifest() instanceof ModrinthManifest))
|
||||
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new ModrinthInstallTask(dependencyManager, zipFile, modpack, (ModrinthManifest) modpack.getManifest(), name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modpack readManifest(ZipFile zip, Path file, Charset encoding) throws IOException, JsonParseException {
|
||||
ModrinthManifest manifest = JsonUtils.fromNonNullJson(CompressingUtils.readTextZipEntry(zip, "modrinth.index.json"), ModrinthManifest.class);
|
||||
return new Modpack(manifest.getName(), "", manifest.getVersionId(), manifest.getGameVersion(), manifest.getSummary(), encoding, manifest) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, java.io.File zipFile, String name) {
|
||||
return new ModrinthInstallTask(dependencyManager, zipFile, this, manifest, name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,17 +23,14 @@ import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.Hex;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -42,11 +39,15 @@ import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
public static final ModrinthRemoteModRepository INSTANCE = new ModrinthRemoteModRepository();
|
||||
public static final ModrinthRemoteModRepository MODS = new ModrinthRemoteModRepository("mod");
|
||||
public static final ModrinthRemoteModRepository MODPACKS = new ModrinthRemoteModRepository("modpack");
|
||||
|
||||
private static final String PREFIX = "https://api.modrinth.com";
|
||||
|
||||
private ModrinthRemoteModRepository() {
|
||||
private final String projectType;
|
||||
|
||||
private ModrinthRemoteModRepository(String projectType) {
|
||||
this.projectType = projectType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,19 +74,22 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
@Override
|
||||
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
|
||||
List<List<String>> facets = new ArrayList<>();
|
||||
facets.add(Collections.singletonList("project_type:" + projectType));
|
||||
if (StringUtils.isNotBlank(gameVersion)) {
|
||||
facets.add(Collections.singletonList("versions:" + gameVersion));
|
||||
}
|
||||
Map<String, String> query = mapOf(
|
||||
pair("query", searchFilter),
|
||||
pair("facets", JsonUtils.UGLY_GSON.toJson(facets)),
|
||||
pair("offset", Integer.toString(pageOffset)),
|
||||
pair("limit", Integer.toString(pageSize)),
|
||||
pair("index", convertSortType(sort))
|
||||
);
|
||||
if (StringUtils.isNotBlank(gameVersion)) {
|
||||
query.put("version", "versions=" + gameVersion);
|
||||
}
|
||||
Response<ModResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/api/v1/mod", query))
|
||||
.getJson(new TypeToken<Response<ModResult>>() {
|
||||
Response<ProjectSearchResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/v2/search", query))
|
||||
.getJson(new TypeToken<Response<ProjectSearchResult>>() {
|
||||
}.getType());
|
||||
return response.getHits().stream().map(ModResult::toMod);
|
||||
return response.getHits().stream().map(ProjectSearchResult::toMod);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,9 +97,9 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
String sha1 = Hex.encodeHex(DigestUtils.digest("SHA-1", file));
|
||||
|
||||
try {
|
||||
ModVersion mod = HttpRequest.GET(PREFIX + "/api/v1/version_file/" + sha1,
|
||||
ProjectVersion mod = HttpRequest.GET(PREFIX + "/v2/version_file/" + sha1,
|
||||
pair("algorithm", "sha1"))
|
||||
.getJson(ModVersion.class);
|
||||
.getJson(ProjectVersion.class);
|
||||
return mod.toVersion();
|
||||
} catch (ResponseCodeException e) {
|
||||
if (e.getResponseCode() == 404) {
|
||||
@@ -119,14 +123,14 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
@Override
|
||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||
id = StringUtils.removePrefix(id, "local-");
|
||||
List<ModVersion> versions = HttpRequest.GET("https://api.modrinth.com/api/v1/mod/" + id + "/version")
|
||||
.getJson(new TypeToken<List<ModVersion>>() {
|
||||
List<ProjectVersion> versions = HttpRequest.GET(PREFIX + "/v2/project/" + id + "/version")
|
||||
.getJson(new TypeToken<List<ProjectVersion>>() {
|
||||
}.getType());
|
||||
return versions.stream().map(ModVersion::toVersion).flatMap(Lang::toStream);
|
||||
return versions.stream().map(ProjectVersion::toVersion).flatMap(Lang::toStream);
|
||||
}
|
||||
|
||||
public List<String> getCategoriesImpl() throws IOException {
|
||||
return HttpRequest.GET("https://api.modrinth.com/api/v1/tag/category").getJson(new TypeToken<List<String>>() {
|
||||
return HttpRequest.GET(PREFIX + "/v2/tag/category").getJson(new TypeToken<List<String>>() {
|
||||
}.getType());
|
||||
}
|
||||
|
||||
@@ -135,56 +139,58 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
.map(name -> new Category(null, name, Collections.emptyList()));
|
||||
}
|
||||
|
||||
public static class Mod {
|
||||
private final String id;
|
||||
|
||||
public static class Project {
|
||||
private final String slug;
|
||||
|
||||
private final String team;
|
||||
|
||||
private final String title;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final Instant published;
|
||||
|
||||
private final Instant updated;
|
||||
|
||||
private final List<String> categories;
|
||||
|
||||
private final List<String> versions;
|
||||
/**
|
||||
* A long body describing project in detail.
|
||||
*/
|
||||
private final String body;
|
||||
|
||||
@SerializedName("project_type")
|
||||
private final String projectType;
|
||||
|
||||
private final int downloads;
|
||||
|
||||
@SerializedName("icon_url")
|
||||
private final String iconUrl;
|
||||
|
||||
public Mod(String id, String slug, String team, String title, String description, Instant published, Instant updated, List<String> categories, List<String> versions, int downloads, String iconUrl) {
|
||||
this.id = id;
|
||||
private final String id;
|
||||
|
||||
private final String team;
|
||||
|
||||
private final Date published;
|
||||
|
||||
private final Date updated;
|
||||
|
||||
private final List<String> versions;
|
||||
|
||||
public Project(String slug, String title, String description, List<String> categories, String body, String projectType, int downloads, String iconUrl, String id, String team, Date published, Date updated, List<String> versions) {
|
||||
this.slug = slug;
|
||||
this.team = team;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.published = published;
|
||||
this.updated = updated;
|
||||
this.categories = categories;
|
||||
this.versions = versions;
|
||||
this.body = body;
|
||||
this.projectType = projectType;
|
||||
this.downloads = downloads;
|
||||
this.iconUrl = iconUrl;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
this.id = id;
|
||||
this.team = team;
|
||||
this.published = published;
|
||||
this.updated = updated;
|
||||
this.versions = versions;
|
||||
}
|
||||
|
||||
public String getSlug() {
|
||||
return slug;
|
||||
}
|
||||
|
||||
public String getTeam() {
|
||||
return team;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
@@ -193,20 +199,16 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Instant getPublished() {
|
||||
return published;
|
||||
}
|
||||
|
||||
public Instant getUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public List<String> getCategories() {
|
||||
return categories;
|
||||
}
|
||||
|
||||
public List<String> getVersions() {
|
||||
return versions;
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public String getProjectType() {
|
||||
return projectType;
|
||||
}
|
||||
|
||||
public int getDownloads() {
|
||||
@@ -216,17 +218,59 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
public String getIconUrl() {
|
||||
return iconUrl;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getTeam() {
|
||||
return team;
|
||||
}
|
||||
|
||||
public Date getPublished() {
|
||||
return published;
|
||||
}
|
||||
|
||||
public Date getUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public List<String> getVersions() {
|
||||
return versions;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModVersion implements RemoteMod.IVersion {
|
||||
private final String id;
|
||||
@Immutable
|
||||
public static class Dependency {
|
||||
@SerializedName("version_id")
|
||||
private final String versionId;
|
||||
|
||||
@SerializedName("mod_id")
|
||||
private final String modId;
|
||||
@SerializedName("project_id")
|
||||
private final String projectId;
|
||||
|
||||
@SerializedName("author_id")
|
||||
private final String authorId;
|
||||
@SerializedName("dependency_type")
|
||||
private final String dependencyType;
|
||||
|
||||
public Dependency(String versionId, String projectId, String dependencyType) {
|
||||
this.versionId = versionId;
|
||||
this.projectId = projectId;
|
||||
this.dependencyType = dependencyType;
|
||||
}
|
||||
|
||||
public String getVersionId() {
|
||||
return versionId;
|
||||
}
|
||||
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public String getDependencyType() {
|
||||
return dependencyType;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProjectVersion implements RemoteMod.IVersion {
|
||||
private final String name;
|
||||
|
||||
@SerializedName("version_number")
|
||||
@@ -234,49 +278,52 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
private final String changelog;
|
||||
|
||||
private final List<Dependency> dependencies;
|
||||
|
||||
@SerializedName("game_versions")
|
||||
private final List<String> gameVersions;
|
||||
|
||||
@SerializedName("version_type")
|
||||
private final String versionType;
|
||||
|
||||
private final List<String> loaders;
|
||||
|
||||
private final boolean featured;
|
||||
|
||||
private final String id;
|
||||
|
||||
@SerializedName("project_id")
|
||||
private final String projectId;
|
||||
|
||||
@SerializedName("author_id")
|
||||
private final String authorId;
|
||||
|
||||
@SerializedName("date_published")
|
||||
private final Date datePublished;
|
||||
|
||||
private final int downloads;
|
||||
|
||||
@SerializedName("version_type")
|
||||
private final String versionType;
|
||||
@SerializedName("changelog_url")
|
||||
private final String changelogUrl;
|
||||
|
||||
private final List<ModVersionFile> files;
|
||||
private final List<ProjectVersionFile> files;
|
||||
|
||||
private final List<String> dependencies;
|
||||
|
||||
@SerializedName("game_versions")
|
||||
private final List<String> gameVersions;
|
||||
|
||||
private final List<String> loaders;
|
||||
|
||||
public ModVersion(String id, String modId, String authorId, String name, String versionNumber, String changelog, Date datePublished, int downloads, String versionType, List<ModVersionFile> files, List<String> dependencies, List<String> gameVersions, List<String> loaders) {
|
||||
this.id = id;
|
||||
this.modId = modId;
|
||||
this.authorId = authorId;
|
||||
public ProjectVersion(String name, String versionNumber, String changelog, List<Dependency> dependencies, List<String> gameVersions, String versionType, List<String> loaders, boolean featured, String id, String projectId, String authorId, Date datePublished, int downloads, String changelogUrl, List<ProjectVersionFile> files) {
|
||||
this.name = name;
|
||||
this.versionNumber = versionNumber;
|
||||
this.changelog = changelog;
|
||||
this.datePublished = datePublished;
|
||||
this.downloads = downloads;
|
||||
this.versionType = versionType;
|
||||
this.files = files;
|
||||
this.dependencies = dependencies;
|
||||
this.gameVersions = gameVersions;
|
||||
this.versionType = versionType;
|
||||
this.loaders = loaders;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getModId() {
|
||||
return modId;
|
||||
}
|
||||
|
||||
public String getAuthorId() {
|
||||
return authorId;
|
||||
this.featured = featured;
|
||||
this.id = id;
|
||||
this.projectId = projectId;
|
||||
this.authorId = authorId;
|
||||
this.datePublished = datePublished;
|
||||
this.downloads = downloads;
|
||||
this.changelogUrl = changelogUrl;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -291,6 +338,38 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
return changelog;
|
||||
}
|
||||
|
||||
public List<Dependency> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
public List<String> getGameVersions() {
|
||||
return gameVersions;
|
||||
}
|
||||
|
||||
public String getVersionType() {
|
||||
return versionType;
|
||||
}
|
||||
|
||||
public List<String> getLoaders() {
|
||||
return loaders;
|
||||
}
|
||||
|
||||
public boolean isFeatured() {
|
||||
return featured;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public String getAuthorId() {
|
||||
return authorId;
|
||||
}
|
||||
|
||||
public Date getDatePublished() {
|
||||
return datePublished;
|
||||
}
|
||||
@@ -299,26 +378,14 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
return downloads;
|
||||
}
|
||||
|
||||
public String getVersionType() {
|
||||
return versionType;
|
||||
public String getChangelogUrl() {
|
||||
return changelogUrl;
|
||||
}
|
||||
|
||||
public List<ModVersionFile> getFiles() {
|
||||
public List<ProjectVersionFile> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public List<String> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
public List<String> getGameVersions() {
|
||||
return gameVersions;
|
||||
}
|
||||
|
||||
public List<String> getLoaders() {
|
||||
return loaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteMod.Type getType() {
|
||||
return RemoteMod.Type.MODRINTH;
|
||||
@@ -342,14 +409,14 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
return Optional.of(new RemoteMod.Version(
|
||||
this,
|
||||
modId,
|
||||
projectId,
|
||||
name,
|
||||
versionNumber,
|
||||
changelog,
|
||||
datePublished,
|
||||
type,
|
||||
files.get(0).toFile(),
|
||||
dependencies,
|
||||
dependencies.stream().map(Dependency::getProjectId).collect(Collectors.toList()),
|
||||
gameVersions,
|
||||
loaders.stream().flatMap(loader -> {
|
||||
if ("fabric".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.FABRIC);
|
||||
@@ -360,15 +427,19 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModVersionFile {
|
||||
public static class ProjectVersionFile {
|
||||
private final Map<String, String> hashes;
|
||||
private final String url;
|
||||
private final String filename;
|
||||
private final boolean primary;
|
||||
private final int size;
|
||||
|
||||
public ModVersionFile(Map<String, String> hashes, String url, String filename) {
|
||||
public ProjectVersionFile(Map<String, String> hashes, String url, String filename, boolean primary, int size) {
|
||||
this.hashes = hashes;
|
||||
this.url = url;
|
||||
this.filename = filename;
|
||||
this.primary = primary;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public Map<String, String> getHashes() {
|
||||
@@ -383,76 +454,72 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public boolean isPrimary() {
|
||||
return primary;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public RemoteMod.File toFile() {
|
||||
return new RemoteMod.File(hashes, url, filename);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ModResult implements RemoteMod.IMod {
|
||||
@SerializedName("mod_id")
|
||||
private final String modId;
|
||||
|
||||
public static class ProjectSearchResult implements RemoteMod.IMod {
|
||||
private final String slug;
|
||||
|
||||
private final String author;
|
||||
|
||||
private final String title;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final List<String> categories;
|
||||
|
||||
private final List<String> versions;
|
||||
@SerializedName("project_type")
|
||||
private final String projectType;
|
||||
|
||||
private final int downloads;
|
||||
|
||||
@SerializedName("page_url")
|
||||
private final String pageUrl;
|
||||
|
||||
@SerializedName("icon_url")
|
||||
private final String iconUrl;
|
||||
|
||||
@SerializedName("author_url")
|
||||
private final String authorUrl;
|
||||
@SerializedName("project_id")
|
||||
private final String projectId;
|
||||
|
||||
private final String author;
|
||||
|
||||
private final List<String> versions;
|
||||
|
||||
@SerializedName("date_created")
|
||||
private final Instant dateCreated;
|
||||
private final Date dateCreated;
|
||||
|
||||
@SerializedName("date_modified")
|
||||
private final Instant dateModified;
|
||||
private final Date dateModified;
|
||||
|
||||
@SerializedName("latest_version")
|
||||
private final String latestVersion;
|
||||
|
||||
public ModResult(String modId, String slug, String author, String title, String description, List<String> categories, List<String> versions, int downloads, String pageUrl, String iconUrl, String authorUrl, Instant dateCreated, Instant dateModified, String latestVersion) {
|
||||
this.modId = modId;
|
||||
public ProjectSearchResult(String slug, String title, String description, List<String> categories, String projectType, int downloads, String iconUrl, String projectId, String author, List<String> versions, Date dateCreated, Date dateModified, String latestVersion) {
|
||||
this.slug = slug;
|
||||
this.author = author;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.categories = categories;
|
||||
this.versions = versions;
|
||||
this.projectType = projectType;
|
||||
this.downloads = downloads;
|
||||
this.pageUrl = pageUrl;
|
||||
this.iconUrl = iconUrl;
|
||||
this.authorUrl = authorUrl;
|
||||
this.projectId = projectId;
|
||||
this.author = author;
|
||||
this.versions = versions;
|
||||
this.dateCreated = dateCreated;
|
||||
this.dateModified = dateModified;
|
||||
this.latestVersion = latestVersion;
|
||||
}
|
||||
|
||||
public String getModId() {
|
||||
return modId;
|
||||
}
|
||||
|
||||
public String getSlug() {
|
||||
return slug;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
@@ -465,31 +532,35 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
return categories;
|
||||
}
|
||||
|
||||
public List<String> getVersions() {
|
||||
return versions;
|
||||
public String getProjectType() {
|
||||
return projectType;
|
||||
}
|
||||
|
||||
public int getDownloads() {
|
||||
return downloads;
|
||||
}
|
||||
|
||||
public String getPageUrl() {
|
||||
return pageUrl;
|
||||
}
|
||||
|
||||
public String getIconUrl() {
|
||||
return iconUrl;
|
||||
}
|
||||
|
||||
public String getAuthorUrl() {
|
||||
return authorUrl;
|
||||
public String getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public Instant getDateCreated() {
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public List<String> getVersions() {
|
||||
return versions;
|
||||
}
|
||||
|
||||
public Date getDateCreated() {
|
||||
return dateCreated;
|
||||
}
|
||||
|
||||
public Instant getDateModified() {
|
||||
public Date getDateModified() {
|
||||
return dateModified;
|
||||
}
|
||||
|
||||
@@ -504,7 +575,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
@Override
|
||||
public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository) throws IOException {
|
||||
return modRepository.getRemoteVersionsById(getModId());
|
||||
return modRepository.getRemoteVersionsById(getProjectId());
|
||||
}
|
||||
|
||||
public RemoteMod toMod() {
|
||||
@@ -514,7 +585,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
title,
|
||||
description,
|
||||
categories,
|
||||
pageUrl,
|
||||
String.format("https://modrinth.com/%s/%s", projectType, projectId),
|
||||
iconUrl,
|
||||
this
|
||||
);
|
||||
|
||||
@@ -17,32 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod.multimc;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.mod.ModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class MultiMCInstanceConfiguration {
|
||||
public final class MultiMCInstanceConfiguration implements ModpackManifest {
|
||||
|
||||
private final String instanceType; // InstanceType
|
||||
private final String name; // name
|
||||
@@ -71,7 +61,7 @@ public final class MultiMCInstanceConfiguration {
|
||||
|
||||
private final MultiMCManifest mmcPack;
|
||||
|
||||
private MultiMCInstanceConfiguration(String defaultName, InputStream contentStream, MultiMCManifest mmcPack) throws IOException {
|
||||
MultiMCInstanceConfiguration(String defaultName, InputStream contentStream, MultiMCManifest mmcPack) throws IOException {
|
||||
Properties p = new Properties();
|
||||
p.load(new InputStreamReader(contentStream, StandardCharsets.UTF_8));
|
||||
|
||||
@@ -335,58 +325,9 @@ public final class MultiMCInstanceConfiguration {
|
||||
return mmcPack;
|
||||
}
|
||||
|
||||
private static boolean testPath(Path root) {
|
||||
return Files.exists(root.resolve("instance.cfg"));
|
||||
}
|
||||
|
||||
public static Path getRootPath(Path root) throws IOException {
|
||||
if (testPath(root)) return root;
|
||||
try (Stream<Path> stream = Files.list(root)) {
|
||||
Path candidate = stream.filter(Files::isDirectory).findAny()
|
||||
.orElseThrow(() -> new IOException("Not a valid MultiMC modpack"));
|
||||
if (testPath(candidate)) return candidate;
|
||||
throw new IOException("Not a valid MultiMC modpack");
|
||||
}
|
||||
}
|
||||
|
||||
public static String getRootEntryName(ZipFile file) throws IOException {
|
||||
final String instanceFileName = "instance.cfg";
|
||||
|
||||
if (file.getEntry(instanceFileName) != null) return "";
|
||||
|
||||
Enumeration<ZipArchiveEntry> entries = file.getEntries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipArchiveEntry entry = entries.nextElement();
|
||||
String entryName = entry.getName();
|
||||
|
||||
int idx = entryName.indexOf('/');
|
||||
if (idx >= 0
|
||||
&& entryName.length() == idx + instanceFileName.length() + 1
|
||||
&& entryName.startsWith(instanceFileName, idx + 1))
|
||||
return entryName.substring(0, idx + 1);
|
||||
}
|
||||
|
||||
throw new IOException("Not a valid MultiMC modpack");
|
||||
}
|
||||
|
||||
public static Modpack readMultiMCModpackManifest(ZipFile modpackFile, Path modpackPath, Charset encoding) throws IOException {
|
||||
String rootEntryName = getRootEntryName(modpackFile);
|
||||
MultiMCManifest manifest = MultiMCManifest.readMultiMCModpackManifest(modpackFile, rootEntryName);
|
||||
|
||||
String name = rootEntryName.isEmpty() ? FileUtils.getNameWithoutExtension(modpackPath) : rootEntryName.substring(0, rootEntryName.length() - 1);
|
||||
ZipArchiveEntry instanceEntry = modpackFile.getEntry(rootEntryName + "instance.cfg");
|
||||
|
||||
if (instanceEntry == null)
|
||||
throw new IOException("`instance.cfg` not found, " + modpackFile + " is not a valid MultiMC modpack.");
|
||||
try (InputStream instanceStream = modpackFile.getInputStream(instanceEntry)) {
|
||||
MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, instanceStream, manifest);
|
||||
return new Modpack(cfg.getName(), "", "", cfg.getGameVersion(), cfg.getNotes(), encoding, cfg) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name) {
|
||||
return new MultiMCModpackInstallTask(dependencyManager, zipFile, this, cfg, name);
|
||||
}
|
||||
};
|
||||
}
|
||||
@Override
|
||||
public ModpackProvider getProvider() {
|
||||
return MultiMCModpackProvider.INSTANCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
@Immutable
|
||||
public final class MultiMCManifest {
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -118,20 +119,26 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<MultiMCInstanceConfiguration>>() {
|
||||
}.getType());
|
||||
|
||||
if (!MODPACK_TYPE.equals(config.getType()))
|
||||
if (!MultiMCModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a MultiMC modpack. Cannot update this version.");
|
||||
}
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
|
||||
String subDirectory;
|
||||
|
||||
try (FileSystem fs = CompressingUtils.readonly(zipFile.toPath()).setEncoding(modpack.getEncoding()).build()) {
|
||||
if (Files.exists(fs.getPath("/" + manifest.getName() + "/.minecraft")))
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/" + manifest.getName() + "/.minecraft", any -> true, config).withStage("hmcl.modpack"));
|
||||
else if (Files.exists(fs.getPath("/" + manifest.getName() + "/minecraft")))
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", any -> true, config).withStage("hmcl.modpack"));
|
||||
if (Files.exists(fs.getPath("/" + manifest.getName() + "/.minecraft"))) {
|
||||
subDirectory = "/" + manifest.getName() + "/.minecraft";
|
||||
} else if (Files.exists(fs.getPath("/" + manifest.getName() + "/minecraft"))) {
|
||||
subDirectory = "/" + manifest.getName() + "/minecraft";
|
||||
} else {
|
||||
subDirectory = "/" + manifest.getName() + "/minecraft";
|
||||
}
|
||||
}
|
||||
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/" + manifest.getName() + "/minecraft", manifest, MODPACK_TYPE, manifest.getName(), null, repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(subDirectory), any -> true, config).withStage("hmcl.modpack"));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(subDirectory), manifest, MultiMCModpackProvider.INSTANCE, manifest.getName(), null, repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,7 +151,7 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
Version version = repository.readVersionJson(name);
|
||||
|
||||
try (FileSystem fs = CompressingUtils.readonly(zipFile.toPath()).setAutoDetectEncoding(true).build()) {
|
||||
Path root = MultiMCInstanceConfiguration.getRootPath(fs.getPath("/"));
|
||||
Path root = MultiMCModpackProvider.getRootPath(fs.getPath("/"));
|
||||
Path patches = root.resolve("patches");
|
||||
|
||||
if (Files.exists(patches)) {
|
||||
@@ -178,6 +185,4 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
|
||||
dependencies.add(repository.saveAsync(version));
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "MultiMC";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.mod.multimc;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
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.task.Task;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Enumeration;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class MultiMCModpackProvider implements ModpackProvider {
|
||||
public static final MultiMCModpackProvider INSTANCE = new MultiMCModpackProvider();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MultiMC";
|
||||
}
|
||||
|
||||
@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 MultiMCInstanceConfiguration))
|
||||
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new MultiMCModpackInstallTask(dependencyManager, zipFile, modpack, (MultiMCInstanceConfiguration) modpack.getManifest(), name));
|
||||
}
|
||||
|
||||
private static boolean testPath(Path root) {
|
||||
return Files.exists(root.resolve("instance.cfg"));
|
||||
}
|
||||
|
||||
public static Path getRootPath(Path root) throws IOException {
|
||||
if (testPath(root)) return root;
|
||||
try (Stream<Path> stream = Files.list(root)) {
|
||||
Path candidate = stream.filter(Files::isDirectory).findAny()
|
||||
.orElseThrow(() -> new IOException("Not a valid MultiMC modpack"));
|
||||
if (testPath(candidate)) return candidate;
|
||||
throw new IOException("Not a valid MultiMC modpack");
|
||||
}
|
||||
}
|
||||
|
||||
private static String getRootEntryName(ZipFile file) throws IOException {
|
||||
final String instanceFileName = "instance.cfg";
|
||||
|
||||
if (file.getEntry(instanceFileName) != null) return "";
|
||||
|
||||
Enumeration<ZipArchiveEntry> entries = file.getEntries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipArchiveEntry entry = entries.nextElement();
|
||||
String entryName = entry.getName();
|
||||
|
||||
int idx = entryName.indexOf('/');
|
||||
if (idx >= 0
|
||||
&& entryName.length() == idx + instanceFileName.length() + 1
|
||||
&& entryName.startsWith(instanceFileName, idx + 1))
|
||||
return entryName.substring(0, idx + 1);
|
||||
}
|
||||
|
||||
throw new IOException("Not a valid MultiMC modpack");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modpack readManifest(ZipFile modpackFile, Path modpackPath, Charset encoding) throws IOException {
|
||||
String rootEntryName = getRootEntryName(modpackFile);
|
||||
MultiMCManifest manifest = MultiMCManifest.readMultiMCModpackManifest(modpackFile, rootEntryName);
|
||||
|
||||
String name = rootEntryName.isEmpty() ? FileUtils.getNameWithoutExtension(modpackPath) : rootEntryName.substring(0, rootEntryName.length() - 1);
|
||||
ZipArchiveEntry instanceEntry = modpackFile.getEntry(rootEntryName + "instance.cfg");
|
||||
|
||||
if (instanceEntry == null)
|
||||
throw new IOException("`instance.cfg` not found, " + modpackFile + " is not a valid MultiMC modpack.");
|
||||
try (InputStream instanceStream = modpackFile.getInputStream(instanceEntry)) {
|
||||
MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, instanceStream, manifest);
|
||||
return new Modpack(cfg.getName(), "", "", cfg.getGameVersion(), cfg.getNotes(), encoding, cfg) {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name) {
|
||||
return new MultiMCModpackInstallTask(dependencyManager, zipFile, this, cfg, name);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,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 class ServerModpackLocalInstallTask extends Task<Void> {
|
||||
@@ -74,13 +75,13 @@ public class ServerModpackLocalInstallTask extends Task<Void> {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<ServerModpackManifest>>() {
|
||||
}.getType());
|
||||
|
||||
if (!MODPACK_TYPE.equals(config.getType()))
|
||||
if (!ServerModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version.");
|
||||
}
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/overrides", any -> true, config).withStage("hmcl.modpack"));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/overrides", manifest, MODPACK_TYPE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList("/overrides"), any -> true, config).withStage("hmcl.modpack"));
|
||||
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/overrides"), manifest, ServerModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,6 +97,4 @@ public class ServerModpackLocalInstallTask extends Task<Void> {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "Server";
|
||||
}
|
||||
|
||||
@@ -18,15 +18,14 @@
|
||||
package org.jackhuang.hmcl.mod.server;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.ModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -36,7 +35,7 @@ import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
|
||||
|
||||
public class ServerModpackManifest implements Validation {
|
||||
public class ServerModpackManifest implements ModpackManifest, Validation {
|
||||
private final String name;
|
||||
private final String author;
|
||||
private final String version;
|
||||
@@ -87,6 +86,11 @@ public class ServerModpackManifest implements Validation {
|
||||
return addons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModpackProvider getProvider() {
|
||||
return ServerModpackProvider.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
if (fileApi == null)
|
||||
@@ -128,15 +132,4 @@ public class ServerModpackManifest implements Validation {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zip the CurseForge modpack file.
|
||||
* @throws IOException if the file is not a valid zip file.
|
||||
* @throws JsonParseException if the server-manifest.json is missing or malformed.
|
||||
* @return the manifest.
|
||||
*/
|
||||
public static Modpack readManifest(ZipFile zip, Charset encoding) throws IOException, JsonParseException {
|
||||
String json = CompressingUtils.readTextZipEntry(zip, "server-manifest.json");
|
||||
ServerModpackManifest manifest = JsonUtils.fromNonNullJson(json, ServerModpackManifest.class);
|
||||
return manifest.toModpack(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.mod.server;
|
||||
|
||||
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.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
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;
|
||||
|
||||
public final class ServerModpackProvider implements ModpackProvider {
|
||||
public static final ServerModpackProvider INSTANCE = new ServerModpackProvider();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Server";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {
|
||||
return new ServerModpackCompletionTask(dependencyManager, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack) throws MismatchedModpackTypeException {
|
||||
if (!(modpack.getManifest() instanceof ServerModpackManifest))
|
||||
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new ServerModpackLocalInstallTask(dependencyManager, zipFile, modpack, (ServerModpackManifest) modpack.getManifest(), name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modpack readManifest(ZipFile zip, Path file, Charset encoding) throws IOException, JsonParseException {
|
||||
String json = CompressingUtils.readTextZipEntry(zip, "server-manifest.json");
|
||||
ServerModpackManifest manifest = JsonUtils.fromNonNullJson(json, ServerModpackManifest.class);
|
||||
return manifest.toModpack(encoding);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user