Skip to content

Support for packwiz-based modpacks #577

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/me/itzg/helpers/McImageHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import me.itzg.helpers.modrinth.InstallModrinthModpackCommand;
import me.itzg.helpers.modrinth.ModrinthCommand;
import me.itzg.helpers.mvn.MavenDownloadCommand;
import me.itzg.helpers.packwiz.InstallPackwizModpackCommand;
import me.itzg.helpers.paper.InstallPaperCommand;
import me.itzg.helpers.patch.PatchCommand;
import me.itzg.helpers.properties.SetPropertiesCommand;
Expand Down Expand Up @@ -75,6 +76,7 @@
InstallForgeCommand.class,
InstallModrinthModpackCommand.class,
InstallNeoForgeCommand.class,
InstallPackwizModpackCommand.class,
InstallPaperCommand.class,
InstallPurpurCommand.class,
InstallQuiltCommand.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package me.itzg.helpers.packwiz;

import com.fasterxml.jackson.dataformat.toml.TomlMapper;
import lombok.extern.slf4j.Slf4j;
import me.itzg.helpers.errors.GenericException;
import me.itzg.helpers.errors.InvalidParameterException;
import me.itzg.helpers.fabric.FabricLauncherInstaller;
import me.itzg.helpers.files.Manifests;
import me.itzg.helpers.files.ResultsFileWriter;
import me.itzg.helpers.forge.ForgeInstaller;
import me.itzg.helpers.forge.ForgeInstallerResolver;
import me.itzg.helpers.forge.NeoForgeInstallerResolver;
import me.itzg.helpers.http.Fetch;
import me.itzg.helpers.http.SharedFetch;
import me.itzg.helpers.http.SharedFetchArgs;
import me.itzg.helpers.modrinth.ModrinthModpackManifest;
import me.itzg.helpers.mvn.MavenRepoApi;
import me.itzg.helpers.packwiz.model.PackwizPack;
import me.itzg.helpers.quilt.QuiltInstaller;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.ExitCode;
import picocli.CommandLine.Option;
import java.io.File;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.concurrent.Callable;

