diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index e13cd3d5d31..dff82a21b89 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -31,8 +31,11 @@ import java.io.IOException; import java.io.InputStream; -import java.nio.file.*; -import java.util.*; +import java.nio.file.FileVisitOption; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Map; import java.util.stream.Stream; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -168,10 +171,9 @@ public void makeLaunchScript(Path scriptFile) throws IOException { @Override protected void appendJvmArgs(CommandBuilder result) { super.appendJvmArgs(result); + if (!options.isAllowAutoAgent() && !options.isNoGeneratedJVMArgs()) return; - if (options.isAllowAutoAgent() - && !options.isNoGeneratedJVMArgs() - && !options.isNoGeneratedOptimizingJVMArgs() + if (!options.isNoGeneratedOptimizingJVMArgs() && NativePatcher.needPatchMemoryUtil(manifest, options.getJava().getParsedVersion())) { LOG.info("Attempting to patch game with lwjgl-unsafe-agent"); try { @@ -219,5 +221,4 @@ private Path extractLwjglUnsafeAgent() throws IOException { FileUtils.saveSafely(agentPath, output -> output.write(bytes)); return agentPath; } - } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index aaef6607bce..f1ee8d56f09 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -215,7 +215,7 @@ private void launch0() { }) ); }).withStage("launch.state.dependencies") - .thenComposeAsync(() -> new GameVerificationFixTask(gameInstance, gameInstance.getVersion(), launchManifest.get())) + .thenComposeAsync(() -> new GameVerificationFixTask(gameInstance, gameInstance.getVersion())) .thenComposeAsync(() -> { if (setting.getInheritable(GameSettings::allowAutoAgentProperty) || setting.getInheritable(GameSettings::noJVMOptionsProperty) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/InstallerListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/InstallerListPage.java index 2935343558a..4a0dd68d38f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/InstallerListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/instances/InstallerListPage.java @@ -144,7 +144,7 @@ private void reloadCurrentInstance() { public void installOffline() { FileChooser chooser = new FileChooser(); - chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.modloader.installer"), "*.jar", "*.exe")); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.modloader.installer"), "*.jar", "*.exe", "*.zip")); Path file = Controllers.showOpenDialog(chooser); if (file != null) doInstallOffline(file); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java index 5ad597f5df0..783311c4626 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/i18n/I18n.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.download.cleanroom.CleanroomInstallTask; import org.jackhuang.hmcl.download.fabric.FabricAPIInstallTask; import org.jackhuang.hmcl.download.fabric.FabricInstallTask; +import org.jackhuang.hmcl.download.forge.ForgeLegacyInstallTask; import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask; import org.jackhuang.hmcl.download.forge.ForgeOldInstallTask; import org.jackhuang.hmcl.download.game.GameAssetDownloadTask; @@ -64,7 +65,8 @@ import java.io.InputStream; import java.net.URL; import java.time.temporal.TemporalAccessor; -import java.util.*; +import java.util.Locale; +import java.util.ResourceBundle; public final class I18n { @@ -193,7 +195,7 @@ public static String translateLoaderName(AddonLoader loader) { return i18n("install.installer.install", i18n("install.installer.cleanroom")); } else if (task instanceof LegacyFabricInstallTask) { return i18n("install.installer.install", i18n("install.installer.legacyfabric")); - } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) { + } else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask || task instanceof ForgeLegacyInstallTask) { return i18n("install.installer.install", i18n("install.installer.forge")); } else if (task instanceof NeoForgeInstallTask || task instanceof NeoForgeOldInstallTask) { return i18n("install.installer.install", i18n("install.installer.neoforge")); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java index 81f358c94e6..1861385987c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/meta/ForgeOldModMetadata.java @@ -30,6 +30,7 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.tree.ZipFileTree; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.file.Path; @@ -124,8 +125,12 @@ public String[] getAuthors() { return authors; } - public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException { - ZipArchiveEntry mcmod = tree.getEntry("mcmod.info"); + public static LocalModFile fromFile(@Nullable ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException { + return fromFile(modManager, modFile, tree, "mcmod.info"); + } + + public static LocalModFile fromFile(@Nullable ModManager modManager, Path modFile, ZipFileTree tree, String infoFileName) throws IOException, JsonParseException { + ZipArchiveEntry mcmod = tree.getEntry(infoFileName); if (mcmod == null) throw new IOException("File " + modFile + " is not a Forge mod."); @@ -157,7 +162,7 @@ else if (firstToken == JsonToken.BEGIN_OBJECT) { authors = String.join(", ", metadata.getAuthorList()); if (StringUtils.isBlank(authors)) authors = metadata.getCredits(); - return new LocalModFile(modManager, modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()), + return new LocalModFile(modManager, modManager == null ? null : modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()), authors, metadata.getVersion(), metadata.getGameVersion(), StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url, metadata.getLogoFile()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java index ab364826864..4706200b3c4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/addon/mod/LocalModFile.java @@ -25,11 +25,15 @@ import org.jackhuang.hmcl.addon.RemoteAddonRepository; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.util.io.FileUtils; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -40,7 +44,9 @@ public final class LocalModFile extends LocalAddonFile implements Comparable { private Path file; + @Nullable private final ModManager modManager; + @Nullable private final LocalMod mod; private final String name; private final Description description; @@ -52,11 +58,11 @@ public final class LocalModFile extends LocalAddonFile implements Comparable installComponentLocalAsync(GameInstance instan } try { - return ForgeInstallTask.install(this, baseManifest, gameVersion, installer); + return ForgeInstallation.install(this, baseManifest, gameVersion, installer); } catch (IOException ignore) { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java index 209809d93c7..4cdfba7f8ad 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/cleanroom/CleanroomInstallTask.java @@ -186,13 +186,13 @@ public static Task install( } ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); + if (!gameVersion.equals(profile.minecraft())) + throw new VersionMismatchException(profile.minecraft(), gameVersion); return new CleanroomInstallTask( dependencyManager, manifest, gameVersion, - modifyVersion(profile.getVersion()), + modifyVersion(profile.version()), installer); } else { throw new IOException(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java index d9aae6a7c97..896dcfda083 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java @@ -23,15 +23,16 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.gson.JsonSerializable; import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.NetworkUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.*; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.fromLookupVersion; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.toLookupVersion; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; @@ -57,14 +58,6 @@ public Task refreshAsync() { throw new UnsupportedOperationException("ForgeBMCLVersionList does not support loading the entire Forge remote version list."); } - private static String toLookupVersion(String gameVersion) { - return "1.7.10-pre4".equals(gameVersion) ? "1.7.10_pre4" : gameVersion; - } - - private static String fromLookupVersion(String lookupVersion) { - return "1.7.10_pre4".equals(lookupVersion) ? "1.7.10-pre4" : lookupVersion; - } - private static String toLookupBranch(String gameVersion, String branch) { if ("1.7.10-pre4".equals(gameVersion)) { return "prerelease"; @@ -86,21 +79,21 @@ public Task refreshAsync(String gameVersion) { if (version == null) continue; List urls = new ArrayList<>(); - for (ForgeVersion.File file : version.getFiles()) - if ("installer".equals(file.getCategory()) && "jar".equals(file.getFormat())) { - String branch = toLookupBranch(gameVersion, version.getBranch()); + for (ForgeVersion.File file : version.files()) + if (("installer".equals(file.category()) && "jar".equals(file.format())) || (("client".equals(file.category()) || "universal".equals(file.category())) && "zip".equals(file.format()))) { + String branch = toLookupBranch(gameVersion, version.branch()); - String classifier = lookupVersion + "-" + version.getVersion() + (branch.isEmpty() ? "" : '-' + branch); - String fileName1 = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat(); - String fileName2 = "forge-" + classifier + "-" + lookupVersion + "-" + file.getCategory() + "." + file.getFormat(); + String classifier = lookupVersion + "-" + version.version() + (branch.isEmpty() ? "" : '-' + branch); + String fileName1 = "forge-" + classifier + "-" + file.category() + "." + file.format(); + String fileName2 = "forge-" + classifier + "-" + lookupVersion + "-" + file.category() + "." + file.format(); urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1); urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + lookupVersion + "/" + fileName2); urls.add(NetworkUtils.withQuery("https://bmclapi2.bangbang93.com/forge/download", mapOf( - pair("mcversion", version.getGameVersion()), - pair("version", version.getVersion()), + pair("mcversion", version.mcversion()), + pair("version", version.version()), pair("branch", branch), - pair("category", file.getCategory()), - pair("format", file.getFormat()) + pair("category", file.category()), + pair("format", file.format()) ))); } @@ -108,16 +101,16 @@ public Task refreshAsync(String gameVersion) { continue; Instant releaseDate = null; - if (version.getModified() != null) { + if (version.modified() != null) { try { - releaseDate = Instant.parse(version.getModified()); + releaseDate = Instant.parse(version.modified()); } catch (DateTimeParseException e) { - LOG.warning("Failed to parse instant " + version.getModified(), e); + LOG.warning("Failed to parse instant " + version.modified(), e); } } versions.put(gameVersion, new ForgeRemoteVersion( - fromLookupVersion(version.getGameVersion()), version.getVersion(), releaseDate, urls)); + fromLookupVersion(version.mcversion()), version.version(), releaseDate, urls)); } } finally { lock.writeLock().unlock(); @@ -132,14 +125,9 @@ public Optional getVersion(String gameVersion, String remote } @Immutable - public static final class ForgeVersion implements Validation { - - private final String branch; - private final int build; - private final String mcversion; - private final String modified; - private final String version; - private final List files; + @JsonSerializable + public record ForgeVersion(String branch, int build, String mcversion, String modified, String version, + List files) implements Validation { /** * No-arg constructor for Gson. @@ -149,44 +137,6 @@ public ForgeVersion() { this(null, 0, "", null, "", Collections.emptyList()); } - public ForgeVersion(String branch, int build, String mcversion, String modified, String version, List files) { - this.branch = branch; - this.build = build; - this.mcversion = mcversion; - this.modified = modified; - this.version = version; - this.files = files; - } - - @Nullable - public String getBranch() { - return branch; - } - - public int getBuild() { - return build; - } - - @NotNull - public String getGameVersion() { - return mcversion; - } - - @Nullable - public String getModified() { - return modified; - } - - @NotNull - public String getVersion() { - return version; - } - - @NotNull - public List getFiles() { - return files; - } - @Override public void validate() throws JsonParseException { if (files == null) @@ -198,32 +148,11 @@ public void validate() throws JsonParseException { } @Immutable - public static final class File { - private final String format; - private final String category; - private final String hash; - + @JsonSerializable + public record File(String format, String category, String hash) { public File() { this("", "", ""); } - - public File(String format, String category, String hash) { - this.format = format; - this.category = category; - this.hash = hash; - } - - public String getFormat() { - return format; - } - - public String getCategory() { - return category; - } - - public String getHash() { - return hash; - } } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstall.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstall.java deleted file mode 100644 index a7a40607998..00000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstall.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui 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 . - */ -package org.jackhuang.hmcl.download.forge; - -import org.jackhuang.hmcl.game.Artifact; -import org.jackhuang.hmcl.util.Immutable; - -/** - * - * @author huangyuhui - */ -@Immutable -public final class ForgeInstall { - - private final String profileName; - private final String target; - private final Artifact path; - private final String version; - private final String filePath; - private final String welcome; - private final String minecraft; - private final String mirrorList; - private final String logo; - - public ForgeInstall() { - this(null, null, null, null, null, null, null, null, null); - } - - public ForgeInstall(String profileName, String target, Artifact path, String version, String filePath, String welcome, String minecraft, String mirrorList, String logo) { - this.profileName = profileName; - this.target = target; - this.path = path; - this.version = version; - this.filePath = filePath; - this.welcome = welcome; - this.minecraft = minecraft; - this.mirrorList = mirrorList; - this.logo = logo; - } - - public String getProfileName() { - return profileName; - } - - public String getTarget() { - return target; - } - - public Artifact getPath() { - return path; - } - - public String getVersion() { - return version; - } - - public String getFilePath() { - return filePath; - } - - public String getWelcome() { - return welcome; - } - - public String getMinecraft() { - return minecraft; - } - - public String getMirrorList() { - return mirrorList; - } - - public String getLogo() { - return logo; - } - -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallManifest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallManifest.java new file mode 100644 index 00000000000..c89f231f064 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallManifest.java @@ -0,0 +1,32 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2020 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +import org.jackhuang.hmcl.game.Artifact; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.gson.JsonSerializable; + +/** + * + * @author huangyuhui + */ +@Immutable +@JsonSerializable +public record ForgeInstallManifest(String profileName, String target, Artifact path, String version, String filePath, + String welcome, String minecraft, String mirrorList, String logo) { +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index 35e6b129d24..3ce2cddda72 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -17,30 +17,25 @@ */ package org.jackhuang.hmcl.download.forge; -import org.jackhuang.hmcl.download.*; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.UnsupportedInstallationException; +import org.jackhuang.hmcl.download.VersionMismatchException; import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.game.GameComponentAnalyzer; -import org.jackhuang.hmcl.game.GameComponentType; import org.jackhuang.hmcl.game.GameInstanceManifest; import org.jackhuang.hmcl.game.GameInstancePatch; import org.jackhuang.hmcl.task.FileDownloadTask; 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.versioning.GameVersionNumber; import java.io.IOException; -import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; -import java.util.Map; -import static org.jackhuang.hmcl.download.UnsupportedInstallationException.CLEANROOM_NOT_COMPATIBLE_WITH_FORGE; import static org.jackhuang.hmcl.download.UnsupportedInstallationException.UNSUPPORTED_LAUNCH_WRAPPER; -import static org.jackhuang.hmcl.util.StringUtils.removePrefix; -import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.detectForgeInstallerType; /** * @@ -75,9 +70,7 @@ public boolean doPreExecute() { public void preExecute() throws Exception { installer = Files.createTempFile("forge-installer", ".jar"); - dependent = new FileDownloadTask( - dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()), - installer, null); + dependent = new FileDownloadTask(dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()), installer, null); dependent.setCacheRepository(dependencyManager.getCacheRepository()); dependent.setCaching(true); dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); @@ -107,114 +100,22 @@ public Collection> getDependencies() { @Override public void execute() throws IOException, VersionMismatchException, UnsupportedInstallationException { String originalMainClass = manifest.mainClass(); + if (GameVersionNumber.compare("1.13", remote.getGameVersion()) <= 0) { // Forge 1.13 is not compatible with fabric. if (!GameComponentAnalyzer.FORGE_OPTIFINE_MAIN.contains(originalMainClass)) throw new UnsupportedInstallationException(UNSUPPORTED_LAUNCH_WRAPPER); } - if (detectForgeInstallerType(remote.getGameVersion(), installer)) { - dependency = new GameDownloadTask(dependencyManager, manifest) - .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( - dependencyManager, - manifest, - minecraftJar, - remote.getSelfVersion(), - installer)); - } else { - dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); - } - } - - /// Returns whether a Forge installer uses the processor-based format. - /// - /// @param gameVersion Minecraft version expected by the installation - /// @param installer the Forge installer JAR - /// @return `true` for the processor-based format, or `false` for the legacy format - /// @throws IOException if the installer profile is missing, malformed, or - /// unsupported - /// @throws VersionMismatchException if the installer targets another Minecraft version - public static boolean detectForgeInstallerType(String gameVersion, Path installer) throws IOException, VersionMismatchException { - try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - String installProfileText = Files.readString(fs.getPath("install_profile.json")); - Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); - if (installProfile.containsKey("spec")) { - ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); - return true; - } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { - ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); - if (!gameVersion.equals(profile.install().getMinecraft())) - throw new VersionMismatchException(profile.install().getMinecraft(), gameVersion); - return false; - } else { - throw new IOException(); - } - } - } + var type = detectForgeInstallerType(remote.getGameVersion(), installer); - /// Creates a task that installs Forge from a local installer JAR. - /// - /// Processor-based installers obtain a verified vanilla client JAR from shared cache storage; - /// neither format reads an instance JAR. - /// - /// @param dependencyManager repository-scoped download services - /// @param manifest working manifest receiving the Forge patch - /// @param gameVersion Minecraft version expected by the installation - /// @param installer the Forge installer JAR - /// @return the task producing the Forge patch - /// @throws IOException if the installer profile is missing, malformed, or - /// unsupported - /// @throws VersionMismatchException if the installer targets another Minecraft version - /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom - public static Task install( - DefaultDependencyManager dependencyManager, - GameInstanceManifest manifest, - String gameVersion, - Path installer) throws IOException, VersionMismatchException, UnsupportedInstallationException { - try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { - String installProfileText = Files.readString(fs.getPath("install_profile.json")); - Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); - if (installProfile.containsKey("spec")) { - checkCleanroomCompatibility(manifest, gameVersion); - ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); - return new GameDownloadTask(dependencyManager, manifest) - .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( - dependencyManager, - manifest, - minecraftJar, - modifyVersion(gameVersion, profile.getVersion()), - installer)); - } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { - checkCleanroomCompatibility(manifest, gameVersion); - ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class); - if (!gameVersion.equals(profile.install().getMinecraft())) - throw new VersionMismatchException(profile.install().getMinecraft(), gameVersion); - return new ForgeOldInstallTask(dependencyManager, manifest, modifyVersion(gameVersion, profile.install().getPath().getVersion().replaceAll("(?i)forge", "")), installer); - } else { - throw new IOException(); - } + switch (type) { + case LEGACY_MODLOADER, LEGACY_FML -> + dependency = new ForgeLegacyInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer, type); + case OLD -> + dependency = new ForgeOldInstallTask(dependencyManager, manifest, remote.getSelfVersion(), installer); + case NEW -> + dependency = new GameDownloadTask(dependencyManager, manifest).thenComposeAsync(minecraftJar -> new ForgeNewInstallTask(dependencyManager, manifest, minecraftJar, remote.getSelfVersion(), installer)); } } - - /// Rejects Forge installation when the manifest already contains Cleanroom. - /// - /// @param manifest working manifest receiving the Forge patch - /// @param gameVersion Minecraft version used for component analysis - /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom - private static void checkCleanroomCompatibility( - GameInstanceManifest manifest, - String gameVersion) throws UnsupportedInstallationException { - GameComponentAnalyzer analyzer = GameComponentAnalyzer.analyze(manifest, GameVersionNumber.asGameVersion(gameVersion)); - if (analyzer.has(GameComponentType.CLEANROOM)) { - throw new UnsupportedInstallationException(CLEANROOM_NOT_COMPATIBLE_WITH_FORGE); - } - } - - private static String modifyVersion(String gameVersion, String version) { - return removePrefix(removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"), "forge-"); - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java new file mode 100644 index 00000000000..955c7f60ba5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallation.java @@ -0,0 +1,161 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.download.UnsupportedInstallationException; +import org.jackhuang.hmcl.download.VersionMismatchException; +import org.jackhuang.hmcl.download.game.GameDownloadTask; +import org.jackhuang.hmcl.game.GameComponentAnalyzer; +import org.jackhuang.hmcl.game.GameComponentType; +import org.jackhuang.hmcl.game.GameInstanceManifest; +import org.jackhuang.hmcl.game.GameInstancePatch; +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.versioning.GameVersionNumber; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +import static org.jackhuang.hmcl.download.UnsupportedInstallationException.CLEANROOM_NOT_COMPATIBLE_WITH_FORGE; +import static org.jackhuang.hmcl.util.StringUtils.removePrefix; +import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; + +public final class ForgeInstallation { + private ForgeInstallation() { + throw new AssertionError(); + } + + /// Returns whether a Forge installer uses the processor-based format. + /// + /// @param gameVersion Minecraft version expected by the installation + /// @param installer the Forge installer JAR + /// @return `true` for the processor-based format, or `false` for the legacy format + /// @throws IOException if the installer profile is missing, malformed, or + /// unsupported + /// @throws VersionMismatchException if the installer targets another Minecraft version + public static ForgeInstallerType detectForgeInstallerType(String gameVersion, Path installer) throws IOException, VersionMismatchException { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + if ((Files.isRegularFile(fs.getPath("fmlversion.properties")) || Files.isRegularFile(fs.getPath("forgeversion.properties"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { + return ForgeInstallerType.LEGACY_FML; + } + + if ((Files.isRegularFile(fs.getPath("mod_MinecraftForge.class"))) && !Files.isRegularFile(fs.getPath("install_profile.json"))) { + return ForgeInstallerType.LEGACY_MODLOADER; + } + + String installProfileText = Files.readString(fs.getPath("install_profile.json")); + Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); + if (installProfile.containsKey("spec")) { + ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); + if (!gameVersion.equals(profile.minecraft())) + throw new VersionMismatchException(profile.minecraft(), gameVersion); + return ForgeInstallerType.NEW; + } else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) { + ForgeOldInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeOldInstallProfile.class); + if (!gameVersion.equals(profile.install().minecraft())) + throw new VersionMismatchException(profile.install().minecraft(), gameVersion); + return ForgeInstallerType.OLD; + } else { + throw new IOException(); + } + } + } + + /// Creates a task that installs Forge from a local installer JAR. + /// + /// Processor-based installers obtain a verified vanilla client JAR from shared cache storage; + /// neither format reads an instance JAR. + /// + /// @param dependencyManager repository-scoped download services + /// @param manifest working manifest receiving the Forge patch + /// @param gameVersion Minecraft version expected by the installation + /// @param installer the Forge installer JAR + /// @return the task producing the Forge patch + /// @throws IOException if the installer profile is missing, malformed, or + /// unsupported + /// @throws VersionMismatchException if the installer targets another Minecraft version + /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom + public static Task install(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, String gameVersion, Path installer) throws IOException, VersionMismatchException, UnsupportedInstallationException { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { + var type = detectForgeInstallerType(gameVersion, installer); + + switch (type) { + case LEGACY_MODLOADER, LEGACY_FML -> { + var forgeProfile = ForgeLegacyInstallProfile.parse(installer); + return new ForgeLegacyInstallTask(dependencyManager, manifest, forgeProfile != null ? forgeProfile.forgeVersion() : null, installer, type); + } + case OLD -> { + checkCleanroomCompatibility(manifest, gameVersion); + String installProfileText = Files.readString(fs.getPath("install_profile.json")); + ForgeOldInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeOldInstallProfile.class); + + return new ForgeOldInstallTask(dependencyManager, manifest, modifyVersion(gameVersion, profile.install().path().getVersion().replaceAll("(?i)forge", "")), installer); + } + case NEW -> { + checkCleanroomCompatibility(manifest, gameVersion); + String installProfileText = Files.readString(fs.getPath("install_profile.json")); + ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); + + return new GameDownloadTask(dependencyManager, manifest).thenComposeAsync(minecraftJar -> new ForgeNewInstallTask(dependencyManager, manifest, minecraftJar, modifyVersion(gameVersion, profile.version()), installer)); + } + default -> throw new IOException(); + } + } + } + + /// Rejects Forge installation when the manifest already contains Cleanroom. + /// + /// @param manifest working manifest receiving the Forge patch + /// @param gameVersion Minecraft version used for component analysis + /// @throws UnsupportedInstallationException if the manifest already contains Cleanroom + private static void checkCleanroomCompatibility( + GameInstanceManifest manifest, + String gameVersion) throws UnsupportedInstallationException { + GameComponentAnalyzer analyzer = GameComponentAnalyzer.analyze(manifest, GameVersionNumber.asGameVersion(gameVersion)); + + + if (analyzer.has(GameComponentType.CLEANROOM)) { + throw new UnsupportedInstallationException(CLEANROOM_NOT_COMPATIBLE_WITH_FORGE); + } + } + + private static String modifyVersion(String gameVersion, String version) { + return removePrefix(removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_"), "forge-"); + } + + public static String toLookupVersion(String gameVersion) { + return switch (gameVersion) { + case "1.7.10-pre4" -> "1.7.10_pre4"; + case "1.4" -> "1.4.0"; + default -> gameVersion; + }; + } + + public static String fromLookupVersion(String gameVersion) { + return switch (gameVersion) { + case "1.7.10_pre4" -> "1.7.10-pre4"; + case "1.4.0" -> "1.4"; + default -> gameVersion; + }; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java new file mode 100644 index 00000000000..094948a3662 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallerType.java @@ -0,0 +1,25 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +public enum ForgeInstallerType { + LEGACY_MODLOADER, + LEGACY_FML, + OLD, + NEW +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallProfile.java new file mode 100644 index 00000000000..da60d46473b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallProfile.java @@ -0,0 +1,79 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +import kala.compress.archivers.zip.ZipArchiveReader; +import org.jackhuang.hmcl.addon.meta.ForgeOldModMetadata; +import org.jackhuang.hmcl.addon.mod.LocalModFile; +import org.jackhuang.hmcl.util.io.CompressingUtils; +import org.jackhuang.hmcl.util.tree.ZipFileTree; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +public record ForgeLegacyInstallProfile(@Nullable String gameVersion, @Nullable String forgeVersion) { + @Nullable + public static ForgeLegacyInstallProfile parse(Path forgeArchive) { + try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(forgeArchive)) { + String gameVersion = null; + String forgeVersion = null; + + if (Files.isRegularFile(fs.getPath("fmlversion.properties"))) { + Properties properties = new Properties(); + properties.load(Files.newInputStream(fs.getPath("fmlversion.properties"))); + + gameVersion = properties.getProperty("fmlbuild.mcversion"); + } + + if (Files.isRegularFile(fs.getPath("forgeversion.properties"))) { + Properties properties = new Properties(); + properties.load(Files.newInputStream(fs.getPath("forgeversion.properties"))); + List list = new ArrayList<>(); + list.add(properties.getProperty("forge.major.number")); + list.add(properties.getProperty("forge.minor.number")); + list.add(properties.getProperty("forge.revision.number")); + list.add(properties.getProperty("forge.build.number")); + forgeVersion = String.join(".", list); + } + + if (forgeVersion == null && Files.isRegularFile(fs.getPath("mod_MinecraftForge.info"))) { + try (ZipFileTree tree = new ZipFileTree(new ZipArchiveReader(forgeArchive))) { + LocalModFile metadata = ForgeOldModMetadata.fromFile(null, forgeArchive, tree, "mod_MinecraftForge.info"); + forgeVersion = metadata.getVersion(); + } + } + + if (forgeVersion == null && gameVersion == null && !Files.isRegularFile(fs.getPath("mod_MinecraftForge.class"))) { + return null; + } + + return new ForgeLegacyInstallProfile(gameVersion, forgeVersion); + } catch (IOException ioException) { + LOG.warning("Failed to parse forge archive " + forgeArchive, ioException); + return null; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java new file mode 100644 index 00000000000..5151f74efe7 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeLegacyInstallTask.java @@ -0,0 +1,138 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui 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 . + */ +package org.jackhuang.hmcl.download.forge; + +import org.jackhuang.hmcl.download.ArtifactMalformedException; +import org.jackhuang.hmcl.download.DefaultDependencyManager; +import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.util.DigestUtils; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +public final class ForgeLegacyInstallTask extends Task { + public static final Library MODLOADER_LIBRARY = new Library(new Artifact("modloader", "modloader", "1.1")); + public static final String MODLOADER_DOWNLOAD_URL = "https://hmcl.glavo.site/metadata/fmllibs/ModLoader%201.1.zip"; + public static final Library MODLOADER_MP_LIBRARY = new Library(new Artifact("modloader", "modloader-mp", "1.1")); + public static final String MODLOADER_MP_DOWNLOAD_URL = "https://hmcl.glavo.site/metadata/fmllibs/ModLoaderMP%201.1%20v4.zip"; + + private final DefaultDependencyManager dependencyManager; + private final GameInstanceManifest manifest; + private final Path installer; + private final @Nullable String selfVersion; + private final ForgeInstallerType type; + private final List> dependencies = new ArrayList<>(1); + + ForgeLegacyInstallTask(DefaultDependencyManager dependencyManager, GameInstanceManifest manifest, @Nullable String selfVersion, Path installer, ForgeInstallerType type) throws IOException { + this.dependencyManager = dependencyManager; + this.manifest = manifest; + this.installer = installer; + if (selfVersion != null) + this.selfVersion = selfVersion; + else + this.selfVersion = DigestUtils.digestToString("SHA-1", installer); + this.type = type; + + setSignificance(TaskSignificance.MAJOR); + } + + @Override + public List> getDependencies() { + return dependencies; + } + + @Override + public void preExecute() throws Exception { + if (type == ForgeInstallerType.LEGACY_MODLOADER) { + GameRepository gameRepository = dependencyManager.getGameRepository(); + + Path modloaderFile = gameRepository.getLayout().getLibraryFile(manifest.id(), MODLOADER_LIBRARY); + var modloaderDownloadTask = new FileDownloadTask(MODLOADER_DOWNLOAD_URL, modloaderFile, null); + modloaderDownloadTask.setCacheRepository(dependencyManager.getCacheRepository()); + modloaderDownloadTask.setCaching(true); + modloaderDownloadTask.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); + dependencies.add(modloaderDownloadTask); + + Path modloaderMpFile = gameRepository.getLayout().getLibraryFile(manifest.id(), MODLOADER_MP_LIBRARY); + var modloaderMpDownloadTask = new FileDownloadTask(MODLOADER_MP_DOWNLOAD_URL, modloaderMpFile, null); + modloaderMpDownloadTask.setCacheRepository(dependencyManager.getCacheRepository()); + modloaderMpDownloadTask.setCaching(true); + modloaderMpDownloadTask.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); + dependencies.add(modloaderMpDownloadTask); + } + } + + @Override + public boolean doPreExecute() { + return true; + } + + @Override + public void execute() throws Exception { + try (ZipFile zipFile = new ZipFile(installer.toFile())) { + ZipEntry entry1 = zipFile.getEntry("fmlversion.properties"); + ZipEntry entry2 = zipFile.getEntry("mod_MinecraftForge.class"); + + InputStream stream = null; + InputStream stream2 = null; + + if (entry1 != null) { + stream = zipFile.getInputStream(entry1); + } + if (entry2 != null) { + stream2 = zipFile.getInputStream(entry2); + } + + if (stream == null && stream2 == null) { + throw new ArtifactMalformedException("Malformed forge installer file, forgeversion.properties and mod_MinecraftForge.class both does not exist."); + } + + Library forgeLibrary = new Library(new Artifact("net.minecraftforge", "forge", selfVersion)); + GameRepository gameRepository = dependencyManager.getGameRepository(); + Path forgeFile = gameRepository.getLayout().getLibraryFile(manifest.id(), forgeLibrary); + Files.createDirectories(forgeFile.getParent()); + + try (InputStream is = Files.newInputStream(installer); OutputStream os = Files.newOutputStream(forgeFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + is.transferTo(os); + } + + List libraries; + if (type == ForgeInstallerType.LEGACY_MODLOADER) { + libraries = List.of(forgeLibrary, MODLOADER_LIBRARY, MODLOADER_MP_LIBRARY); + } else { + libraries = List.of(forgeLibrary); + } + + setResult(new GameInstancePatch(GameComponentType.FORGE.getPatchId(), selfVersion, GameInstancePatch.PRIORITY_LOADER, null, null, libraries)); + } catch (ZipException ex) { + throw new ArtifactMalformedException("Malformed forge installer file", ex); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallProfile.java index d388403cd68..85c01b3fd6e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallProfile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallProfile.java @@ -29,47 +29,33 @@ import java.util.stream.Collectors; @Immutable -public class ForgeNewInstallProfile implements Validation { - - private final int spec; - private final String minecraft; - private final String json; - private final String version; - private final Artifact path; - private final List libraries; - private final List processors; - private final Map data; - - public ForgeNewInstallProfile(int spec, String minecraft, String json, String version, Artifact path, List libraries, List processors, Map data) { - this.spec = spec; - this.minecraft = minecraft; - this.json = json; - this.version = version; - this.path = path; - this.libraries = libraries; - this.processors = processors; - this.data = data; - } +public record ForgeNewInstallProfile(int spec, String minecraft, String json, String version, Artifact path, + List libraries, List processors, + Map data) implements Validation { /** * Specification for install_profile.json. */ - public int getSpec() { + @Override + public int spec() { return spec; } /** * Vanilla game version that this installer supports. */ - public String getMinecraft() { + @Override + public String minecraft() { return minecraft; } /** * Version json to be installed. + * * @return path of the version json relative to the installer JAR file. */ - public String getJson() { + @Override + public String json() { return json; } @@ -77,12 +63,14 @@ public String getJson() { * * @return forge version. */ - public String getVersion() { + @Override + public String version() { return version; } /** * Maven artifact path for the main jar to install. + * * @return artifact path of the main jar. */ public Optional getPath() { @@ -91,16 +79,19 @@ public Optional getPath() { /** * Libraries that processors depend on. + * * @return the required dependencies. */ - public List getLibraries() { + @Override + public List libraries() { return libraries == null ? Collections.emptyList() : libraries; } /** * Tasks to be executed to setup modded environment. */ - public List getProcessors() { + @Override + public List processors() { if (processors == null) return Collections.emptyList(); return processors.stream().filter(p -> p.isSide("client")).collect(Collectors.toList()); } @@ -114,7 +105,7 @@ public Map getData() { if (data == null) return new HashMap<>(); - return data.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getClient())); + return data.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().client())); } @Override @@ -123,23 +114,12 @@ public void validate() throws JsonParseException, TolerableValidationException { throw new JsonParseException("ForgeNewInstallProfile is malformed"); } - public static class Processor implements Validation { - private final List sides; - private final Artifact jar; - private final List classpath; - private final List args; - private final Map outputs; - - public Processor(List sides, Artifact jar, List classpath, List args, Map outputs) { - this.sides = sides; - this.jar = jar; - this.classpath = classpath; - this.args = args; - this.outputs = outputs; - } + public record Processor(List sides, Artifact jar, List classpath, List args, + Map outputs) implements Validation { /** * Check which side this processor should be run on. We only support client install currently. + * * @param side can be one of "client", "server", "extract". * @return true if the processor can run on the side. */ @@ -149,17 +129,21 @@ public boolean isSide(String side) { /** * The executable jar of this processor task. Will be executed in installation process. + * * @return the artifact path of executable jar. */ - public Artifact getJar() { + @Override + public Artifact jar() { return jar; } /** * The dependencies of this processor task. + * * @return the artifact path of dependencies. */ - public List getClasspath() { + @Override + public List classpath() { return classpath == null ? Collections.emptyList() : classpath; } @@ -170,10 +154,12 @@ public List getClasspath() { * {entry}: Get corresponding value of the entry in {@link ForgeNewInstallProfile#getData()} * {MINECRAFT_JAR}: path of the Minecraft jar. * {SIDE}: values other than "client" will be ignored. + * * @return arguments to pass to the processor jar. * @see ForgeNewInstallTask#parseLiteral(String, Map, ExceptionalFunction) */ - public List getArgs() { + @Override + public List args() { return args == null ? Collections.emptyList() : args; } @@ -182,10 +168,12 @@ public List getArgs() { * Arguments to pass to the processor jar. * Keys can be in one of [artifact] or {entry}. Should be file path. * Values can be in one of {entry} or 'literal'. Should be SHA-1 checksum. + * * @return files output from this processor. * @see ForgeNewInstallTask#parseLiteral(String, Map, ExceptionalFunction) */ - public Map getOutputs() { + @Override + public Map outputs() { return outputs == null ? Collections.emptyMap() : outputs; } @@ -196,21 +184,18 @@ public void validate() throws JsonParseException, TolerableValidationException { } } - public static class Datum { - private final String client; - - public Datum(String client) { - this.client = client; - } + public record Datum(String client) { /** * Can be in the following formats: * [value]: An artifact path. * 'value': A string literal. * value: A file in the installer package, to be extracted to a temp folder, and then have the absolute path in replacements. + * * @return Value to use for the client install */ - public String getClient() { + @Override + public String client() { return client; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index 2d5d60b41fe..b8e6fac7ff3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -20,9 +20,10 @@ import org.jackhuang.hmcl.download.ArtifactMalformedException; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile.Processor; -import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.download.game.GameInstanceJsonDownloadTask; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.DigestUtils; @@ -34,7 +35,6 @@ import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.CommandBuilder; -import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.SystemUtils; import org.jetbrains.annotations.NotNull; @@ -53,15 +53,15 @@ import java.util.jar.JarFile; import java.util.zip.ZipException; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class ForgeNewInstallTask extends Task { private class ProcessorTask extends Task { - private Processor processor; - private Map vars; + private final Processor processor; + private final Map vars; public ProcessorTask(@NotNull Processor processor, @NotNull Map vars) { this.processor = processor; @@ -74,7 +74,7 @@ public void execute() throws Exception { Map outputs = new HashMap<>(); boolean miss = false; - for (Map.Entry entry : processor.getOutputs().entrySet()) { + for (Map.Entry entry : processor.outputs().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); @@ -105,11 +105,11 @@ public void execute() throws Exception { } } - if (!processor.getOutputs().isEmpty() && !miss) { + if (!processor.outputs().isEmpty() && !miss) { return; } - Path jar = gameRepository.getLayout().getArtifactFile(processor.getJar()); + Path jar = gameRepository.getLayout().getArtifactFile(processor.jar()); if (!Files.isRegularFile(jar)) throw new FileNotFoundException("Game processor file not found, should be downloaded in preprocess"); @@ -125,8 +125,8 @@ public void execute() throws Exception { command.add(JavaRuntime.getDefault().getBinary().toString()); command.add("-cp"); - List classpath = new ArrayList<>(processor.getClasspath().size() + 1); - for (Artifact artifact : processor.getClasspath()) { + List classpath = new ArrayList<>(processor.classpath().size() + 1); + for (Artifact artifact : processor.classpath()) { Path file = gameRepository.getLayout().getArtifactFile(artifact); if (!Files.isRegularFile(file)) throw new Exception("Game processor dependency missing"); @@ -137,8 +137,8 @@ public void execute() throws Exception { command.add(mainClass); - List args = new ArrayList<>(processor.getArgs().size()); - for (String arg : processor.getArgs()) { + List args = new ArrayList<>(processor.args().size()); + for (String arg : processor.args()) { String parsed = parseLiteral(arg, vars); if (parsed == null) throw new ArtifactMalformedException("Invalid forge installation configuration"); @@ -147,7 +147,7 @@ public void execute() throws Exception { command.addAll(args); - LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); + LOG.info("Executing external processor " + processor.jar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); int exitCode = SystemUtils.callExternalProcess(command); if (exitCode != 0) throw new IOException("Game processor exited abnormally with code " + exitCode); @@ -304,10 +304,10 @@ public boolean doPreExecute() { public void preExecute() throws Exception { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { profile = JsonUtils.fromNonNullJson(Files.readString(fs.getPath("install_profile.json")), ForgeNewInstallProfile.class); - processors = profile.getProcessors(); - forgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.getJson())), GameInstanceManifest.class); + processors = profile.processors(); + forgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.json())), GameInstanceManifest.class); - for (Library library : profile.getLibraries()) { + for (Library library : profile.libraries()) { Path file = fs.getPath("maven").resolve(library.getPath()); if (Files.exists(file)) { Path dest = gameRepository.getLayout().getLibraryFile(manifest.id(), library); @@ -326,7 +326,7 @@ public void preExecute() throws Exception { throw new ArtifactMalformedException("Malformed forge installer file", ex); } - dependents.add(new GameLibrariesTask(dependencyManager, manifest, true, profile.getLibraries())); + dependents.add(new GameLibrariesTask(dependencyManager, manifest, true, profile.libraries())); } private Map parseOptions(List args, Map vars) { @@ -354,7 +354,7 @@ private Map parseOptions(List args, Map } private Task patchDownloadMojangMappingsTask(Processor processor, Map vars) { - Map options = parseOptions(processor.getArgs(), vars); + Map options = parseOptions(processor.args(), vars); if (!"DOWNLOAD_MOJMAPS".equals(options.get("task")) || !"client".equals(options.get("side"))) return null; String version = options.get("version"); @@ -424,7 +424,7 @@ public void execute() throws Exception { vars.put("SIDE", "client"); vars.put("MINECRAFT_JAR", FileUtils.getAbsolutePath(isolatedMinecraftJar)); - vars.put("MINECRAFT_VERSION", profile.getMinecraft()); + vars.put("MINECRAFT_VERSION", profile.minecraft()); vars.put("ROOT", FileUtils.getAbsolutePath(gameRepository.getBaseDirectory())); vars.put("INSTALLER", installer.toAbsolutePath().toString()); vars.put("LIBRARY_DIR", FileUtils.getAbsolutePath(gameRepository.getLayout().getLibrariesDirectory())); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallProfile.java similarity index 75% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallProfile.java index 25482181990..19e28cda595 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallProfile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallProfile.java @@ -27,21 +27,10 @@ /// @author huangyuhui @NotNullByDefault @JsonSerializable -public record ForgeInstallProfile(@SerializedName("install") ForgeInstall install, - @SerializedName("versionInfo") GameInstanceManifest versionInfo) { - - public ForgeInstallProfile { +public record ForgeOldInstallProfile(@SerializedName("install") ForgeInstallManifest install, + @SerializedName("versionInfo") GameInstanceManifest versionInfo) { + public ForgeOldInstallProfile { Objects.requireNonNull(install, "install"); Objects.requireNonNull(versionInfo, "versionInfo"); } - - @Override - public ForgeInstall install() { - return install; - } - - @Override - public GameInstanceManifest versionInfo() { - return versionInfo; - } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java index 6084d457e81..3c5f5ff3690 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeOldInstallTask.java @@ -19,11 +19,7 @@ import org.jackhuang.hmcl.download.ArtifactMalformedException; import org.jackhuang.hmcl.download.DefaultDependencyManager; -import org.jackhuang.hmcl.game.GameComponentType; -import org.jackhuang.hmcl.game.GameInstanceManifest; -import org.jackhuang.hmcl.game.GameInstancePatch; -import org.jackhuang.hmcl.game.GameRepository; -import org.jackhuang.hmcl.game.Library; +import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -38,7 +34,7 @@ import java.util.zip.ZipException; import java.util.zip.ZipFile; -public class ForgeOldInstallTask extends Task { +public final class ForgeOldInstallTask extends Task { private final DefaultDependencyManager dependencyManager; private final GameInstanceManifest manifest; @@ -71,15 +67,15 @@ public void execute() throws Exception { InputStream stream = zipFile.getInputStream(zipFile.getEntry("install_profile.json")); if (stream == null) throw new ArtifactMalformedException("Malformed forge installer file, install_profile.json does not exist."); - ForgeInstallProfile installProfile = JsonUtils.fromNonNullJsonFully(stream, ForgeInstallProfile.class); + ForgeOldInstallProfile installProfile = JsonUtils.fromNonNullJsonFully(stream, ForgeOldInstallProfile.class); // unpack the universal jar in the installer file. - Library forgeLibrary = new Library(installProfile.install().getPath()); + Library forgeLibrary = new Library(installProfile.install().path()); GameRepository gameRepository = dependencyManager.getGameRepository(); Path forgeFile = gameRepository.getLayout().getLibraryFile(manifest.id(), forgeLibrary); Files.createDirectories(forgeFile.getParent()); - ZipEntry forgeEntry = zipFile.getEntry(installProfile.install().getFilePath()); + ZipEntry forgeEntry = zipFile.getEntry(installProfile.install().filePath()); try (InputStream is = zipFile.getInputStream(forgeEntry); OutputStream os = Files.newOutputStream(forgeFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { is.transferTo(os); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java index a7bb771935f..f037f049da3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersion.java @@ -26,61 +26,8 @@ * @author huangyuhui */ @Immutable -public final class ForgeVersion implements Validation { - - private final String branch; - private final String mcversion; - private final String jobver; - private final String version; - private final int build; - private final long modified; - private final String[][] files; - - /** - * No-arg constructor for Gson. - */ - @SuppressWarnings("unused") - public ForgeVersion() { - this(null, null, null, null, 0, 0, null); - } - - public ForgeVersion(String branch, String mcversion, String jobver, String version, int build, long modified, String[][] files) { - this.branch = branch; - this.mcversion = mcversion; - this.jobver = jobver; - this.version = version; - this.build = build; - this.modified = modified; - this.files = files; - } - - public String getBranch() { - return branch; - } - - public String getGameVersion() { - return mcversion; - } - - public String getJobver() { - return jobver; - } - - public String getVersion() { - return version; - } - - public int getBuild() { - return build; - } - - public long getModified() { - return modified; - } - - public String[][] getFiles() { - return files; - } +public record ForgeVersion(String branch, String mcversion, String jobver, String version, int build, long modified, + String[][] files) implements Validation { @Override public void validate() throws JsonParseException { @@ -91,5 +38,4 @@ public void validate() throws JsonParseException { if (mcversion == null) throw new JsonParseException("ForgeVersion mcversion cannot be null"); } - } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java index be63c853989..4cbe5e0f2fa 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionList.java @@ -29,6 +29,9 @@ import java.util.Collections; import java.util.Map; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.fromLookupVersion; +import static org.jackhuang.hmcl.download.forge.ForgeInstallation.toLookupVersion; + /** * * @author huangyuhui @@ -45,14 +48,6 @@ public boolean hasType() { return false; } - private static String toLookupVersion(String gameVersion) { - return "1.7.10-pre4".equals(gameVersion) ? "1.7.10_pre4" : gameVersion; - } - - private static String fromLookupVersion(String lookupVersion) { - return "1.7.10_pre4".equals(lookupVersion) ? "1.7.10-pre4" : lookupVersion; - } - @Override public Task refreshAsync() { return new GetTask(FORGE_LIST).thenGetJsonAsync(ForgeVersionRoot.class) @@ -64,29 +59,29 @@ public Task refreshAsync() { return; versions.clear(); - for (Map.Entry entry : root.getGameVersions().entrySet()) { + for (Map.Entry entry : root.mcversion().entrySet()) { String gameVersion = fromLookupVersion(VersionNumber.normalize(entry.getKey())); for (int v : entry.getValue()) { - ForgeVersion version = root.getNumber().get(v); + ForgeVersion version = root.number().get(v); if (version == null) continue; - String jar = null; - for (String[] file : version.getFiles()) - if (file.length > 1 && "installer".equals(file[1])) { - String classifier = version.getGameVersion() + "-" + version.getVersion() - + (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : ""); - String fileName = root.getArtifact() + "-" + classifier + "-" + file[1] + "." + file[0]; - jar = root.getWebPath() + classifier + "/" + fileName; + String installer = null; + for (String[] file : version.files()) + if (file.length > 1) { + String classifier = version.mcversion() + "-" + version.version() + + (StringUtils.isNotBlank(version.branch()) ? "-" + version.branch() : ""); + String fileName = root.artifact() + "-" + classifier + "-" + file[1] + "." + file[0]; + installer = root.webpath() + classifier + "/" + fileName; } - if (jar == null) + if (installer == null) continue; versions.put(gameVersion, new ForgeRemoteVersion( - toLookupVersion(version.getGameVersion()), - version.getVersion(), - version.getModified() > 0 ? Instant.ofEpochSecond(version.getModified()) : null, - Collections.singletonList(jar) + toLookupVersion(version.mcversion()), + version.version(), + version.modified() > 0 ? Instant.ofEpochSecond(version.modified()) : null, + Collections.singletonList(installer) )); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java index cad542569b9..cf294625bb1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeVersionRoot.java @@ -28,74 +28,9 @@ * @author huangyuhui */ @Immutable -public final class ForgeVersionRoot implements Validation { - - private final String artifact; - private final String webpath; - private final String adfly; - private final String homepage; - private final String name; - private final Map branches; - private final Map mcversion; - private final Map promos; - private final Map number; - - /** - * No-arg constructor for Gson. - */ - @SuppressWarnings("unused") - public ForgeVersionRoot() { - this(null, null, null, null, null, null, null, null, null); - } - - public ForgeVersionRoot(String artifact, String webpath, String adfly, String homepage, String name, Map branches, Map mcversion, Map promos, Map number) { - this.artifact = artifact; - this.webpath = webpath; - this.adfly = adfly; - this.homepage = homepage; - this.name = name; - this.branches = branches; - this.mcversion = mcversion; - this.promos = promos; - this.number = number; - } - - public String getArtifact() { - return artifact; - } - - public String getWebPath() { - return webpath; - } - - public String getAdfly() { - return adfly; - } - - public String getHomePage() { - return homepage; - } - - public String getName() { - return name; - } - - public Map getBranches() { - return branches; - } - - public Map getGameVersions() { - return mcversion; - } - - public Map getPromos() { - return promos; - } - - public Map getNumber() { - return number; - } - +public record ForgeVersionRoot(String artifact, String webpath, String adfly, String homepage, String name, + Map branches, Map mcversion, Map promos, + Map number) implements Validation { @Override public void validate() throws JsonParseException { if (number == null) @@ -103,5 +38,4 @@ public void validate() throws JsonParseException { if (mcversion == null) throw new JsonParseException("ForgeVersionRoot mcversion cannot be null"); } - } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java index fcd6e3ea716..9087eb93786 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameLibrariesTask.java @@ -17,19 +17,25 @@ */ package org.jackhuang.hmcl.download.game; +import com.google.gson.reflect.TypeToken; +import org.jackhuang.hmcl.addon.mod.ModLoaderType; import org.jackhuang.hmcl.download.AbstractDependencyManager; +import org.jackhuang.hmcl.download.forge.ForgeLegacyInstallTask; import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.DigestUtils; +import org.jackhuang.hmcl.util.gson.JsonSerializable; +import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.Files; @@ -37,6 +43,7 @@ import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -124,7 +131,7 @@ private static boolean shouldDownloadFMLLib(FMLLib fmlLib, Path file) { return true; try { - return !DigestUtils.digestToString("SHA-1", file).equalsIgnoreCase(fmlLib.sha1); + return !DigestUtils.digestToString("SHA-1", file).equalsIgnoreCase(fmlLib.checksum); } catch (IOException e) { LOG.warning("Unable to calc hash value of file " + file, e); return true; @@ -136,21 +143,31 @@ private static boolean shouldDownloadFMLLib(FMLLib fmlLib, Path file) { public void execute() throws IOException { int progress = 0; GameRepository gameRepository = dependencyManager.getGameRepository(); + + @Nullable GameComponentAnalyzer analyzer = null; + + try { + analyzer = gameRepository.getInstance(manifest.id()).getAnalyzer(); + } catch (Exception e) { + LOG.warning("Failed to analyze instance", e); + } + for (Library library : libraries) { + boolean handled = false; + if (!library.appliesToCurrentEnvironment()) { continue; } // https://github.com/HMCL-dev/HMCL/issues/3975 - if (library.is("net.minecraftforge", "minecraftforge") - && gameRepository instanceof DefaultGameRepository defaultGameRepository) { + if (analyzer != null && analyzer.has(ModLoaderType.FORGE)) { List fmlLibs = getFMLLibs(library.version()); if (fmlLibs != null) { - Path libDir = defaultGameRepository.getBaseDirectory().resolve("lib") + Path libDir = gameRepository.getBaseDirectory().resolve("lib") .toAbsolutePath().normalize(); for (FMLLib fmlLib : fmlLibs) { - Path file = libDir.resolve(fmlLib.name); + Path file = libDir.resolve(fmlLib.filename); if (shouldDownloadFMLLib(fmlLib, file)) { List uris = dependencyManager.getDownloadProvider() .injectURLWithCandidates(fmlLib.downloadUrl()); @@ -165,9 +182,7 @@ public void execute() throws IOException { if ("optifine".equals(library.groupId()) && Files.exists(file)) { if (Files.exists(file) && libraries.stream().filter(it -> it.is("optifine", "OptiFine")) .anyMatch(it -> it.version().startsWith("1.20.4_"))) { - @Nullable String forgeVersion = GameComponentAnalyzer.analyze(manifest, GameVersionNumber.asGameVersion("1.20.4")) - .getVersion(GameComponentType.FORGE); - if (forgeVersion != null && GameComponentAnalyzer.FORGE_OPTIFINE_BROKEN_RANGE.contains(VersionNumber.asVersion(forgeVersion))) { + if (analyzer != null && analyzer.getVersion(GameComponentType.FORGE) != null && GameComponentAnalyzer.FORGE_OPTIFINE_BROKEN_RANGE.contains(VersionNumber.asVersion(analyzer.getVersion(GameComponentType.FORGE)))) { try (FileSystem fs2 = CompressingUtils.createWritableZipFileSystem(file)) { Files.deleteIfExists(fs2.getPath("/META-INF/mods.toml")); } catch (IOException e) { @@ -183,6 +198,7 @@ public void execute() throws IOException { "Bundled HMCLMultiMCBootstrap is missing.")) { Files.createDirectories(file.getParent()); Files.copy(input, file, StandardCopyOption.REPLACE_EXISTING); + handled = true; } } } else if (library.is("org.jackhuang.hmcl", "transformer-discovery-service")) { @@ -192,10 +208,26 @@ public void execute() throws IOException { "Bundled HMCLTransformerDiscoveryService is missing.")) { Files.createDirectories(file.getParent()); Files.copy(input, file, StandardCopyOption.REPLACE_EXISTING); + handled = true; + } + } else if (library.groupId().equals("modloader")) { + String url = switch (library.artifactId()) { + case "modloader" -> ForgeLegacyInstallTask.MODLOADER_DOWNLOAD_URL; + case "modloader-mp" -> ForgeLegacyInstallTask.MODLOADER_MP_DOWNLOAD_URL; + default -> null; + }; + if (url != null && shouldDownloadLibrary(gameRepository, manifest, library, integrityCheck)) { + var fileDownloadTask = new FileDownloadTask(url, file, null); + fileDownloadTask.setCacheRepository(dependencyManager.getCacheRepository()); + fileDownloadTask.setCaching(true); + fileDownloadTask.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); + dependencies.add(fileDownloadTask.withCounter("hmcl.install.libraries")); + + handled = true; } } - if (shouldDownloadLibrary(gameRepository, manifest, library, integrityCheck) && (library.hasDownloadURL() || !"optifine".equals(library.groupId()))) { + if (!handled && shouldDownloadLibrary(gameRepository, manifest, library, integrityCheck) && (library.hasDownloadURL() || !"optifine".equals(library.groupId()))) { dependencies.add(new LibraryDownloadTask(dependencyManager, file, library).withCounter("hmcl.install.libraries")); } else { dependencyManager.getCacheRepository().tryCacheLibrary(library, file); @@ -211,30 +243,34 @@ public void execute() throws IOException { } private static @Nullable List getFMLLibs(String forgeVersion) { - if (forgeVersion == null) - return null; - - // Minecraft 1.5.2 - if (forgeVersion.startsWith("7.8.1.")) { - return List.of( - new FMLLib("argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"), - new FMLLib("guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", - "https://repo1.maven.org/maven2/com/google/guava/guava/14.0-rc3/guava-14.0-rc3.jar"), - new FMLLib("asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", - "https://repo1.maven.org/maven2/org/ow2/asm/asm-all/4.1/asm-all-4.1.jar"), - new FMLLib("bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", - "https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48.jar"), - new FMLLib("deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9"), - new FMLLib("scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85") - ); + //noinspection DataFlowIssue + try (Reader input = new InputStreamReader( + GameLibrariesTask.class.getResourceAsStream("/assets/game/fmllibs.json"))) { + var fmlLibs = JsonUtils.GSON.fromJson(input, new TypeToken>>() { + }); + + if (forgeVersion.startsWith("4.")) + return fmlLibs.get("1.3.x"); + else if (forgeVersion.startsWith("6.") || forgeVersion.startsWith("5.")) + return fmlLibs.get("1.4.x"); + else if (forgeVersion.startsWith("7.7.0.5")) + return fmlLibs.get("1.5"); + else if (forgeVersion.startsWith("7.7.0.6") || forgeVersion.startsWith("7.7.1.") || forgeVersion.startsWith("7.7.2.")) + return fmlLibs.get("1.5.1"); + else if (forgeVersion.startsWith("7.8.")) + return fmlLibs.get("1.5.2"); + } catch (Throwable e) { + LOG.warning("Failed to load fmllibs", e); } + return null; } - private record FMLLib(String name, String sha1, String downloadUrl) { - FMLLib(String name, String sha1) { - this(name, sha1, "https://hmcl.glavo.site/metadata/fmllibs/" + name); + @JsonSerializable + private record FMLLib(String filename, String checksum) { + public String downloadUrl() { + return "https://hmcl.glavo.site/metadata/fmllibs/" + filename; } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java index 510073482fb..e83b19b3b42 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java @@ -19,7 +19,6 @@ import org.jackhuang.hmcl.game.GameComponentType; import org.jackhuang.hmcl.game.GameInstance; -import org.jackhuang.hmcl.game.GameInstanceManifest; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; @@ -41,18 +40,14 @@ public final class GameVerificationFixTask extends Task { /// The detected Minecraft version. private final GameVersionNumber gameVersion; - /// The effective launch manifest used to detect Forge. - private final GameInstanceManifest manifest; - /// Creates a task for a fixed instance and effective launch manifest. /// /// @param instance the instance whose client jar may be modified /// @param gameVersion the detected Minecraft version /// @param manifest the effective launch manifest used to detect Forge - public GameVerificationFixTask(GameInstance instance, GameVersionNumber gameVersion, GameInstanceManifest manifest) { + public GameVerificationFixTask(GameInstance instance, GameVersionNumber gameVersion) { this.instance = instance; this.gameVersion = gameVersion; - this.manifest = manifest; setSignificance(TaskSignificance.MODERATE); } @@ -68,6 +63,9 @@ public void execute() throws IOException { try (FileSystem fs = CompressingUtils.createWritableZipFileSystem(jar, StandardCharsets.UTF_8)) { Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.DSA")); Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.SF")); + Files.deleteIfExists(fs.getPath("META-INF/CODESIGN.SF")); + Files.deleteIfExists(fs.getPath("META-INF/CODESIGN.RSA")); + Files.deleteIfExists(fs.getPath("META-INF/MANIFEST.MF")); } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java index 6181794c583..1e73bb5cdf8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java @@ -19,7 +19,8 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.VersionMismatchException; -import org.jackhuang.hmcl.download.forge.*; +import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile; +import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask; import org.jackhuang.hmcl.download.game.GameDownloadTask; import org.jackhuang.hmcl.game.GameComponentType; import org.jackhuang.hmcl.game.GameInstanceManifest; @@ -33,7 +34,9 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; import static org.jackhuang.hmcl.util.StringUtils.removePrefix; import static org.jackhuang.hmcl.util.StringUtils.removeSuffix; @@ -124,14 +127,14 @@ public static Task install( Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class); if (GameComponentType.FORGE.getPatchId().equals(installProfile.get("profile")) && (Files.exists(fs.getPath("META-INF/NEOFORGE.RSA")) || installProfileText.contains("neoforge"))) { ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); + if (!gameVersion.equals(profile.minecraft())) + throw new VersionMismatchException(profile.minecraft(), gameVersion); return new GameDownloadTask(dependencyManager, manifest) .thenComposeAsync(minecraftJar -> new ForgeNewInstallTask( dependencyManager, manifest, minecraftJar, - modifyNeoForgeOldVersion(gameVersion, profile.getVersion()), + modifyNeoForgeOldVersion(gameVersion, profile.version()), installer)) .thenApplyAsync(neoForgeVersion -> { if (!neoForgeVersion.id().equals(GameComponentType.FORGE.getPatchId()) || neoForgeVersion.version() == null) { @@ -144,14 +147,14 @@ public static Task install( }); } else if (GameComponentType.NEO_FORGE.getPatchId().equals(installProfile.get("profile")) || "NeoForge".equals(installProfile.get("profile"))) { ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class); - if (!gameVersion.equals(profile.getMinecraft())) - throw new VersionMismatchException(profile.getMinecraft(), gameVersion); + if (!gameVersion.equals(profile.minecraft())) + throw new VersionMismatchException(profile.minecraft(), gameVersion); return new GameDownloadTask(dependencyManager, manifest) .thenComposeAsync(minecraftJar -> new NeoForgeOldInstallTask( dependencyManager, manifest, minecraftJar, - modifyNeoForgeNewVersion(profile.getVersion()), + modifyNeoForgeNewVersion(profile.version()), installer)); } else { throw new IOException(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java index 522729d4df5..dc9ae474b13 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java @@ -21,9 +21,10 @@ import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile; import org.jackhuang.hmcl.download.forge.ForgeNewInstallProfile.Processor; -import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.download.game.GameInstanceJsonDownloadTask; +import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.game.*; +import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.DigestUtils; @@ -34,7 +35,6 @@ import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.CommandBuilder; -import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.util.platform.SystemUtils; import org.jetbrains.annotations.NotNull; @@ -53,8 +53,8 @@ import java.util.jar.JarFile; import java.util.zip.ZipException; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.gson.JsonUtils.fromNonNullJson; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public class NeoForgeOldInstallTask extends Task { @@ -74,7 +74,7 @@ public void execute() throws Exception { Map outputs = new HashMap<>(); boolean miss = false; - for (Map.Entry entry : processor.getOutputs().entrySet()) { + for (Map.Entry entry : processor.outputs().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); @@ -105,11 +105,11 @@ public void execute() throws Exception { } } - if (!processor.getOutputs().isEmpty() && !miss) { + if (!processor.outputs().isEmpty() && !miss) { return; } - Path jar = gameRepository.getLayout().getArtifactFile(processor.getJar()); + Path jar = gameRepository.getLayout().getArtifactFile(processor.jar()); if (!Files.isRegularFile(jar)) throw new FileNotFoundException("Game processor file not found, should be downloaded in preprocess"); @@ -125,8 +125,8 @@ public void execute() throws Exception { command.add(JavaRuntime.getDefault().getBinary().toString()); command.add("-cp"); - List classpath = new ArrayList<>(processor.getClasspath().size() + 1); - for (Artifact artifact : processor.getClasspath()) { + List classpath = new ArrayList<>(processor.classpath().size() + 1); + for (Artifact artifact : processor.classpath()) { Path file = gameRepository.getLayout().getArtifactFile(artifact); if (!Files.isRegularFile(file)) throw new Exception("Game processor dependency missing"); @@ -137,8 +137,8 @@ public void execute() throws Exception { command.add(mainClass); - List args = new ArrayList<>(processor.getArgs().size()); - for (String arg : processor.getArgs()) { + List args = new ArrayList<>(processor.args().size()); + for (String arg : processor.args()) { String parsed = parseLiteral(arg, vars); if (parsed == null) throw new ArtifactMalformedException("Invalid forge installation configuration"); @@ -147,7 +147,7 @@ public void execute() throws Exception { command.addAll(args); - LOG.info("Executing external processor " + processor.getJar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); + LOG.info("Executing external processor " + processor.jar().toString() + ", command line: " + new CommandBuilder().addAll(command).toString()); int exitCode = SystemUtils.callExternalProcess(command); if (exitCode != 0) throw new IOException("Game processor exited abnormally with code " + exitCode); @@ -288,10 +288,10 @@ public boolean doPreExecute() { public void preExecute() throws Exception { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) { profile = JsonUtils.fromNonNullJson(Files.readString(fs.getPath("install_profile.json")), ForgeNewInstallProfile.class); - processors = profile.getProcessors(); - neoForgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.getJson())), GameInstanceManifest.class); + processors = profile.processors(); + neoForgeVersion = JsonUtils.fromNonNullJson(Files.readString(fs.getPath(profile.json())), GameInstanceManifest.class); - for (Library library : profile.getLibraries()) { + for (Library library : profile.libraries()) { Path file = fs.getPath("maven").resolve(library.getPath()); if (Files.exists(file)) { Path dest = gameRepository.getLayout().getLibraryFile(manifest.id(), library); @@ -310,7 +310,7 @@ public void preExecute() throws Exception { throw new ArtifactMalformedException("Malformed forge installer file", ex); } - dependents.add(new GameLibrariesTask(dependencyManager, manifest, true, profile.getLibraries())); + dependents.add(new GameLibrariesTask(dependencyManager, manifest, true, profile.libraries())); } private Map parseOptions(List args, Map vars) { @@ -338,7 +338,7 @@ private Map parseOptions(List args, Map } private Task patchDownloadMojangMappingsTask(Processor processor, Map vars) { - Map options = parseOptions(processor.getArgs(), vars); + Map options = parseOptions(processor.args(), vars); if (!"DOWNLOAD_MOJMAPS".equals(options.get("task")) || !"client".equals(options.get("side"))) return null; String version = options.get("version"); @@ -408,7 +408,7 @@ public void execute() throws Exception { vars.put("SIDE", "client"); vars.put("MINECRAFT_JAR", FileUtils.getAbsolutePath(isolatedMinecraftJar)); - vars.put("MINECRAFT_VERSION", profile.getMinecraft()); + vars.put("MINECRAFT_VERSION", profile.minecraft()); vars.put("ROOT", FileUtils.getAbsolutePath(gameRepository.getBaseDirectory())); vars.put("INSTALLER", installer.toAbsolutePath().toString()); vars.put("LIBRARY_DIR", FileUtils.getAbsolutePath(gameRepository.getLayout().getLibrariesDirectory())); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java index 041467d669b..4bd3a517f85 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/DefaultGameInstance.java @@ -168,7 +168,7 @@ public GameInstanceManifest getResolvedManifest() { @Override public GameComponentAnalyzer getAnalyzer() { if (analyzer == null) { - analyzer = GameComponentAnalyzer.analyze(manifest.isModifiable() ? manifest : getResolvedManifest(), getVersion()); + analyzer = GameComponentAnalyzer.analyze(manifest.isModifiable() ? manifest : getResolvedManifest(), getVersion(), this.getInstanceJarFile()); } return analyzer; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameComponentAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameComponentAnalyzer.java index ea91f49e031..c321d1f130e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameComponentAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameComponentAnalyzer.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.game; import org.jackhuang.hmcl.addon.mod.ModLoaderType; +import org.jackhuang.hmcl.download.forge.ForgeLegacyInstallProfile; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jackhuang.hmcl.util.versioning.VersionRange; @@ -25,6 +26,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Unmodifiable; +import java.nio.file.Path; import java.util.*; import java.util.regex.Pattern; @@ -34,7 +36,8 @@ public final class GameComponentAnalyzer implements Iterable(GameComponentType.class); @Nullable String bootstrapVersion = null; @@ -67,6 +70,13 @@ private static GameComponentAnalyzer analyze( } } + if (jar != null && !components.containsKey(GameComponentType.FORGE)) { + var legacyForgeVersion = ForgeLegacyInstallProfile.parse(jar); + if (legacyForgeVersion != null) { + components.put(GameComponentType.FORGE, new Mark(GameComponentType.FORGE, legacyForgeVersion.forgeVersion(), false)); + } + } + return new GameComponentAnalyzer(standaloneManifest, components, bootstrapVersion); } @@ -74,7 +84,14 @@ public static GameComponentAnalyzer analyze(GameInstanceManifest manifest, @Null if (manifest.inheritsFrom() != null) throw new IllegalArgumentException("GameComponentAnalyzer can only analyze independent game version"); - return analyze(manifest, manifest, gameVersion); + return analyze(manifest, gameVersion, null); + } + + public static GameComponentAnalyzer analyze(GameInstanceManifest manifest, @Nullable GameVersionNumber gameVersion, @Nullable Path jar) { + if (manifest.inheritsFrom() != null) + throw new IllegalArgumentException("GameComponentAnalyzer can only analyze independent game version"); + + return analyze(manifest, manifest, gameVersion, jar); } private final GameInstanceManifest manifest; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 86d358044a2..cebc8e782b7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -283,6 +283,16 @@ private Command generateCommandLine(Path nativeFolder) throws IOException { libraryClasspath.removeIf(c -> c.contains("icu4j-core-mojang")); } + if (instance.hasComponent(GameComponentType.FORGE) && instance.getVersion().compareTo("1.2") < 0) { + var path = instance.getLayout().getLibrariesDirectory().resolve("modloader").resolve("temp"); + decompressModloader(path); + // modloader/modloader + // modloader/modloader-mp + + libraryClasspath.removeIf(c -> c.contains("modloader" + File.separator + "modloader")); + libraryClasspath.add(FileUtils.getAbsolutePath(path)); + } + Path jar = instance.getInstanceJarFile(); if (!Files.isRegularFile(jar)) throw new IOException("Minecraft jar does not exist"); @@ -492,6 +502,19 @@ public void decompressNatives(Path destination) throws NotDecompressingNativesEx } } + public void decompressModloader(Path destination) throws IOException { + LOG.info("Decompress modloader to " + destination); + + + FileUtils.cleanDirectoryQuietly(destination); + for (Library library : manifest.getLibraries()) + if (library.groupId().equals("modloader")) + new Unzipper(instance.getLayout().getLibraryFile(instance.getId(), library), destination) + .setReplaceExistentFile(false) + .unzip(); + + } + /// Returns the detected Minecraft version string for this instance, if known. /// /// @return the version string, or empty when detection failed diff --git a/HMCLCore/src/main/resources/assets/game/fmllibs.json b/HMCLCore/src/main/resources/assets/game/fmllibs.json new file mode 100644 index 00000000000..07c058c2b02 --- /dev/null +++ b/HMCLCore/src/main/resources/assets/game/fmllibs.json @@ -0,0 +1,112 @@ +{ + "1.3.x": [ + { + "filename": "argo-2.25.jar", + "checksum": "bb672829fde76cb163004752b86b0484bd0a7f4b" + }, + { + "filename": "guava-12.0.1.jar", + "checksum": "b8e78b9af7bf45900e14c6f958486b6ca682195f" + }, + { + "filename": "asm-all-4.0.jar", + "checksum": "98308890597acb64047f7e896638e0d98753ae82" + } + ], + "1.4.x": [ + { + "filename": "argo-2.25.jar", + "checksum": "bb672829fde76cb163004752b86b0484bd0a7f4b" + }, + { + "filename": "guava-12.0.1.jar", + "checksum": "b8e78b9af7bf45900e14c6f958486b6ca682195f" + }, + { + "filename": "asm-all-4.0.jar", + "checksum": "98308890597acb64047f7e896638e0d98753ae82" + }, + { + "filename": "bcprov-jdk15on-147.jar", + "checksum": "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb" + } + ], + "1.5": [ + { + "filename": "argo-small-3.2.jar", + "checksum": "58912ea2858d168c50781f956fa5b59f0f7c6b51" + }, + { + "filename": "guava-14.0-rc3.jar", + "checksum": "931ae21fa8014c3ce686aaa621eae565fefb1a6a" + }, + { + "filename": "asm-all-4.1.jar", + "checksum": "054986e962b88d8660ae4566475658469595ef58" + }, + { + "filename": "bcprov-jdk15on-148.jar", + "checksum": "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65" + }, + { + "filename": "deobfuscation_data_1.5.zip", + "checksum": "5f7c142d53776f16304c0bbe10542014abad6af8" + }, + { + "filename": "scala-library.jar", + "checksum": "458d046151ad179c85429ed7420ffb1eaf6ddf85" + } + ], + "1.5.1": [ + { + "filename": "argo-small-3.2.jar", + "checksum": "58912ea2858d168c50781f956fa5b59f0f7c6b51" + }, + { + "filename": "guava-14.0-rc3.jar", + "checksum": "931ae21fa8014c3ce686aaa621eae565fefb1a6a" + }, + { + "filename": "asm-all-4.1.jar", + "checksum": "054986e962b88d8660ae4566475658469595ef58" + }, + { + "filename": "bcprov-jdk15on-148.jar", + "checksum": "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65" + }, + { + "filename": "deobfuscation_data_1.5.1.zip", + "checksum": "22e221a0d89516c1f721d6cab056a7e37471d0a6" + }, + { + "filename": "scala-library.jar", + "checksum": "458d046151ad179c85429ed7420ffb1eaf6ddf85" + } + ], + "1.5.2": [ + { + "filename": "argo-small-3.2.jar", + "checksum": "58912ea2858d168c50781f956fa5b59f0f7c6b51" + }, + { + "filename": "guava-14.0-rc3.jar", + "checksum": "931ae21fa8014c3ce686aaa621eae565fefb1a6a" + }, + { + "filename": "asm-all-4.1.jar", + "checksum": "054986e962b88d8660ae4566475658469595ef58" + }, + { + "filename": "bcprov-jdk15on-148.jar", + "checksum": "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65" + }, + { + "filename": "deobfuscation_data_1.5.2.zip", + "checksum": "446e55cd986582c70fcf12cb27bc00114c5adfd9" + }, + { + "filename": "scala-library.jar", + "checksum": "458d046151ad179c85429ed7420ffb1eaf6ddf85" + } + ] +} diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/DefaultGameInstanceTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/DefaultGameInstanceTest.java index 0a5efa439d4..548d98f38f6 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/DefaultGameInstanceTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/DefaultGameInstanceTest.java @@ -33,6 +33,7 @@ import org.jackhuang.hmcl.modpack.server.ServerModpackCompletionTask; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.DigestUtils; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.NotNullByDefault; @@ -53,13 +54,7 @@ import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotSame; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /// Tests snapshot-bound behavior of [DefaultGameInstance]. @NotNullByDefault @@ -324,7 +319,7 @@ protected void updateProgressImmediately(double progress) { }; var executor = task.executor(); - assertTrue(executor.test(), () -> String.valueOf(executor.getException())); + assertTrue(executor.test(), () -> StringUtils.getStackTrace(executor.getException())); assertTrue(Files.isRegularFile(minecraftJar)); } @@ -384,7 +379,7 @@ public void testVerificationFixKeepsCapturedInstance(@TempDir Path tempDirectory tempDirectory.resolve("versions/instance/current.json")); writeSignedJar(current.getInstanceJarFile()); - new GameVerificationFixTask(captured, GameVersionNumber.asGameVersion("1.5.2"), manifest).execute(); + new GameVerificationFixTask(captured, GameVersionNumber.asGameVersion("1.5.2")).execute(); assertFalse(hasZipEntry(captured.getInstanceJarFile(), "META-INF/MOJANG_C.DSA")); assertFalse(hasZipEntry(captured.getInstanceJarFile(), "META-INF/MOJANG_C.SF"));