diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 74a9e97197..a1cb3d4438 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -141,6 +141,30 @@ public final class Metadata { return null; } + /// Default client Server Modpack fileApi published by TDLMPack (Gitea generic package `latest`). + public static final String DEFAULT_CLIENT_FILE_API = + "https://git.nest.potatolab.uk:8443/api/packages/TDLM/generic/tdlm-client/latest"; + + /// Stable client update root (`server-manifest.json` and `overrides/` live under this URL). + /// + /// Override with system property `hmcl.client.fileApi` or jar attribute `hmcl.client.fileApi`. + public static String getClientFileApi() { + String fromProperty = System.getProperty("hmcl.client.fileApi", "").trim(); + if (StringUtils.isNotBlank(fromProperty)) { + return StringUtils.removeSuffix(fromProperty, "/"); + } + String fromJar = JarUtils.getAttribute("hmcl.client.fileApi", null); + if (StringUtils.isNotBlank(fromJar)) { + return StringUtils.removeSuffix(fromJar.trim(), "/"); + } + return DEFAULT_CLIENT_FILE_API; + } + + /// Absolute URL of the client `server-manifest.json` used for first install and differential updates. + public static String getClientManifestUrl() { + return getClientFileApi() + "/server-manifest.json"; + } + /// Server address used when the installed modpack manifest does not provide one. /// /// Can be set at build or run time with the `hmcl.server.address` system property; empty by default. @@ -150,9 +174,10 @@ public final class Metadata { /// The server name shown in the title bar. /// - /// Can be set with the `hmcl.server.name` system property; empty by default. + /// Can be set with the `hmcl.server.name` system property; defaults to `TDLM`. public static String getServerName() { - return System.getProperty("hmcl.server.name", "").trim(); + String name = System.getProperty("hmcl.server.name", "TDLM").trim(); + return name.isEmpty() ? "TDLM" : name; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 88a8d16e06..4009895644 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -27,11 +27,16 @@ import org.jackhuang.hmcl.game.LauncherHelper; import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.java.JavaManager; import org.jackhuang.hmcl.java.JavaRuntime; +import org.glavo.url.WebURL; import org.jackhuang.hmcl.modpack.Modpack; +import org.jackhuang.hmcl.modpack.server.ServerModpackManifest; import org.jackhuang.hmcl.setting.*; +import org.jackhuang.hmcl.task.GetTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; +import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.ui.account.AccountListPage; import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.construct.*; @@ -354,35 +359,59 @@ public final class Controllers { ); } - /// Installs the package found by [Metadata#findBundledModpackFile()] asynchronously, if present. - /// - /// Shows progress without a cancel action and attempts to delete the package after success. - /// Failures are logged; a retained package may be retried at the next startup. + /// Installs a launcher-bundled modpack when present; otherwise, when the repository has no + /// instances yet, pulls the org client [Metadata#getClientManifestUrl] and opens the install wizard. private static void tryInstallBundledModpack(HMCLGameRepository repository) { @Nullable Path modpackFile = Metadata.findBundledModpackFile(); - if (modpackFile == null) { + if (modpackFile != null) { + LOG.info("Found bundled modpack at " + modpackFile + "; starting automatic install"); + + Controllers.taskDialog( + Task.composeAsync(Schedulers.io(), () -> { + Modpack modpack = ModpackHelper.readModpackManifest(modpackFile); + return ModpackHelper.getInstallTask( + repository, modpackFile, new GameInstanceID(modpack.getName()), modpack, null); + }) + .whenComplete(Schedulers.javafx(), (ignored, exception) -> { + if (exception != null) { + LOG.warning("Failed to install bundled modpack", exception); + return; + } + try { + Files.deleteIfExists(modpackFile); + } catch (IOException e) { + LOG.warning("Failed to delete bundled modpack: " + modpackFile, e); + } + }), i18n("modpack.installing"), TaskCancellationAction.NO_CANCEL + ); return; } - LOG.info("Found bundled modpack at " + modpackFile + "; starting automatic install"); + if (repository.getDisplayInstances().findAny().isPresent()) { + return; + } + String manifestUrl = Metadata.getClientManifestUrl(); + LOG.info("No instances yet; fetching client Server Modpack from " + manifestUrl); Controllers.taskDialog( - Task.composeAsync(Schedulers.io(), () -> { - Modpack modpack = ModpackHelper.readModpackManifest(modpackFile); - return ModpackHelper.getInstallTask( - repository, modpackFile, new GameInstanceID(modpack.getName()), modpack, null); - }) - .whenComplete(Schedulers.javafx(), (ignored, exception) -> { + new GetTask(WebURL.parse(manifestUrl)) + .whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception != null) { - LOG.warning("Failed to install bundled modpack", exception); + LOG.warning("Failed to fetch client Server Modpack manifest", exception); return; } - try { - Files.deleteIfExists(modpackFile); - } catch (IOException e) { - LOG.warning("Failed to delete bundled modpack: " + modpackFile, e); + ServerModpackManifest manifest = + JsonUtils.fromMaybeMalformedJson(result, ServerModpackManifest.class); + if (manifest == null) { + LOG.warning("Malformed client Server Modpack manifest from " + manifestUrl); + return; } - }), i18n("modpack.installing"), TaskCancellationAction.NO_CANCEL + Controllers.getDecorator().startWizard( + new ModpackInstallWizardProvider(repository, manifest), + i18n("install.modpack")); + }), + i18n("message.downloading"), + TaskCancellationAction.NO_CANCEL ); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java index c08b1be9d4..5ee83b30c6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackInstallWizardProvider.java @@ -28,25 +28,40 @@ public final class ModpackInstallWizardProvider implements WizardProvider { private final HMCLGameRepository repository; private final Path file; private final String updateVersion; + private final ServerModpackManifest remoteManifest; private String iconUrl; private boolean hasSource; public ModpackInstallWizardProvider(HMCLGameRepository repository) { - this(repository, null, null); + this(repository, null, null, null); } public ModpackInstallWizardProvider(HMCLGameRepository repository, Path modpackFile) { - this(repository, modpackFile, null); + this(repository, modpackFile, null, null); } public ModpackInstallWizardProvider(HMCLGameRepository repository, GameInstanceID updateInstanceId) { - this(repository, null, updateInstanceId); + this(repository, null, updateInstanceId, null); + } + + /// Starts the wizard with a remote Server Modpack manifest (TDLM fileApi install). + public ModpackInstallWizardProvider(HMCLGameRepository repository, ServerModpackManifest remoteManifest) { + this(repository, null, null, remoteManifest); } public ModpackInstallWizardProvider(HMCLGameRepository repository, Path modpackFile, GameInstanceID updateInstanceId) { + this(repository, modpackFile, updateInstanceId, null); + } + + private ModpackInstallWizardProvider( + HMCLGameRepository repository, + Path modpackFile, + GameInstanceID updateInstanceId, + ServerModpackManifest remoteManifest) { this.repository = repository; this.file = modpackFile; this.updateVersion = updateInstanceId != null ? updateInstanceId.toString() : null; + this.remoteManifest = remoteManifest; } public void setIconUrl(String iconUrl) { @@ -57,6 +72,8 @@ public final class ModpackInstallWizardProvider implements WizardProvider { public void start(SettingsMap settings) { if (file != null) settings.put(LocalModpackPage.MODPACK_FILE, file); + if (remoteManifest != null) + settings.put(RemoteModpackPage.MODPACK_SERVER_MANIFEST, remoteManifest); if (updateVersion != null) settings.put(LocalModpackPage.MODPACK_NAME, updateVersion); if (StringUtils.isNotBlank(iconUrl)) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/Instances.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/Instances.java index 416e175124..232ad88c51 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/Instances.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/Instances.java @@ -13,6 +13,9 @@ import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.setting.*; +import org.glavo.url.WebURL; +import org.jackhuang.hmcl.modpack.server.ServerModpackManifest; +import org.jackhuang.hmcl.task.GetTask; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -45,9 +48,10 @@ public final class Instances { private Instances() { } - /// Installs or updates the server client from the launcher-bundled modpack when present. + /// Installs or updates the TDLM client. /// - /// Falls back to the local/remote modpack import wizard when no bundled package is found. + /// Prefers a launcher-bundled zip, then the org [Metadata#getClientManifestUrl] Server Modpack, + /// then the generic local/remote import wizard. public static void installServerModpack() { HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository(); if (!repository.isLoaded()) { @@ -62,7 +66,33 @@ public final class Instances { return; } - importModpack(); + Controllers.taskDialog( + new GetTask(WebURL.parse(Metadata.getClientManifestUrl())) + .whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception != null) { + Controllers.dialog( + exception.getMessage(), + i18n("message.error"), + MessageDialogPane.MessageType.ERROR); + importModpack(); + return; + } + ServerModpackManifest manifest = JsonUtils.fromMaybeMalformedJson( + result, ServerModpackManifest.class); + if (manifest == null) { + Controllers.dialog( + i18n("modpack.type.server.malformed"), + i18n("message.error"), + MessageDialogPane.MessageType.ERROR); + importModpack(); + return; + } + Controllers.getDecorator().startWizard( + new ModpackInstallWizardProvider(repository, manifest), + i18n("install.modpack")); + }), + i18n("message.downloading"), + TaskCancellationAction.NORMAL); } public static void importModpack() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java index eaa2ffee22..5de5464843 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/LaunchOptions.java @@ -327,6 +327,10 @@ public class LaunchOptions implements Serializable { return this; } + public Path getGameDir() { + return options.gameDir; + } + public Builder setJava(JavaRuntime java) { options.java = java; return this; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/Lwjgl3ifyApplyTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/Lwjgl3ifyApplyTask.java new file mode 100644 index 0000000000..af582453a8 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/Lwjgl3ifyApplyTask.java @@ -0,0 +1,220 @@ +// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors +// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL +package org.jackhuang.hmcl.modpack.server; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; +import org.jackhuang.hmcl.download.game.GameDownloadTask; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; +import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.modpack.multimc.MultiMCComponents; +import org.jackhuang.hmcl.modpack.multimc.MultiMCInstancePatch; +import org.jackhuang.hmcl.modpack.multimc.MultiMCManifest; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.NotNullByDefault; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.*; + +/// Applies the default-on lwjgl3ify MultiMC companion under {@link Lwjgl3ifySupport#ROOT}. +/// +/// Replaces the plain Forge install produced by {@link org.jackhuang.hmcl.download.GameBuilder} with the +/// resolved MultiMC artifact (libraries, JVM args, bootstrap main class) so Java 25 launches work +/// without a player toggle. +@NotNullByDefault +public final class Lwjgl3ifyApplyTask extends Task { + private final DefaultDependencyManager dependencyManager; + private final DefaultGameRepository repository; + private final GameInstanceID instanceId; + private final @Nullable DefaultGameInstance updateTarget; + private final List> dependents = new ArrayList<>(); + private final List> dependencies = new ArrayList<>(); + private @Nullable MultiMCInstancePatch.ResolvedInstance artifact; + + public Lwjgl3ifyApplyTask( + DefaultDependencyManager dependencyManager, + GameInstanceID instanceId, + @Nullable DefaultGameInstance updateTarget) { + this.dependencyManager = dependencyManager; + this.repository = dependencyManager.getGameRepository(); + this.instanceId = instanceId; + this.updateTarget = updateTarget; + setStage("hmcl.modpack"); + setSignificance(TaskSignificance.MODERATE); + + Path instanceRoot = repository.getLayout().getInstanceRoot(instanceId); + if (!Lwjgl3ifySupport.isPresent(instanceRoot)) { + return; + } + + try { + dependents.add(new AssemblePatchesTask(instanceRoot)); + } catch (IOException e) { + dependents.add(Task.failed(e)); + } + } + + /// Runs after Server Modpack file sync when the companion is present; otherwise a completed no-op. + public static Task afterCompletion(DefaultDependencyManager dependencyManager, DefaultGameInstance instance) { + if (!Lwjgl3ifySupport.isPresent(instance.getInstanceRoot())) { + return Task.completed(null); + } + return new Lwjgl3ifyApplyTask(dependencyManager, instance.getId(), instance); + } + + @Override + public List> getDependents() { + return dependents; + } + + @Override + public List> getDependencies() { + return dependencies; + } + + @Override + public void execute() throws Exception { + Path instanceRoot = repository.getLayout().getInstanceRoot(instanceId); + if (!Lwjgl3ifySupport.isPresent(instanceRoot)) { + return; + } + + @Nullable AssemblePatchesTask assemble = null; + for (Task dependent : dependents) { + if (dependent instanceof AssemblePatchesTask patchesTask) { + assemble = patchesTask; + break; + } + } + Objects.requireNonNull(assemble, "lwjgl3ify patch assemble task"); + artifact = MultiMCInstancePatch.resolveArtifact(assemble.getResult(), instanceId); + + Path embeddedLibraries = Lwjgl3ifySupport.root(instanceRoot).resolve("libraries"); + if (Files.isDirectory(embeddedLibraries)) { + FileUtils.copyDirectory(embeddedLibraries, instanceRoot.resolve("libraries")); + } + + for (Library library : artifact.getManifest().getLibraries()) { + if ("local".equals(library.hint())) { + Path from = repository.getLayout().getLibraryFile(artifact.getManifest().id(), library); + Path target = repository.getLayout().getLibraryFile(artifact.getManifest().id(), library.withoutCommunityFields()); + if (Files.exists(from)) { + Files.createDirectories(target.getParent()); + Files.copy(from, target, StandardCopyOption.REPLACE_EXISTING); + } + } + } + + try (InputStream input = Objects.requireNonNull( + Lwjgl3ifyApplyTask.class.getResourceAsStream("/assets/game/HMCLMultiMCBootstrap-1.0.jar"), + "Bundled HMCLMultiMCBootstrap is missing.")) { + Path libraryPath = repository.getLayout().getLibraryFile(artifact.getManifest().id(), MultiMCInstancePatch.BOOTSTRAP_LIBRARY); + Files.createDirectories(libraryPath.getParent()); + Files.copy(input, libraryPath, StandardCopyOption.REPLACE_EXISTING); + } + + GameInstanceManifest instanceManifest = artifact.getManifest(); + repository.save(instanceManifest); + + dependencies.add(new GameAssetDownloadTask(dependencyManager, instanceManifest, GameAssetDownloadTask.DOWNLOAD_INDEX_FORCIBLY, true)); + dependencies.add(new GameLibrariesTask( + dependencyManager, + instanceManifest.withLibraries(Lang.merge(instanceManifest.getLibraries(), artifact.getMavenOnlyFiles())), + true)); + + Path instanceJar = getPrimaryJarFile(); + dependencies.add(new GameDownloadTask(dependencyManager, instanceManifest) + .thenAcceptAsync(cachedJar -> FileUtils.copyFile(cachedJar, instanceJar)) + .withStage("hmcl.modpack.download")); + } + + private Path getPrimaryJarFile() { + if (updateTarget == null) { + return repository.getLayout().getInstanceJarFile(instanceId); + } + Path manifestFile = updateTarget.getManifestFile(); + return manifestFile.resolveSibling(FileUtils.getNameWithoutExtension(manifestFile) + ".jar"); + } + + private static final class AssemblePatchesTask extends Task> { + private final Path companionRoot; + private final String mcVersion; + private final List> patches = new ArrayList<>(); + + AssemblePatchesTask(Path instanceRoot) throws IOException { + this.companionRoot = Lwjgl3ifySupport.root(instanceRoot); + MultiMCManifest mmcPack = Lwjgl3ifySupport.readMmcPack(instanceRoot); + List components = Objects.requireNonNull( + mmcPack.getComponents(), "components"); + + String resolvedMc = null; + for (MultiMCManifest.MultiMCManifestComponent component : components) { + if (MultiMCComponents.getComponent(component.getUid()) == GameComponentType.GAME) { + resolvedMc = component.getVersion(); + break; + } + } + if (resolvedMc == null) { + throw new IOException("lwjgl3ify mmc-pack.json has no Minecraft component"); + } + this.mcVersion = resolvedMc; + + for (MultiMCManifest.MultiMCManifestComponent component : components) { + String componentID = Objects.requireNonNull(component.getUid(), "Component ID"); + Path patchPath = companionRoot.resolve("patches").resolve(componentID + ".json"); + if (Files.isRegularFile(patchPath)) { + MultiMCInstancePatch patch = MultiMCInstancePatch.read(componentID, Files.readString(patchPath)); + patches.add(Task.supplyAsync(() -> patch)); + } else { + patches.add(new GetTask(MultiMCComponents.getMetaURL(componentID, component.getVersion(), mcVersion)) + .thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s))); + } + } + } + + @Override + public Collection> getDependents() { + return patches; + } + + @Override + public void execute() throws Exception { + Map existed = new LinkedHashMap<>(); + for (Task patch : patches) { + MultiMCInstancePatch result = patch.getResult(); + existed.put(result.getID(), result); + } + + checking: + while (true) { + for (MultiMCInstancePatch patch : existed.values()) { + for (MultiMCManifest.MultiMCManifestCachedRequires require : patch.getRequires()) { + String componentID = require.getID(); + if (!existed.containsKey(componentID)) { + Task task = new GetTask(MultiMCComponents.getMetaURL( + componentID, + Lang.requireNonNullElse(require.getEqualsVersion(), require.getSuggests()), + mcVersion + )).thenApplyAsync(s -> MultiMCInstancePatch.read(componentID, s)); + task.run(); + MultiMCInstancePatch result = Objects.requireNonNull(task.getResult()); + existed.put(result.getID(), result); + continue checking; + } + } + } + break; + } + + setResult(new ArrayList<>(existed.values())); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/Lwjgl3ifySupport.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/Lwjgl3ifySupport.java new file mode 100644 index 0000000000..a7b6e2cd9c --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/Lwjgl3ifySupport.java @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright (C) 2026 The HMCL Authors +// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL +package org.jackhuang.hmcl.modpack.server; + +import org.jackhuang.hmcl.game.LaunchOptions; +import org.jackhuang.hmcl.modpack.multimc.MultiMCInstancePatch; +import org.jackhuang.hmcl.modpack.multimc.MultiMCManifest; +import org.jackhuang.hmcl.util.gson.JsonUtils; +import org.jetbrains.annotations.NotNullByDefault; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/// Default-on lwjgl3ify companion shipped under {@code tdlm/lwjgl3ify/} in the TDLM Server Modpack. +@NotNullByDefault +public final class Lwjgl3ifySupport { + /// Instance-root-relative directory written by the TDLM pack builder. + public static final String ROOT = "tdlm/lwjgl3ify"; + + private Lwjgl3ifySupport() { + } + + /// Returns whether the instance carries an enabled lwjgl3ify MultiMC companion. + public static boolean isPresent(Path instanceRoot) { + Path root = root(instanceRoot); + return Files.isRegularFile(root.resolve("enabled")) && Files.isRegularFile(root.resolve("mmc-pack.json")); + } + + /// Returns the companion root under the instance. + public static Path root(Path instanceRoot) { + return instanceRoot.resolve(ROOT); + } + + /// Injects {@code +jvmArgs} from every local patch when the companion is present. + /// + /// Version-json application via {@link Lwjgl3ifyApplyTask} is the primary path; this covers launches + /// where the resolved manifest has not yet absorbed those args. + public static void injectLaunchOptions(Path instanceRoot, LaunchOptions.Builder builder) { + if (!isPresent(instanceRoot)) { + return; + } + Path patches = root(instanceRoot).resolve("patches"); + if (!Files.isDirectory(patches)) { + return; + } + List args = new ArrayList<>(); + try (DirectoryStream stream = Files.newDirectoryStream(patches, "*.json")) { + for (Path patchFile : stream) { + try { + MultiMCInstancePatch patch = MultiMCInstancePatch.read( + patchFile.getFileName().toString(), Files.readString(patchFile)); + args.addAll(patch.getJvmArgs()); + } catch (IOException | RuntimeException e) { + LOG.warning("Failed to read lwjgl3ify patch " + patchFile, e); + } + } + } catch (IOException e) { + LOG.warning("Failed to list lwjgl3ify patches under " + patches, e); + return; + } + for (String arg : args) { + if (!builder.getJavaArguments().contains(arg)) { + builder.getJavaArguments().add(arg); + } + } + } + + /// Parses {@code mmc-pack.json} from the companion root. + public static MultiMCManifest readMmcPack(Path instanceRoot) throws IOException { + Path mmcPack = root(instanceRoot).resolve("mmc-pack.json"); + return JsonUtils.fromNonNullJson(Files.readString(mmcPack), MultiMCManifest.class); + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackLocalInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackLocalInstallTask.java index b64892576c..aeae22e5e1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackLocalInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackLocalInstallTask.java @@ -32,6 +32,7 @@ public class ServerModpackLocalInstallTask extends Task { /// Existing instance selecting update mode, or `null` for a new installation. private final @Nullable DefaultGameInstance updateTarget; + private final DefaultDependencyManager dependencyManager; private final DefaultGameRepository repository; private final List> dependencies = new ArrayList<>(); private final List> dependents = new ArrayList<>(4); @@ -97,6 +98,7 @@ public class ServerModpackLocalInstallTask extends Task { this.manifest = manifest; this.instanceId = instanceId; this.updateTarget = updateTarget; + this.dependencyManager = dependencyManager; this.repository = dependencyManager.getGameRepository(); Path run = repository.getLayout().getInstanceRoot(instanceId); @@ -148,5 +150,9 @@ public class ServerModpackLocalInstallTask extends Task { @Override public void execute() throws Exception { + Path instanceRoot = repository.getLayout().getInstanceRoot(instanceId); + if (Lwjgl3ifySupport.isPresent(instanceRoot)) { + dependencies.add(new Lwjgl3ifyApplyTask(dependencyManager, instanceId, updateTarget)); + } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackProvider.java index 939be72195..a9b7ef0b9f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackProvider.java @@ -6,6 +6,7 @@ import com.google.gson.JsonParseException; import kala.compress.archivers.zip.ZipArchiveReader; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.game.DefaultGameInstance; +import org.jackhuang.hmcl.game.LaunchOptions; import org.jackhuang.hmcl.modpack.MismatchedModpackTypeException; import org.jackhuang.hmcl.modpack.Modpack; import org.jackhuang.hmcl.modpack.ModpackProvider; @@ -29,7 +30,8 @@ public final class ServerModpackProvider implements ModpackProvider { @Override public Task createCompletionTask(DefaultDependencyManager dependencyManager, DefaultGameInstance instance) { - return new ServerModpackCompletionTask(dependencyManager, instance); + return new ServerModpackCompletionTask(dependencyManager, instance) + .thenComposeAsync(ignored -> Lwjgl3ifyApplyTask.afterCompletion(dependencyManager, instance)); } @Override @@ -51,4 +53,12 @@ public final class ServerModpackProvider implements ModpackProvider { ServerModpackManifest manifest = JsonUtils.fromNonNullJson(json, ServerModpackManifest.class); return manifest.toModpack(); } + + @Override + public void injectLaunchOptions(String modpackConfigurationJson, LaunchOptions.Builder builder) { + Path gameDir = builder.getGameDir(); + if (gameDir != null) { + Lwjgl3ifySupport.injectLaunchOptions(gameDir, builder); + } + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackRemoteInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackRemoteInstallTask.java index d074efb218..dc30bc618d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackRemoteInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/modpack/server/ServerModpackRemoteInstallTask.java @@ -126,10 +126,12 @@ public class ServerModpackRemoteInstallTask extends Task { @Override public void execute() throws Exception { + DefaultGameInstance instance = repository.getInstance(instanceId); dependencies.add(new ServerModpackCompletionTask( dependency, - repository.getInstance(instanceId), - new ModpackConfiguration<>(manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), Collections.emptyList()))); + instance, + new ModpackConfiguration<>(manifest, MODPACK_TYPE, manifest.getName(), manifest.getVersion(), Collections.emptyList())) + .thenComposeAsync(ignored -> Lwjgl3ifyApplyTask.afterCompletion(dependency, instance))); } public static final String MODPACK_TYPE = "Server";