@Command(name = "install-packwiz-modpack",
description = "Supports installation of Packwiz modpacks along with the associated mod loader",
mixinStandardHelpOptions = true
)
@Slf4j
public final class InstallPackwizModpackCommand implements Callable<Integer> {
@Option(names = "--pack", required = true, description = "The path or URL to the modpack's pack.toml")
String pack;

@Option(names = "--output-directory", defaultValue = ".", paramLabel = "DIR")
Path outputDirectory;

@Option(names = "--results-file", description = ResultsFileWriter.OPTION_DESCRIPTION, paramLabel = "FILE")
Path resultsFile;

@Option(names = "--force-update", defaultValue = "${env:PACKWIZ_FORCE_UPDATE:-false}",
description = "Force update the pack even when the version hasn't changed (default: ${DEFAULT-VALUE})"
)
boolean forceUpdate;

@ArgGroup(exclusive = false)
SharedFetchArgs sharedFetchArgs = new SharedFetchArgs();

@Override
public Integer call() throws IOException {
final PackwizModpackManifest prevManifest = Manifests.load(
outputDirectory, PackwizModpackManifest.ID,
PackwizModpackManifest.class
);

final PackwizModpackManifest newManifest;
try (SharedFetch fetch = Fetch.sharedFetch("install-packwiz-modpack", sharedFetchArgs.options())) {
newManifest = getManifest(fetch);
if (prevManifest != null && prevManifest.getVersion().equals(newManifest.getVersion()) && !forceUpdate) {
return ExitCode.OK;
}

final String minecraftVersion = newManifest.minecraftVersion();
if (minecraftVersion == null) {
throw new GenericException("Minecraft version not set in packwiz pack");
}
// TODO might need to set/save the Minecraft version somewhere

installModLoader(newManifest, fetch);
bootstrapPackwiz(fetch);

// populate the results file (MODPACK_NAME, MODPACK_VERSION)
}

Manifests.cleanup(outputDirectory, prevManifest, newManifest, log);
Manifests.save(outputDirectory, ModrinthModpackManifest.ID, newManifest);

return ExitCode.OK;
}

private void bootstrapPackwiz(SharedFetch fetch) throws IOException {
// if ! packwizInstaller=$(mc-image-helper maven-download \
// --maven-repo=https://maven.packwiz.infra.link/repository/release/ \
// --group=link.infra.packwiz --artifact=packwiz-installer --classifier=dist \
// --skip-existing); then
//
//
//
// java -cp "${packwizInstaller}" link.infra.packwiz.installer.Main -s server "${PACKWIZ_URL}"; then

final MavenRepoApi mavenRepoApi = new MavenRepoApi("https://maven.packwiz.infra.link/repository/release/", fetch)
.setMetadataCacheDir(outputDirectory.resolve(".maven"))
.setSkipExisting(true)
.setSkipUpToDate(false);

final String group = "link.infra.packwiz";
final String artifactId = "packwiz-installer";
final Path installerJar = mavenRepoApi.fetchMetadata(group, artifactId)
.flatMap(metadata -> mavenRepoApi.download(
outputDirectory, group, artifactId, metadata.getVersioning().getRelease(), "jar", "dist"
))
.block();

final Process process = new ProcessBuilder(
"java",
"-cp", installerJar.toAbsolutePath().toString(),
"link.infra.packwiz.installer.Main",
"-s", "server",
pack)
.directory(outputDirectory.toFile())
.redirectOutput(Redirect.INHERIT)
.redirectError(Redirect.INHERIT)
.start();
// TODO check exit code
}

private void installModLoader(PackwizModpackManifest manifest, SharedFetch fetch) {
final String minecraftVersion = manifest.minecraftVersion();
// TODO support forceReinstall parameters (the falses)
// check NeoForge and Forge first, in case the pack is using Fabric mods on (Neo)Forge via Sinytra Connector or similar
// (packwiz requires you to declare a fabric version in your pack.toml to add fabric mods to a (neo)forge-based pack)
final String neoforgeVersion = manifest.neoforgeVersion();
if (neoforgeVersion != null) {
new ForgeInstaller(new NeoForgeInstallerResolver(fetch, minecraftVersion, neoforgeVersion))
.install(outputDirectory, resultsFile, false, "NeoForge");
return;
}

final String forgeVersion = manifest.forgeVersion();
if (forgeVersion != null) {
new ForgeInstaller(new ForgeInstallerResolver(fetch, minecraftVersion, forgeVersion))
.install(outputDirectory, resultsFile, false, "Forge");
return;
}

final String fabricVersion = manifest.fabricVersion();
if (fabricVersion != null) {
new FabricLauncherInstaller(outputDirectory)
.setResultsFile(resultsFile)
.installUsingVersions(sharedFetchArgs.options(), minecraftVersion, fabricVersion, null);
return;
}

final String quiltVersion = manifest.quiltVersion();
if (quiltVersion != null) {
try (QuiltInstaller installer = new QuiltInstaller(
QuiltInstaller.DEFAULT_REPO_URL,
sharedFetchArgs.options(),
outputDirectory,
minecraftVersion
).setResultsFile(resultsFile)) {
installer.installWithVersion(null, quiltVersion);
}
}
}

private PackwizModpackManifest getManifest(SharedFetch fetch) throws IOException {
URI uri;
try {
uri = new URI(pack);
} catch (URISyntaxException e) {
throw new InvalidParameterException("Could not parse packwiz modpack URI/path", e);
}

final PackwizPack packFile;
if (uri.getScheme() == null || uri.getScheme().equals("file")) {
log.debug("Fetching packwiz modpack from file {}", uri.getPath());
packFile = new TomlMapper().readValue(new File(uri.getPath()), PackwizPack.class);
} else {
log.debug("Fetching packwiz modpack from URL {}", uri);
packFile = fetch.fetch(uri).toObject(PackwizPack.class, new TomlMapper()).execute();
}

log.debug(
"Found packwiz pack with name={}, author={}, version={}, dependencies={}",
packFile.getName(),
packFile.getAuthor(),
packFile.getVersion(),
packFile.getVersions()
);

return PackwizModpackManifest.builder()
.name(packFile.getName())
.author(packFile.getAuthor())
.version(packFile.getVersion())
.dependencies(packFile.getVersions())
.files(Collections.emptyList())
.build();
}
}
41 changes: 41 additions & 0 deletions src/main/java/me/itzg/helpers/packwiz/PackwizModpackManifest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package me.itzg.helpers.packwiz;

import lombok.Getter;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import me.itzg.helpers.files.BaseManifest;
import java.util.Map;

@SuperBuilder
@Getter
@Jacksonized
public final class PackwizModpackManifest extends BaseManifest
{
public static final String ID = "packwiz-modpack";

private String name;
private String author;
private String version;
// string -> string because the versions can have arbitrary entries
private Map<String, String> dependencies;

public String neoforgeVersion() {
return dependencies.get("neoforge");
}

public String forgeVersion() {
return dependencies.get("forge");
}

public String fabricVersion() {
return dependencies.get("fabric");
}

public String quiltVersion() {
return dependencies.get("quilt");
}

public String minecraftVersion() {
return dependencies.get("minecraft");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package me.itzg.helpers.packwiz.model;

public enum PackwizModLoader {
NEOFORGE,
FABRIC,
FORGE,
QUILT,
}
14 changes: 14 additions & 0 deletions src/main/java/me/itzg/helpers/packwiz/model/PackwizPack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package me.itzg.helpers.packwiz.model;

import lombok.Data;
import java.util.HashMap;
import java.util.Map;

@Data
public final class PackwizPack {
private String name;
private String author;
private String version;

private Map<String, String> versions = new HashMap<>();
}