From 472892adf79171f0a11256c9d00edaaceadd28a4 Mon Sep 17 00:00:00 2001 From: Josh Cotton Date: Fri, 23 May 2025 19:30:52 -0700 Subject: [PATCH 1/5] Checkpoint. --- .../java/me/itzg/helpers/McImageHelper.java | 2 + .../packwiz/InstallPackwizModpackCommand.java | 142 ++++++++++++++++++ .../packwiz/PackwizModpackManifest.java | 41 +++++ .../packwiz/model/PackwizModLoader.java | 8 + .../helpers/packwiz/model/PackwizPack.java | 14 ++ 5 files changed, 207 insertions(+) create mode 100644 src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java create mode 100644 src/main/java/me/itzg/helpers/packwiz/PackwizModpackManifest.java create mode 100644 src/main/java/me/itzg/helpers/packwiz/model/PackwizModLoader.java create mode 100644 src/main/java/me/itzg/helpers/packwiz/model/PackwizPack.java diff --git a/src/main/java/me/itzg/helpers/McImageHelper.java b/src/main/java/me/itzg/helpers/McImageHelper.java index ebb5357b..9f2e4fed 100644 --- a/src/main/java/me/itzg/helpers/McImageHelper.java +++ b/src/main/java/me/itzg/helpers/McImageHelper.java @@ -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; @@ -75,6 +76,7 @@ InstallForgeCommand.class, InstallModrinthModpackCommand.class, InstallNeoForgeCommand.class, + InstallPackwizModpackCommand.class, InstallPaperCommand.class, InstallPurpurCommand.class, InstallQuiltCommand.class, diff --git a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java new file mode 100644 index 00000000..84d9a0df --- /dev/null +++ b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java @@ -0,0 +1,142 @@ +package me.itzg.helpers.packwiz; + +import com.fasterxml.jackson.dataformat.toml.TomlMapper; +import lombok.extern.slf4j.Slf4j; +import me.itzg.helpers.errors.InvalidParameterException; +import me.itzg.helpers.files.Manifests; +import me.itzg.helpers.files.ResultsFileWriter; +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.packwiz.model.PackwizModLoader; +import me.itzg.helpers.packwiz.model.PackwizPack; +import org.apache.commons.lang3.tuple.Pair; +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.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 { + @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 updating 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 Pair loaderInfo = getModLoader(newManifest); + if (loaderInfo != null) { + final PackwizModLoader loader = loaderInfo.getLeft(); + final String version = loaderInfo.getRight(); + // TODO install modloader, see ModrinthPackInstaller::applyModLoader + } + + // might need to set the Minecraft version somewhere + final String minecraftVersion = newManifest.minecraftVersion(); + // populate the results file + // run packwiz bootstrap + } + + Manifests.cleanup(outputDirectory, prevManifest, newManifest, log); + Manifests.save(outputDirectory, ModrinthModpackManifest.ID, newManifest); + + return ExitCode.OK; + } + + private Pair getModLoader(PackwizModpackManifest manifest) { + // 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 neoforge = manifest.neoforgeVersion(); + if (neoforge != null) { + return Pair.of(PackwizModLoader.NEOFORGE, neoforge); + } + + final String forge = manifest.forgeVersion(); + if (forge != null) { + return Pair.of(PackwizModLoader.FORGE, forge); + } + + final String fabric = manifest.fabricVersion(); + if (fabric != null) { + return Pair.of(PackwizModLoader.FABRIC, fabric); + } + + final String quilt = manifest.quiltVersion(); + if (quilt != null) { + return Pair.of(PackwizModLoader.QUILT, quilt); + } + + return null; + } + + 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(); + } +} diff --git a/src/main/java/me/itzg/helpers/packwiz/PackwizModpackManifest.java b/src/main/java/me/itzg/helpers/packwiz/PackwizModpackManifest.java new file mode 100644 index 00000000..0b8ee366 --- /dev/null +++ b/src/main/java/me/itzg/helpers/packwiz/PackwizModpackManifest.java @@ -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 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"); + } +} diff --git a/src/main/java/me/itzg/helpers/packwiz/model/PackwizModLoader.java b/src/main/java/me/itzg/helpers/packwiz/model/PackwizModLoader.java new file mode 100644 index 00000000..60734902 --- /dev/null +++ b/src/main/java/me/itzg/helpers/packwiz/model/PackwizModLoader.java @@ -0,0 +1,8 @@ +package me.itzg.helpers.packwiz.model; + +public enum PackwizModLoader { + NEOFORGE, + FABRIC, + FORGE, + QUILT, +} diff --git a/src/main/java/me/itzg/helpers/packwiz/model/PackwizPack.java b/src/main/java/me/itzg/helpers/packwiz/model/PackwizPack.java new file mode 100644 index 00000000..3ff73fac --- /dev/null +++ b/src/main/java/me/itzg/helpers/packwiz/model/PackwizPack.java @@ -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 versions = new HashMap<>(); +} From 53f40e8263960e31d3a2818e237a880eacf4fd9e Mon Sep 17 00:00:00 2001 From: Josh Cotton Date: Fri, 23 May 2025 19:53:01 -0700 Subject: [PATCH 2/5] Install mod loader. diff --git a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java index 84d9a0d..032bd27 100644 --- a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java +++ b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java @@ -2,15 +2,21 @@ 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.packwiz.model.PackwizModLoader; import me.itzg.helpers.packwiz.model.PackwizPack; +import me.itzg.helpers.quilt.QuiltInstaller; import org.apache.commons.lang3.tuple.Pair; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; @@ -61,15 +67,19 @@ public final class InstallPackwizModpackCommand implements Callable { 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 + final Pair loaderInfo = getModLoader(newManifest); if (loaderInfo != null) { final PackwizModLoader loader = loaderInfo.getLeft(); final String version = loaderInfo.getRight(); - // TODO install modloader, see ModrinthPackInstaller::applyModLoader + installModLoader(fetch, loader, version, minecraftVersion); } - // might need to set the Minecraft version somewhere - final String minecraftVersion = newManifest.minecraftVersion(); // populate the results file // run packwiz bootstrap } @@ -80,6 +90,35 @@ public final class InstallPackwizModpackCommand implements Callable { return ExitCode.OK; } + private void installModLoader(SharedFetch fetch, PackwizModLoader loader, String loaderVersion, String minecraftVersion) { + // TODO support forceReinstall parameters (the falses) + switch (loader) { + case NEOFORGE: + new ForgeInstaller(new NeoForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) + .install(outputDirectory, resultsFile, false, "NeoForge"); + break; + case FORGE: + new ForgeInstaller(new ForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) + .install(outputDirectory, resultsFile, false, "Forge"); + break; + case FABRIC: + new FabricLauncherInstaller(outputDirectory) + .setResultsFile(resultsFile) + .installUsingVersions(sharedFetchArgs.options(), minecraftVersion, loaderVersion, null); + break; + case QUILT: + try (QuiltInstaller installer = new QuiltInstaller( + QuiltInstaller.DEFAULT_REPO_URL, + sharedFetchArgs.options(), + outputDirectory, + minecraftVersion + ).setResultsFile(resultsFile)) { + installer.installWithVersion(null, loaderVersion); + } + break; + } + } + private Pair getModLoader(PackwizModpackManifest manifest) { // 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) --- .../packwiz/InstallPackwizModpackCommand.java | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java index 84d9a0df..032bd279 100644 --- a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java +++ b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java @@ -2,15 +2,21 @@ 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.packwiz.model.PackwizModLoader; import me.itzg.helpers.packwiz.model.PackwizPack; +import me.itzg.helpers.quilt.QuiltInstaller; import org.apache.commons.lang3.tuple.Pair; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; @@ -61,15 +67,19 @@ public Integer call() throws IOException { 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 + final Pair loaderInfo = getModLoader(newManifest); if (loaderInfo != null) { final PackwizModLoader loader = loaderInfo.getLeft(); final String version = loaderInfo.getRight(); - // TODO install modloader, see ModrinthPackInstaller::applyModLoader + installModLoader(fetch, loader, version, minecraftVersion); } - // might need to set the Minecraft version somewhere - final String minecraftVersion = newManifest.minecraftVersion(); // populate the results file // run packwiz bootstrap } @@ -80,6 +90,35 @@ public Integer call() throws IOException { return ExitCode.OK; } + private void installModLoader(SharedFetch fetch, PackwizModLoader loader, String loaderVersion, String minecraftVersion) { + // TODO support forceReinstall parameters (the falses) + switch (loader) { + case NEOFORGE: + new ForgeInstaller(new NeoForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) + .install(outputDirectory, resultsFile, false, "NeoForge"); + break; + case FORGE: + new ForgeInstaller(new ForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) + .install(outputDirectory, resultsFile, false, "Forge"); + break; + case FABRIC: + new FabricLauncherInstaller(outputDirectory) + .setResultsFile(resultsFile) + .installUsingVersions(sharedFetchArgs.options(), minecraftVersion, loaderVersion, null); + break; + case QUILT: + try (QuiltInstaller installer = new QuiltInstaller( + QuiltInstaller.DEFAULT_REPO_URL, + sharedFetchArgs.options(), + outputDirectory, + minecraftVersion + ).setResultsFile(resultsFile)) { + installer.installWithVersion(null, loaderVersion); + } + break; + } + } + private Pair getModLoader(PackwizModpackManifest manifest) { // 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) From d798dd9713c19a72d51cc95a7e650cd01d4ff6e2 Mon Sep 17 00:00:00 2001 From: Josh Cotton Date: Fri, 23 May 2025 19:53:48 -0700 Subject: [PATCH 3/5] Fix line endings. --- .../packwiz/InstallPackwizModpackCommand.java | 362 +++++++++--------- .../packwiz/PackwizModpackManifest.java | 82 ++-- .../packwiz/model/PackwizModLoader.java | 16 +- .../helpers/packwiz/model/PackwizPack.java | 28 +- 4 files changed, 244 insertions(+), 244 deletions(-) diff --git a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java index 032bd279..b3a68535 100644 --- a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java +++ b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java @@ -1,181 +1,181 @@ -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.packwiz.model.PackwizModLoader; -import me.itzg.helpers.packwiz.model.PackwizPack; -import me.itzg.helpers.quilt.QuiltInstaller; -import org.apache.commons.lang3.tuple.Pair; -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.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 { - @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 updating 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 - - final Pair loaderInfo = getModLoader(newManifest); - if (loaderInfo != null) { - final PackwizModLoader loader = loaderInfo.getLeft(); - final String version = loaderInfo.getRight(); - installModLoader(fetch, loader, version, minecraftVersion); - } - - // populate the results file - // run packwiz bootstrap - } - - Manifests.cleanup(outputDirectory, prevManifest, newManifest, log); - Manifests.save(outputDirectory, ModrinthModpackManifest.ID, newManifest); - - return ExitCode.OK; - } - - private void installModLoader(SharedFetch fetch, PackwizModLoader loader, String loaderVersion, String minecraftVersion) { - // TODO support forceReinstall parameters (the falses) - switch (loader) { - case NEOFORGE: - new ForgeInstaller(new NeoForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) - .install(outputDirectory, resultsFile, false, "NeoForge"); - break; - case FORGE: - new ForgeInstaller(new ForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) - .install(outputDirectory, resultsFile, false, "Forge"); - break; - case FABRIC: - new FabricLauncherInstaller(outputDirectory) - .setResultsFile(resultsFile) - .installUsingVersions(sharedFetchArgs.options(), minecraftVersion, loaderVersion, null); - break; - case QUILT: - try (QuiltInstaller installer = new QuiltInstaller( - QuiltInstaller.DEFAULT_REPO_URL, - sharedFetchArgs.options(), - outputDirectory, - minecraftVersion - ).setResultsFile(resultsFile)) { - installer.installWithVersion(null, loaderVersion); - } - break; - } - } - - private Pair getModLoader(PackwizModpackManifest manifest) { - // 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 neoforge = manifest.neoforgeVersion(); - if (neoforge != null) { - return Pair.of(PackwizModLoader.NEOFORGE, neoforge); - } - - final String forge = manifest.forgeVersion(); - if (forge != null) { - return Pair.of(PackwizModLoader.FORGE, forge); - } - - final String fabric = manifest.fabricVersion(); - if (fabric != null) { - return Pair.of(PackwizModLoader.FABRIC, fabric); - } - - final String quilt = manifest.quiltVersion(); - if (quilt != null) { - return Pair.of(PackwizModLoader.QUILT, quilt); - } - - return null; - } - - 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(); - } -} +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.packwiz.model.PackwizModLoader; +import me.itzg.helpers.packwiz.model.PackwizPack; +import me.itzg.helpers.quilt.QuiltInstaller; +import org.apache.commons.lang3.tuple.Pair; +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.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 { + @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 updating 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 + + final Pair loaderInfo = getModLoader(newManifest); + if (loaderInfo != null) { + final PackwizModLoader loader = loaderInfo.getLeft(); + final String version = loaderInfo.getRight(); + installModLoader(fetch, loader, version, minecraftVersion); + } + + // populate the results file + // run packwiz bootstrap + } + + Manifests.cleanup(outputDirectory, prevManifest, newManifest, log); + Manifests.save(outputDirectory, ModrinthModpackManifest.ID, newManifest); + + return ExitCode.OK; + } + + private void installModLoader(SharedFetch fetch, PackwizModLoader loader, String loaderVersion, String minecraftVersion) { + // TODO support forceReinstall parameters (the falses) + switch (loader) { + case NEOFORGE: + new ForgeInstaller(new NeoForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) + .install(outputDirectory, resultsFile, false, "NeoForge"); + break; + case FORGE: + new ForgeInstaller(new ForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) + .install(outputDirectory, resultsFile, false, "Forge"); + break; + case FABRIC: + new FabricLauncherInstaller(outputDirectory) + .setResultsFile(resultsFile) + .installUsingVersions(sharedFetchArgs.options(), minecraftVersion, loaderVersion, null); + break; + case QUILT: + try (QuiltInstaller installer = new QuiltInstaller( + QuiltInstaller.DEFAULT_REPO_URL, + sharedFetchArgs.options(), + outputDirectory, + minecraftVersion + ).setResultsFile(resultsFile)) { + installer.installWithVersion(null, loaderVersion); + } + break; + } + } + + private Pair getModLoader(PackwizModpackManifest manifest) { + // 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 neoforge = manifest.neoforgeVersion(); + if (neoforge != null) { + return Pair.of(PackwizModLoader.NEOFORGE, neoforge); + } + + final String forge = manifest.forgeVersion(); + if (forge != null) { + return Pair.of(PackwizModLoader.FORGE, forge); + } + + final String fabric = manifest.fabricVersion(); + if (fabric != null) { + return Pair.of(PackwizModLoader.FABRIC, fabric); + } + + final String quilt = manifest.quiltVersion(); + if (quilt != null) { + return Pair.of(PackwizModLoader.QUILT, quilt); + } + + return null; + } + + 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(); + } +} diff --git a/src/main/java/me/itzg/helpers/packwiz/PackwizModpackManifest.java b/src/main/java/me/itzg/helpers/packwiz/PackwizModpackManifest.java index 0b8ee366..1555d5b2 100644 --- a/src/main/java/me/itzg/helpers/packwiz/PackwizModpackManifest.java +++ b/src/main/java/me/itzg/helpers/packwiz/PackwizModpackManifest.java @@ -1,41 +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 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"); - } -} +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 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"); + } +} diff --git a/src/main/java/me/itzg/helpers/packwiz/model/PackwizModLoader.java b/src/main/java/me/itzg/helpers/packwiz/model/PackwizModLoader.java index 60734902..7c2df725 100644 --- a/src/main/java/me/itzg/helpers/packwiz/model/PackwizModLoader.java +++ b/src/main/java/me/itzg/helpers/packwiz/model/PackwizModLoader.java @@ -1,8 +1,8 @@ -package me.itzg.helpers.packwiz.model; - -public enum PackwizModLoader { - NEOFORGE, - FABRIC, - FORGE, - QUILT, -} +package me.itzg.helpers.packwiz.model; + +public enum PackwizModLoader { + NEOFORGE, + FABRIC, + FORGE, + QUILT, +} diff --git a/src/main/java/me/itzg/helpers/packwiz/model/PackwizPack.java b/src/main/java/me/itzg/helpers/packwiz/model/PackwizPack.java index 3ff73fac..baa4e9a7 100644 --- a/src/main/java/me/itzg/helpers/packwiz/model/PackwizPack.java +++ b/src/main/java/me/itzg/helpers/packwiz/model/PackwizPack.java @@ -1,14 +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 versions = new HashMap<>(); -} +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 versions = new HashMap<>(); +} From 3d64633546d06ced72f328902f7fc52d6744cbf3 Mon Sep 17 00:00:00 2001 From: Josh Cotton Date: Sat, 24 May 2025 16:28:49 -0700 Subject: [PATCH 4/5] Simplify modloader installation. --- .../packwiz/InstallPackwizModpackCommand.java | 84 +++++++------------ 1 file changed, 31 insertions(+), 53 deletions(-) diff --git a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java index b3a68535..9a36ff8e 100644 --- a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java +++ b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java @@ -14,10 +14,8 @@ import me.itzg.helpers.http.SharedFetch; import me.itzg.helpers.http.SharedFetchArgs; import me.itzg.helpers.modrinth.ModrinthModpackManifest; -import me.itzg.helpers.packwiz.model.PackwizModLoader; import me.itzg.helpers.packwiz.model.PackwizPack; import me.itzg.helpers.quilt.QuiltInstaller; -import org.apache.commons.lang3.tuple.Pair; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; import picocli.CommandLine.ExitCode; @@ -46,7 +44,7 @@ public final class InstallPackwizModpackCommand implements Callable { Path resultsFile; @Option(names = "--force-update", defaultValue = "${env:PACKWIZ_FORCE_UPDATE:-false}", - description = "Force updating the pack even when the version hasn't changed (default: ${DEFAULT-VALUE})" + description = "Force update the pack even when the version hasn't changed (default: ${DEFAULT-VALUE})" ) boolean forceUpdate; @@ -73,15 +71,10 @@ public Integer call() throws IOException { } // TODO might need to set/save the Minecraft version somewhere - final Pair loaderInfo = getModLoader(newManifest); - if (loaderInfo != null) { - final PackwizModLoader loader = loaderInfo.getLeft(); - final String version = loaderInfo.getRight(); - installModLoader(fetch, loader, version, minecraftVersion); - } + installModLoader(newManifest, fetch); - // populate the results file // run packwiz bootstrap + // populate the results file (MODPACK_NAME, MODPACK_VERSION) } Manifests.cleanup(outputDirectory, prevManifest, newManifest, log); @@ -90,59 +83,44 @@ public Integer call() throws IOException { return ExitCode.OK; } - private void installModLoader(SharedFetch fetch, PackwizModLoader loader, String loaderVersion, String minecraftVersion) { + private void installModLoader(PackwizModpackManifest manifest, SharedFetch fetch) { + final String minecraftVersion = manifest.minecraftVersion(); // TODO support forceReinstall parameters (the falses) - switch (loader) { - case NEOFORGE: - new ForgeInstaller(new NeoForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) - .install(outputDirectory, resultsFile, false, "NeoForge"); - break; - case FORGE: - new ForgeInstaller(new ForgeInstallerResolver(fetch, minecraftVersion, loaderVersion)) - .install(outputDirectory, resultsFile, false, "Forge"); - break; - case FABRIC: - new FabricLauncherInstaller(outputDirectory) - .setResultsFile(resultsFile) - .installUsingVersions(sharedFetchArgs.options(), minecraftVersion, loaderVersion, null); - break; - case QUILT: - try (QuiltInstaller installer = new QuiltInstaller( - QuiltInstaller.DEFAULT_REPO_URL, - sharedFetchArgs.options(), - outputDirectory, - minecraftVersion - ).setResultsFile(resultsFile)) { - installer.installWithVersion(null, loaderVersion); - } - break; - } - } - - private Pair getModLoader(PackwizModpackManifest manifest) { // 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 neoforge = manifest.neoforgeVersion(); - if (neoforge != null) { - return Pair.of(PackwizModLoader.NEOFORGE, neoforge); + final String neoforgeVersion = manifest.neoforgeVersion(); + if (neoforgeVersion != null) { + new ForgeInstaller(new NeoForgeInstallerResolver(fetch, minecraftVersion, neoforgeVersion)) + .install(outputDirectory, resultsFile, false, "NeoForge"); + return; } - final String forge = manifest.forgeVersion(); - if (forge != null) { - return Pair.of(PackwizModLoader.FORGE, forge); + final String forgeVersion = manifest.forgeVersion(); + if (forgeVersion != null) { + new ForgeInstaller(new ForgeInstallerResolver(fetch, minecraftVersion, forgeVersion)) + .install(outputDirectory, resultsFile, false, "Forge"); + return; } - final String fabric = manifest.fabricVersion(); - if (fabric != null) { - return Pair.of(PackwizModLoader.FABRIC, fabric); + final String fabricVersion = manifest.fabricVersion(); + if (fabricVersion != null) { + new FabricLauncherInstaller(outputDirectory) + .setResultsFile(resultsFile) + .installUsingVersions(sharedFetchArgs.options(), minecraftVersion, fabricVersion, null); + return; } - final String quilt = manifest.quiltVersion(); - if (quilt != null) { - return Pair.of(PackwizModLoader.QUILT, quilt); + 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); + } } - - return null; } private PackwizModpackManifest getManifest(SharedFetch fetch) throws IOException { From f63334c3a7c95f5e6efa6024b41032aed140d468 Mon Sep 17 00:00:00 2001 From: Josh Cotton Date: Sun, 15 Jun 2025 17:35:30 -0700 Subject: [PATCH 5/5] Download and invoke packwiz bootstrapper. --- .../packwiz/InstallPackwizModpackCommand.java | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java index 9a36ff8e..96005f07 100644 --- a/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java +++ b/src/main/java/me/itzg/helpers/packwiz/InstallPackwizModpackCommand.java @@ -14,6 +14,7 @@ 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; @@ -22,6 +23,7 @@ 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; @@ -72,8 +74,8 @@ public Integer call() throws IOException { // TODO might need to set/save the Minecraft version somewhere installModLoader(newManifest, fetch); + bootstrapPackwiz(fetch); - // run packwiz bootstrap // populate the results file (MODPACK_NAME, MODPACK_VERSION) } @@ -83,6 +85,42 @@ public Integer call() throws IOException { 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)