From 3c6712d64a42e4ec200909912e72749499aaca79 Mon Sep 17 00:00:00 2001 From: ZekerZhayard Date: Fri, 1 Mar 2024 14:51:26 +0800 Subject: [PATCH 1/2] Thanks to LexManos, he replaced all installers with the new version, so the codes for compatibility with the old installer do not need to exist. The task of checking for extra files is now done by the installer instead of ForgeWrapper itself, thus avoiding some inaccurate checking. --- build.gradle | 11 +- common/build.gradle | 11 -- .../forgewrapper/util/CheckedLambdaUtil.java | 40 ---- gradle.properties | 2 +- jigsaw/build.gradle | 4 - .../installer/util/ModuleUtil.java | 60 ++++-- legacy/build.gradle | 22 --- .../installer/util/AbstractInstaller.java | 12 -- .../installer/util/InstallerV0.java | 37 ---- settings.gradle | 2 - .../forgewrapper/installer/Bootstrap.java | 19 +- .../forgewrapper/installer/Installer.java | 173 +++++++++++++++-- .../forgewrapper/installer/Main.java | 61 +++--- .../installer/detector/IFileDetector.java | 182 ------------------ .../installer/util/InstallerV1.java | 38 ---- 15 files changed, 246 insertions(+), 428 deletions(-) delete mode 100644 common/build.gradle delete mode 100644 common/src/main/java/io/github/zekerzhayard/forgewrapper/util/CheckedLambdaUtil.java delete mode 100644 legacy/build.gradle delete mode 100644 legacy/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/AbstractInstaller.java delete mode 100644 legacy/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV0.java delete mode 100644 src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV1.java diff --git a/build.gradle b/build.gradle index 45e6dee..b0bcf5d 100644 --- a/build.gradle +++ b/build.gradle @@ -15,9 +15,6 @@ group = "io.github.zekerzhayard" archivesBaseName = rootProject.name configurations { - provided { - implementation.extendsFrom provided - } multirelase { implementation.extendsFrom multirelase } @@ -34,11 +31,9 @@ repositories { dependencies { compileOnly "com.google.code.gson:gson:2.8.7" compileOnly "cpw.mods:modlauncher:8.0.9" - compileOnly "net.minecraftforge:installer:2.1.9" + compileOnly "net.minecraftforge:installer:2.2.7" compileOnly "net.sf.jopt-simple:jopt-simple:5.0.4" - provided project(":common") - provided project(":legacy") multirelase project(":jigsaw") } @@ -60,10 +55,6 @@ jar { "GitCommit": String.valueOf(System.getenv("GITHUB_SHA")) ]) - from configurations.provided.files.collect { - zipTree(it) - } - into "META-INF/versions/9", { from configurations.multirelase.files.collect { zipTree(it) diff --git a/common/build.gradle b/common/build.gradle deleted file mode 100644 index abd2997..0000000 --- a/common/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ - -plugins { - id "java" - id "eclipse" - id "maven-publish" -} - -sourceCompatibility = targetCompatibility = 1.8 -compileJava { - sourceCompatibility = targetCompatibility = 1.8 -} diff --git a/common/src/main/java/io/github/zekerzhayard/forgewrapper/util/CheckedLambdaUtil.java b/common/src/main/java/io/github/zekerzhayard/forgewrapper/util/CheckedLambdaUtil.java deleted file mode 100644 index c36b8b3..0000000 --- a/common/src/main/java/io/github/zekerzhayard/forgewrapper/util/CheckedLambdaUtil.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.github.zekerzhayard.forgewrapper.util; - -import java.util.function.BiConsumer; -import java.util.function.Consumer; - -public class CheckedLambdaUtil { - public static Consumer wrapConsumer(CheckedConsumer consumer) { - return consumer; - } - - public static BiConsumer wrapBiConsumer(CheckedBiConsumer biconsumer) { - return biconsumer; - } - - public interface CheckedConsumer extends Consumer { - void checkedAccept(T t) throws Throwable; - - @Override - default void accept(T t) { - try { - this.checkedAccept(t); - } catch (Throwable th) { - throw new RuntimeException(th); - } - } - } - - public interface CheckedBiConsumer extends BiConsumer { - void checkedAccept(T t, U u) throws Throwable; - - @Override - default void accept(T t, U u) { - try { - this.checkedAccept(t, u); - } catch (Throwable th) { - throw new RuntimeException(th); - } - } - } -} diff --git a/gradle.properties b/gradle.properties index 4fb1212..96acf92 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.daemon = false -fw_version = 1.5.8 +fw_version = 1.6.0 diff --git a/jigsaw/build.gradle b/jigsaw/build.gradle index 08c394a..a555ec3 100644 --- a/jigsaw/build.gradle +++ b/jigsaw/build.gradle @@ -26,7 +26,3 @@ configurations { } } } - -dependencies { - compileOnly project(":common") -} diff --git a/jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java b/jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java index e15d39c..09792b1 100644 --- a/jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java +++ b/jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java @@ -12,6 +12,7 @@ import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -19,10 +20,8 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; -import io.github.zekerzhayard.forgewrapper.util.CheckedLambdaUtil; import sun.misc.Unsafe; public class ModuleUtil { @@ -53,17 +52,27 @@ public static void addModules(String modulePath) throws Throwable { MethodHandle loadModuleMH = IMPL_LOOKUP.findVirtual(Class.forName("jdk.internal.loader.BuiltinClassLoader"), "loadModule", MethodType.methodType(void.class, ModuleReference.class)); // Resolve modules to a new config and load all extra modules in system class loader (unnamed modules for now) - Configuration config = Configuration.resolveAndBind(finder, List.of(ModuleLayer.boot().configuration()), finder, finder.findAll().stream().filter(mref -> !ModuleLayer.boot().findModule(mref.descriptor().name()).isPresent()).peek(CheckedLambdaUtil.wrapConsumer(mref -> loadModuleMH.invokeWithArguments(ClassLoader.getSystemClassLoader(), mref))).map(mref -> mref.descriptor().name()).collect(Collectors.toList())); + List roots = new ArrayList<>(); + for (ModuleReference mref : finder.findAll()) { + String name = mref.descriptor().name(); + if (!ModuleLayer.boot().findModule(name).isPresent()) { + loadModuleMH.invokeWithArguments(ClassLoader.getSystemClassLoader(), mref); + roots.add(name); + } + } + Configuration config = Configuration.resolveAndBind(finder, List.of(ModuleLayer.boot().configuration()), finder, roots); // Copy the new config graph to boot module layer config MethodHandle graphGetter = IMPL_LOOKUP.findGetter(Configuration.class, "graph", Map.class); HashMap> graphMap = new HashMap<>((Map>) graphGetter.invokeWithArguments(config)); MethodHandle cfSetter = IMPL_LOOKUP.findSetter(ResolvedModule.class, "cf", Configuration.class); // Reset all extra resolved modules config to boot module layer config - graphMap.forEach(CheckedLambdaUtil.wrapBiConsumer((k, v) -> { - cfSetter.invokeWithArguments(k, ModuleLayer.boot().configuration()); - v.forEach(CheckedLambdaUtil.wrapConsumer(m -> cfSetter.invokeWithArguments(m, ModuleLayer.boot().configuration()))); - })); + for (Map.Entry> entry : graphMap.entrySet()) { + cfSetter.invokeWithArguments(entry.getKey(), ModuleLayer.boot().configuration()); + for (ResolvedModule resolvedModule : entry.getValue()) { + cfSetter.invokeWithArguments(resolvedModule, ModuleLayer.boot().configuration()); + } + } graphMap.putAll((Map>) graphGetter.invokeWithArguments(ModuleLayer.boot().configuration())); IMPL_LOOKUP.findSetter(Configuration.class, "graph", Map.class).invokeWithArguments(ModuleLayer.boot().configuration(), new HashMap<>(graphMap)); @@ -92,7 +101,17 @@ public static void addModules(String modulePath) throws Throwable { // Add reads from extra modules to jdk modules MethodHandle implAddReadsMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddReads", MethodType.methodType(void.class, Module.class)); - config.modules().forEach(rm -> ModuleLayer.boot().findModule(rm.name()).ifPresent(m -> oldBootModules.forEach(brm -> ModuleLayer.boot().findModule(brm.name()).ifPresent(CheckedLambdaUtil.wrapConsumer(bm -> implAddReadsMH.invokeWithArguments(m, bm)))))); + for (ResolvedModule resolvedModule : config.modules()) { + Module module = ModuleLayer.boot().findModule(resolvedModule.name()).orElse(null); + if (module != null) { + for (ResolvedModule bootResolvedModule : oldBootModules) { + Module bootModule = ModuleLayer.boot().findModule(bootResolvedModule.name()).orElse(null); + if (bootModule != null) { + implAddReadsMH.invokeWithArguments(module, bootModule); + } + } + } + } } public static void addExports(List exports) { @@ -124,13 +143,26 @@ private enum TypeToAdd { } void implAdd(List extras) { - extras.stream().map(ModuleUtil::parseModuleExtra).filter(Optional::isPresent).map(Optional::get).forEach(CheckedLambdaUtil.wrapConsumer(data -> ModuleLayer.boot().findModule(data.module).ifPresent(CheckedLambdaUtil.wrapConsumer(m -> { - if ("ALL-UNNAMED".equals(data.target)) { - this.implAddToAllUnnamedMH.invokeWithArguments(m, data.packages); - } else { - ModuleLayer.boot().findModule(data.target).ifPresent(CheckedLambdaUtil.wrapConsumer(tm -> this.implAddMH.invokeWithArguments(m, data.packages, tm))); + for (String extra : extras) { + ParserData data = ModuleUtil.parseModuleExtra(extra).orElse(null); + if (data != null) { + Module module = ModuleLayer.boot().findModule(data.module).orElse(null); + if (module != null) { + try { + if ("ALL-UNNAMED".equals(data.target)) { + this.implAddToAllUnnamedMH.invokeWithArguments(module, data.packages); + } else { + Module targetModule = ModuleLayer.boot().findModule(data.target).orElse(null); + if (targetModule != null) { + this.implAddMH.invokeWithArguments(module, data.packages, targetModule); + } + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + } } - })))); + } } } diff --git a/legacy/build.gradle b/legacy/build.gradle deleted file mode 100644 index 4778335..0000000 --- a/legacy/build.gradle +++ /dev/null @@ -1,22 +0,0 @@ - -plugins { - id "java" - id "eclipse" -} - -sourceCompatibility = targetCompatibility = 1.8 -compileJava { - sourceCompatibility = targetCompatibility = 1.8 -} - -repositories { - mavenCentral() - maven { - name = "forge" - url = "https://maven.minecraftforge.net/" - } -} - -dependencies { - compileOnly "net.minecraftforge:installer:2.0.24" -} diff --git a/legacy/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/AbstractInstaller.java b/legacy/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/AbstractInstaller.java deleted file mode 100644 index 6b2d550..0000000 --- a/legacy/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/AbstractInstaller.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.zekerzhayard.forgewrapper.installer.util; - -import java.io.File; - -import net.minecraftforge.installer.actions.ProgressCallback; -import net.minecraftforge.installer.json.Install; - -public abstract class AbstractInstaller { - public abstract Install loadInstallProfile(); - - public abstract boolean runClientInstall(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar, File installerJar); -} diff --git a/legacy/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV0.java b/legacy/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV0.java deleted file mode 100644 index ff1cd43..0000000 --- a/legacy/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV0.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.zekerzhayard.forgewrapper.installer.util; - -import java.io.File; -import java.util.function.Predicate; - -import net.minecraftforge.installer.actions.ClientInstall; -import net.minecraftforge.installer.actions.ProgressCallback; -import net.minecraftforge.installer.json.Install; -import net.minecraftforge.installer.json.Util; - -public class InstallerV0 extends AbstractInstaller { - @Override - public Install loadInstallProfile() { - return Util.loadInstallProfile(); - } - - @Override - public boolean runClientInstall(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar, File installerJar) { - return new ClientInstall4MultiMC(profile, monitor, libraryDir, minecraftJar).run(null, input -> true); - } - - public static class ClientInstall4MultiMC extends ClientInstall { - protected File libraryDir; - protected File minecraftJar; - - public ClientInstall4MultiMC(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar) { - super(profile, monitor); - this.libraryDir = libraryDir; - this.minecraftJar = minecraftJar; - } - - @Override - public boolean run(File target, Predicate optionals) { - return this.processors.process(this.libraryDir, this.minecraftJar); - } - } -} diff --git a/settings.gradle b/settings.gradle index 34c563d..3444232 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,3 @@ rootProject.name = 'ForgeWrapper' -include 'common' -include 'legacy' include 'jigsaw' diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java index 3604371..90d3325 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java @@ -7,11 +7,12 @@ import io.github.zekerzhayard.forgewrapper.installer.util.ModuleUtil; public class Bootstrap { - public static void bootstrap(List jvmArgs, String minecraftJar, String libraryDir) throws Throwable { + public static void bootstrap(String[] jvmArgs, String minecraftJar, String libraryDir) throws Throwable { // Replace all placeholders - List replacedJvmArgs = new ArrayList<>(); - for (String arg : jvmArgs) { - replacedJvmArgs.add(arg.replace("${classpath}", System.getProperty("java.class.path").replace(File.separator, "/")).replace("${classpath_separator}", File.pathSeparator).replace("${library_directory}", libraryDir).replace("${version_name}", minecraftJar.substring(0, minecraftJar.lastIndexOf('.')))); + String[] replacedJvmArgs = new String[jvmArgs.length]; + for (int i = 0; i < jvmArgs.length; i++) { + String arg = jvmArgs[i]; + replacedJvmArgs[i] = arg.replace("${classpath}", System.getProperty("java.class.path").replace(File.separator, "/")).replace("${classpath_separator}", File.pathSeparator).replace("${library_directory}", libraryDir).replace("${version_name}", minecraftJar.substring(0, minecraftJar.lastIndexOf('.'))); } jvmArgs = replacedJvmArgs; @@ -27,23 +28,23 @@ public static void bootstrap(List jvmArgs, String minecraftJar, String l String modulePath = null; List addExports = new ArrayList<>(); List addOpens = new ArrayList<>(); - for (int i = 0; i < jvmArgs.size(); i++) { - String arg = jvmArgs.get(i); + for (int i = 0; i < jvmArgs.length; i++) { + String arg = jvmArgs[i]; if (arg.equals("-p") || arg.equals("--module-path")) { - modulePath = jvmArgs.get(i + 1); + modulePath = jvmArgs[i + 1]; } else if (arg.startsWith("--module-path=")) { modulePath = arg.split("=", 2)[1]; } if (arg.equals("--add-exports")) { - addExports.add(jvmArgs.get(i + 1)); + addExports.add(jvmArgs[i + 1]); } else if (arg.startsWith("--add-exports=")) { addExports.add(arg.split("=", 2)[1]); } if (arg.equals("--add-opens")) { - addOpens.add(jvmArgs.get(i + 1)); + addOpens.add(jvmArgs[i + 1]); } else if (arg.startsWith("--add-opens=")) { addOpens.add(arg.split("=", 2)[1]); } diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java index 278688e..10895b1 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java @@ -1,20 +1,47 @@ package io.github.zekerzhayard.forgewrapper.installer; import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; -import io.github.zekerzhayard.forgewrapper.installer.util.AbstractInstaller; -import io.github.zekerzhayard.forgewrapper.installer.util.InstallerV0; -import io.github.zekerzhayard.forgewrapper.installer.util.InstallerV1; +import net.minecraftforge.installer.DownloadUtils; +import net.minecraftforge.installer.actions.PostProcessors; import net.minecraftforge.installer.actions.ProgressCallback; +import net.minecraftforge.installer.json.Artifact; import net.minecraftforge.installer.json.Install; import net.minecraftforge.installer.json.InstallV1; import net.minecraftforge.installer.json.Util; +import net.minecraftforge.installer.json.Version; public class Installer { - public static boolean install(File libraryDir, File minecraftJar, File installerJar) { - AbstractInstaller installer = createInstaller(); + private static InstallV1Wrapper wrapper; + private static InstallV1Wrapper getWrapper(File librariesDir) { + if (wrapper == null) { + wrapper = new InstallV1Wrapper(Util.loadInstallProfile(), librariesDir); + } + return wrapper; + } + + public static Map getData(File librariesDir) { + Map data = new HashMap<>(); + Version0 version = Version0.loadVersion(getWrapper(librariesDir)); + data.put("mainClass", version.getMainClass()); + data.put("jvmArgs", version.getArguments().getJvm()); + data.put("extraLibraries", getExtraLibraries(version)); + return data; + } + + public static boolean install(File libraryDir, File minecraftJar, File installerJar) throws Throwable { ProgressCallback monitor = ProgressCallback.withOutputs(System.out); - Install profile = installer.loadInstallProfile(); if (System.getProperty("java.net.preferIPv4Stack") == null) { System.setProperty("java.net.preferIPv4Stack", "true"); } @@ -23,21 +50,129 @@ public static boolean install(File libraryDir, File minecraftJar, File installer String jvmVersion = System.getProperty("java.vm.version", "missing jvm version"); monitor.message(String.format("JVM info: %s - %s - %s", vendor, javaVersion, jvmVersion)); monitor.message("java.net.preferIPv4Stack=" + System.getProperty("java.net.preferIPv4Stack")); - return installer.runClientInstall(profile, monitor, libraryDir, minecraftJar, installerJar); + monitor.message("Current Time: " + new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new Date())); + + // MinecraftForge has removed all old installers since 2024/2/27, but they still exist in NeoForge. + PostProcessors processors = new PostProcessors(wrapper, true, monitor); + Method processMethod = PostProcessors.class.getMethod("process", File.class, File.class, File.class, File.class); + if (boolean.class.equals(processMethod.getReturnType())) { + return (boolean) processMethod.invoke(processors, libraryDir, minecraftJar, libraryDir.getParentFile(), installerJar); + } else { + return processMethod.invoke(processors, libraryDir, minecraftJar, libraryDir.getParentFile(), installerJar) != null; + } + } + + // Some libraries in the version json are not available via direct download, + // so they are not available in the MultiMC meta json, + // so wee need to get them manually. + private static List getExtraLibraries(Version0 version) { + List paths = new ArrayList<>(); + for (Version.Library library : version.getLibraries()) { + Version.LibraryDownload artifact = library.getDownloads().getArtifact(); + if (artifact.getUrl().isEmpty()) { + paths.add(artifact.getPath()); + } + } + return paths; + } + + public static class InstallV1Wrapper extends InstallV1 { + protected Map> processors = new HashMap<>(); + protected File librariesDir; + + public InstallV1Wrapper(InstallV1 v1, File librariesDir) { + super(v1); + this.serverJarPath = v1.getServerJarPath(); + this.librariesDir = librariesDir; + } + + @Override + public List getProcessors(String side) { + List processor = this.processors.get(side); + if (processor == null) { + checkProcessorFiles(processor = super.getProcessors(side), super.getData("client".equals(side)), this.librariesDir); + this.processors.put(side, processor); + } + return processor; + } + + private static void checkProcessorFiles(List processors, Map data, File base) { + Map artifactData = new HashMap<>(); + for (Map.Entry entry : data.entrySet()) { + String value = entry.getValue(); + if (value.charAt(0) == '[' && value.charAt(value.length() - 1) == ']') { + artifactData.put("{" + entry.getKey() + "}", Artifact.from(value.substring(1, value.length() - 1)).getLocalPath(base)); + } + } + + Map> outputsMap = new HashMap<>(); + label: + for (Processor processor : processors) { + Map outputs = new HashMap<>(); + if (processor.getOutputs().isEmpty()) { + String[] args = processor.getArgs(); + for (int i = 0; i < args.length; i++) { + for (Map.Entry entry : artifactData.entrySet()) { + if (args[i].contains(entry.getKey())) { + // We assume that all files that exist but don't have the sha1 checksum are valid. + if (entry.getValue().exists()) { + outputs.put(entry.getKey(), DownloadUtils.getSha1(entry.getValue())); + } else { + outputsMap.clear(); + break label; + } + } + } + } + outputsMap.put(processor, outputs); + } + } + for (Map.Entry> entry : outputsMap.entrySet()) { + setOutputs(entry.getKey(), entry.getValue()); + } + } + + private static Field outputsField; + private static void setOutputs(Processor processor, Map outputs) { + try { + if (outputsField == null) { + outputsField = Processor.class.getDeclaredField("outputs"); + outputsField.setAccessible(true); + } + outputsField.set(processor, outputs); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } } - private static AbstractInstaller createInstaller() { - try { - Class installerClass = Util.class.getMethod("loadInstallProfile").getReturnType(); - if (installerClass.equals(Install.class)) { - return new InstallerV0(); - } else if (installerClass.equals(InstallV1.class)) { - return new InstallerV1(); - } else { - throw new IllegalArgumentException("Unable to determine the installer version. (" + installerClass + ")"); - } - } catch (Throwable t) { - throw new RuntimeException(t); + public static class Version0 extends Version { + + public static Version0 loadVersion(Install profile) { + try (InputStream stream = Util.class.getResourceAsStream(profile.getJson())) { + return Util.GSON.fromJson(new InputStreamReader(stream, StandardCharsets.UTF_8), Version0.class); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + protected String mainClass; + protected Version0.Arguments arguments; + + public String getMainClass() { + return mainClass; + } + + public Version0.Arguments getArguments() { + return arguments; + } + + public static class Arguments { + protected String[] jvm; + + public String[] getJvm() { + return jvm; + } } } } diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java index 37876a6..f15a2a8 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java @@ -3,8 +3,10 @@ import java.io.File; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -14,6 +16,7 @@ import io.github.zekerzhayard.forgewrapper.installer.util.ModuleUtil; public class Main { + @SuppressWarnings("unchecked") public static void main(String[] args) throws Throwable { // --fml.neoForgeVersion 20.2.20-beta --fml.fmlVersion 1.0.2 --fml.mcVersion 1.20.2 --fml.neoFormVersion 20231019.002635 --launchTarget forgeclient @@ -30,40 +33,44 @@ public static void main(String[] args) throws Throwable { String forgeFullVersion = isNeoForge ? forgeVersion : mcVersion + "-" + forgeVersion; IFileDetector detector = DetectorLoader.loadDetector(); - try { - Bootstrap.bootstrap(detector.getJvmArgs(forgeGroup, forgeArtifact, forgeFullVersion), detector.getMinecraftJar(mcVersion).getFileName().toString(), detector.getLibraryDir().toAbsolutePath().toString()); - } catch (Throwable ignored) { - // Avoid this bunch of hacks that nuke the whole wrapper. + // Check installer jar. + Path installerJar = detector.getInstallerJar(forgeGroup, forgeArtifact, forgeFullVersion); + if (!isFile(installerJar)) { + throw new RuntimeException("Unable to detect the forge installer!"); } - if (!detector.checkExtraFiles(forgeGroup, forgeArtifact, forgeFullVersion)) { - System.out.println("Some extra libraries are missing! Running the installer to generate them now."); - // Check installer jar. - Path installerJar = detector.getInstallerJar(forgeGroup, forgeArtifact, forgeFullVersion); - if (!IFileDetector.isFile(installerJar)) { - throw new RuntimeException("Unable to detect the forge installer!"); - } + // Check vanilla Minecraft jar. + Path minecraftJar = detector.getMinecraftJar(mcVersion); + if (!isFile(minecraftJar)) { + throw new RuntimeException("Unable to detect the Minecraft jar!"); + } + + try (URLClassLoader ucl = URLClassLoader.newInstance(new URL[] { + Main.class.getProtectionDomain().getCodeSource().getLocation(), + Launcher.class.getProtectionDomain().getCodeSource().getLocation(), + installerJar.toUri().toURL() + }, ModuleUtil.getPlatformClassLoader())) { + Class installer = ucl.loadClass("io.github.zekerzhayard.forgewrapper.installer.Installer"); - // Check vanilla Minecraft jar. - Path minecraftJar = detector.getMinecraftJar(mcVersion); - if (!IFileDetector.isFile(minecraftJar)) { - throw new RuntimeException("Unable to detect the Minecraft jar!"); + Map data = (Map) installer.getMethod("getData", File.class).invoke(null, detector.getLibraryDir().toFile()); + try { + Bootstrap.bootstrap((String[]) data.get("jvmArgs"), detector.getMinecraftJar(mcVersion).getFileName().toString(), detector.getLibraryDir().toAbsolutePath().toString()); + } catch (Throwable t) { + // Avoid this bunch of hacks that nuke the whole wrapper. + t.printStackTrace(); } - try (URLClassLoader ucl = URLClassLoader.newInstance(new URL[] { - Main.class.getProtectionDomain().getCodeSource().getLocation(), - Launcher.class.getProtectionDomain().getCodeSource().getLocation(), - installerJar.toUri().toURL() - }, ModuleUtil.getPlatformClassLoader())) { - Class installer = ucl.loadClass("io.github.zekerzhayard.forgewrapper.installer.Installer"); - if (!(boolean) installer.getMethod("install", File.class, File.class, File.class).invoke(null, detector.getLibraryDir().toFile(), minecraftJar.toFile(), installerJar.toFile())) { - return; - } + if (!((boolean) installer.getMethod("install", File.class, File.class, File.class).invoke(null, detector.getLibraryDir().toFile(), minecraftJar.toFile(), installerJar.toFile()))) { + return; } + + ModuleUtil.setupClassPath(detector.getLibraryDir(), (List) data.get("extraLibraries")); + Class mainClass = ModuleUtil.setupBootstrapLauncher(Class.forName((String) data.get("mainClass"))); + mainClass.getMethod("main", String[].class).invoke(null, new Object[] {args}); } + } - ModuleUtil.setupClassPath(detector.getLibraryDir(), detector.getExtraLibraries(forgeGroup, forgeArtifact, forgeFullVersion)); - Class mainClass = ModuleUtil.setupBootstrapLauncher(Class.forName(detector.getMainClass(forgeGroup, forgeArtifact, forgeFullVersion))); - mainClass.getMethod("main", String[].class).invoke(null, new Object[] { args }); + private static boolean isFile(Path path) { + return path != null && Files.isRegularFile(path); } } diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java index 67d3e1d..29b7179 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java @@ -1,33 +1,10 @@ package io.github.zekerzhayard.forgewrapper.installer.detector; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.math.BigInteger; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import cpw.mods.modlauncher.Launcher; public interface IFileDetector { @@ -85,163 +62,4 @@ default Path getMinecraftJar(String mcVersion) { } return null; } - - /** - * @param forgeGroup Forge package group (e.g. net.minecraftforge). - * @param forgeArtifact Forge package artifact (e.g. forge). - * @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0). - * @return The list of jvm args. - */ - default List getJvmArgs(String forgeGroup, String forgeArtifact, String forgeFullVersion) { - return this.getDataFromInstaller(forgeGroup, forgeArtifact, forgeFullVersion, "version.json", e -> { - JsonElement element = e.getAsJsonObject().get("arguments").getAsJsonObject().get("jvm"); - List args = new ArrayList<>(); - if (!element.equals(JsonNull.INSTANCE)) { - element.getAsJsonArray().iterator().forEachRemaining(je -> args.add(je.getAsString())); - } - return args; - }); - } - - default List getExtraLibraries(String forgeGroup, String forgeArtifact, String forgeFullVersion) { - return this.getDataFromInstaller(forgeGroup, forgeArtifact, forgeFullVersion, "version.json", e -> { - List paths = new ArrayList<>(); - e.getAsJsonObject().getAsJsonArray("libraries").iterator().forEachRemaining(je -> { - JsonObject artifact = je.getAsJsonObject().get("downloads").getAsJsonObject().get("artifact").getAsJsonObject(); - if (artifact.get("url").getAsString().isEmpty()) { - paths.add(artifact.get("path").getAsString()); - } - }); - return paths; - }); - } - - /** - * @param forgeGroup Forge package group (e.g. net.minecraftforge). - * @param forgeArtifact Forge package artifact (e.g. forge). - * @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0). - * @return The main class. - */ - default String getMainClass(String forgeGroup, String forgeArtifact, String forgeFullVersion) { - return this.getDataFromInstaller(forgeGroup, forgeArtifact, forgeFullVersion, "version.json", e -> e.getAsJsonObject().getAsJsonPrimitive("mainClass").getAsString()); - } - - /** - * @param forgeGroup Forge package group (e.g. net.minecraftforge). - * @param forgeArtifact Forge package artifact (e.g. forge). - * @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0). - * @return The json object in the-installer-jar-->install_profile.json-->data-->xxx-->client. - */ - default JsonObject getInstallProfileExtraData(String forgeGroup, String forgeArtifact, String forgeFullVersion) { - return this.getDataFromInstaller(forgeGroup, forgeArtifact, forgeFullVersion, "install_profile.json", e -> e.getAsJsonObject().getAsJsonObject("data")); - } - - @SuppressWarnings("deprecation") - default R getDataFromInstaller(String forgeGroup, String forgeArtifact, String forgeFullVersion, String entry, Function function) { - Path installer = this.getInstallerJar(forgeGroup, forgeArtifact, forgeFullVersion); - if (isFile(installer)) { - try (ZipFile zf = new ZipFile(installer.toFile())) { - ZipEntry ze = zf.getEntry(entry); - if (ze != null) { - try ( - InputStream is = zf.getInputStream(ze); - InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8) - ) { - return function.apply(new JsonParser().parse(isr)); - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } else { - throw new RuntimeException("Unable to detect the forge installer!"); - } - return null; - } - - /** - * Check all cached files. - * @param forgeGroup Forge package group (e.g. net.minecraftforge). - * @param forgeArtifact Forge package artifact (e.g. forge). - * @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0). - * @return True represents all files are ready. - */ - default boolean checkExtraFiles(String forgeGroup, String forgeArtifact, String forgeFullVersion) { - JsonObject jo = this.getInstallProfileExtraData(forgeGroup, forgeArtifact, forgeFullVersion); - if (jo != null) { - Map libsMap = new HashMap<>(); - Map hashMap = new HashMap<>(); - - // Get all "data//client" elements. - Pattern artifactPattern = Pattern.compile("^\\[(?[^:]*):(?[^:]*):(?[^:@]*)(:(?[^@]*))?(@(?[^]]*))?]$"); - for (Map.Entry entry : jo.entrySet()) { - String clientStr = entry.getValue().getAsJsonObject().get("client").getAsString(); - if (entry.getKey().endsWith("_SHA")) { - Pattern p = Pattern.compile("^'(?[A-Za-z0-9]{40})'$"); - Matcher m = p.matcher(clientStr); - if (m.find()) { - hashMap.put(entry.getKey(), m.group("sha1")); - } - } else { - Matcher m = artifactPattern.matcher(clientStr); - if (m.find()) { - String groupId = nullToDefault(m.group("groupId"), ""); - String artifactId = nullToDefault(m.group("artifactId"), ""); - String version = nullToDefault(m.group("version"), ""); - String classifier = nullToDefault(m.group("classifier"), ""); - String type = nullToDefault(m.group("type"), "jar"); - libsMap.put(entry.getKey(), this.getLibraryDir() - .resolve(groupId.replace('.', File.separatorChar)) - .resolve(artifactId) - .resolve(version) - .resolve(artifactId + "-" + version + (classifier.isEmpty() ? "" : "-") + classifier + "." + type).toAbsolutePath()); - } - } - } - - // Check all cached libraries. - boolean checked = true; - for (Map.Entry entry : libsMap.entrySet()) { - checked = checkExtraFile(entry.getValue(), hashMap.get(entry.getKey() + "_SHA")); - if (!checked) { - System.out.println("Missing: " + entry.getValue()); - break; - } - } - return checked; - } - // Skip installing process if installer profile doesn't exist. - return true; - } - - /** - * Check the exact file. - * @param path The path of the file to check. - * @param sha1 The sha1 defined in installer. - * @return True represents the file is ready. - */ - static boolean checkExtraFile(Path path, String sha1) { - return sha1 == null || sha1.isEmpty() || (isFile(path) && sha1.toLowerCase(Locale.ENGLISH).equals(getFileSHA1(path))); - } - - static boolean isFile(Path path) { - return path != null && Files.isRegularFile(path); - } - - static String getFileSHA1(Path path) { - try { - StringBuilder sha1 = new StringBuilder(new BigInteger(1, MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(path))).toString(16)); - while (sha1.length() < 40) { - sha1.insert(0, "0"); - } - return sha1.toString().toLowerCase(Locale.ENGLISH); - } catch (IOException | NoSuchAlgorithmException e) { - e.printStackTrace(); - } - return null; - } - - static String nullToDefault(String string, String defaultValue) { - return string == null ? defaultValue : string; - } } diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV1.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV1.java deleted file mode 100644 index 014a624..0000000 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV1.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.github.zekerzhayard.forgewrapper.installer.util; - -import java.io.File; -import java.util.function.Predicate; - -import net.minecraftforge.installer.actions.ClientInstall; -import net.minecraftforge.installer.actions.ProgressCallback; -import net.minecraftforge.installer.json.Install; -import net.minecraftforge.installer.json.InstallV1; -import net.minecraftforge.installer.json.Util; - -public class InstallerV1 extends AbstractInstaller { - @Override - public Install loadInstallProfile() { - return Util.loadInstallProfile(); - } - - @Override - public boolean runClientInstall(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar, File installerJar) { - return new ClientInstall4MultiMC(profile, monitor, libraryDir, minecraftJar).run(null, input -> true, installerJar); - } - - public static class ClientInstall4MultiMC extends ClientInstall { - protected File libraryDir; - protected File minecraftJar; - - public ClientInstall4MultiMC(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar) { - super(profile instanceof InstallV1 ? (InstallV1) profile : new InstallV1(profile), monitor); - this.libraryDir = libraryDir; - this.minecraftJar = minecraftJar; - } - - @Override - public boolean run(File target, Predicate optionals, File installer) { - return this.processors.process(this.libraryDir, this.minecraftJar, this.libraryDir.getParentFile(), installer); - } - } -} From 4f57cae5ef6ffa9aa198be06ac843df9ca1d6261 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 8 Sep 2024 21:28:01 +0100 Subject: [PATCH 2/2] Revert "Merge pull request #1 from TheKodeToad/fix-new-forge-installer" This reverts commit c2a65dd98f5e405bc7997b1fee62c89a0414fa67, reversing changes made to 271bf51f809de6f2f47a1129b86df3a1594e1d6b. --- build.gradle | 2 +- .../installer/util/InstallerV1.java | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 9c3464b..aa71402 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ repositories { dependencies { compileOnly "com.google.code.gson:gson:2.8.7" compileOnly "cpw.mods:modlauncher:8.0.9" - compileOnly "net.minecraftforge:installer:2.2.7" + compileOnly "net.minecraftforge:installer:2.1.9" compileOnly "net.sf.jopt-simple:jopt-simple:5.0.4" provided project(":common") diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV1.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV1.java index c9ceeaf..014a624 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV1.java +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV1.java @@ -1,9 +1,9 @@ package io.github.zekerzhayard.forgewrapper.installer.util; import java.io.File; -import java.lang.reflect.Method; +import java.util.function.Predicate; -import net.minecraftforge.installer.actions.PostProcessors; +import net.minecraftforge.installer.actions.ClientInstall; import net.minecraftforge.installer.actions.ProgressCallback; import net.minecraftforge.installer.json.Install; import net.minecraftforge.installer.json.InstallV1; @@ -16,22 +16,23 @@ public Install loadInstallProfile() { } @Override - public boolean runClientInstall(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar, - File installerJar) { - PostProcessors processors = new PostProcessors( - profile instanceof InstallV1 ? (InstallV1) profile : new InstallV1(profile), true, monitor); + public boolean runClientInstall(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar, File installerJar) { + return new ClientInstall4MultiMC(profile, monitor, libraryDir, minecraftJar).run(null, input -> true, installerJar); + } - try { - Method method = processors.getClass().getMethod("process", File.class, File.class, File.class, File.class); - Object result = method.invoke(processors, libraryDir, minecraftJar, libraryDir.getParentFile(), - installerJar); + public static class ClientInstall4MultiMC extends ClientInstall { + protected File libraryDir; + protected File minecraftJar; - if (method.getReturnType() == boolean.class) - return (boolean) result; + public ClientInstall4MultiMC(Install profile, ProgressCallback monitor, File libraryDir, File minecraftJar) { + super(profile instanceof InstallV1 ? (InstallV1) profile : new InstallV1(profile), monitor); + this.libraryDir = libraryDir; + this.minecraftJar = minecraftJar; + } - return result != null; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); + @Override + public boolean run(File target, Predicate optionals, File installer) { + return this.processors.process(this.libraryDir, this.minecraftJar, this.libraryDir.getParentFile(), installer); } } }