diff options
| author | TheKodeToad <TheKodeToad@proton.me> | 2024-09-08 21:46:07 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-08 21:46:07 +0100 |
| commit | c6a7abbcaf424451b5e0b3b0ddd69b0d53442e0b (patch) | |
| tree | 75b499678dec1d5503009e396e51fa7776ed473a | |
| parent | 4eec3c90f41c74384957b7d3131f80065e6863ce (diff) | |
| parent | 9d6db3960600489a0ed3e7fda9082ae7b1393f16 (diff) | |
| download | Project-Tick-c6a7abbcaf424451b5e0b3b0ddd69b0d53442e0b.tar.gz Project-Tick-c6a7abbcaf424451b5e0b3b0ddd69b0d53442e0b.zip | |
Merge pull request #2 from TheKodeToad/merge-upstream
Merge upstream
14 files changed, 245 insertions, 446 deletions
diff --git a/build.gradle b/build.gradle index 9c3464b364..244e0555ae 100644 --- a/build.gradle +++ b/build.gradle @@ -16,9 +16,6 @@ group = "io.github.zekerzhayard" archivesBaseName = rootProject.name configurations { - provided { - implementation.extendsFrom provided - } multirelase { implementation.extendsFrom multirelase } @@ -38,8 +35,6 @@ dependencies { 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") } @@ -61,10 +56,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 abd2997bba..0000000000 --- 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 c36b8b33a7..0000000000 --- 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 <T> Consumer<T> wrapConsumer(CheckedConsumer<T> consumer) { - return consumer; - } - - public static <T, U> BiConsumer<T, U> wrapBiConsumer(CheckedBiConsumer<T, U> biconsumer) { - return biconsumer; - } - - public interface CheckedConsumer<T> extends Consumer<T> { - 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<T, U> extends BiConsumer<T, U> { - 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/jigsaw/build.gradle b/jigsaw/build.gradle index 08c394afee..a555ec3bf3 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 e15d39c410..09792b1624 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.lang.reflect.Field; 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.Map; 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 class ModuleUtil { 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<String> 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<ResolvedModule, Set<ResolvedModule>> graphMap = new HashMap<>((Map<ResolvedModule, Set<ResolvedModule>>) 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<ResolvedModule, Set<ResolvedModule>> entry : graphMap.entrySet()) { + cfSetter.invokeWithArguments(entry.getKey(), ModuleLayer.boot().configuration()); + for (ResolvedModule resolvedModule : entry.getValue()) { + cfSetter.invokeWithArguments(resolvedModule, ModuleLayer.boot().configuration()); + } + } graphMap.putAll((Map<ResolvedModule, Set<ResolvedModule>>) 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 class ModuleUtil { // 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<String> exports) { @@ -124,13 +143,26 @@ public class ModuleUtil { } void implAdd(List<String> 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 4778335e0d..0000000000 --- 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 6b2d55072f..0000000000 --- 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 ff1cd4373b..0000000000 --- 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<String> optionals) { - return this.processors.process(this.libraryDir, this.minecraftJar); - } - } -} diff --git a/settings.gradle b/settings.gradle index 34c563d573..3444232309 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 3604371a14..90d3325570 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 java.util.List; import io.github.zekerzhayard.forgewrapper.installer.util.ModuleUtil; public class Bootstrap { - public static void bootstrap(List<String> jvmArgs, String minecraftJar, String libraryDir) throws Throwable { + public static void bootstrap(String[] jvmArgs, String minecraftJar, String libraryDir) throws Throwable { // Replace all placeholders - List<String> 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 class Bootstrap { String modulePath = null; List<String> addExports = new ArrayList<>(); List<String> 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 278688e773..10895b13fd 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<String, Object> getData(File librariesDir) { + Map<String, Object> 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 class 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<String> getExtraLibraries(Version0 version) { + List<String> 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<String, List<Processor>> processors = new HashMap<>(); + protected File librariesDir; + + public InstallV1Wrapper(InstallV1 v1, File librariesDir) { + super(v1); + this.serverJarPath = v1.getServerJarPath(); + this.librariesDir = librariesDir; + } + + @Override + public List<Processor> getProcessors(String side) { + List<Processor> 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<Processor> processors, Map<String, String> data, File base) { + Map<String, File> artifactData = new HashMap<>(); + for (Map.Entry<String, String> 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<Processor, Map<String, String>> outputsMap = new HashMap<>(); + label: + for (Processor processor : processors) { + Map<String, String> outputs = new HashMap<>(); + if (processor.getOutputs().isEmpty()) { + String[] args = processor.getArgs(); + for (int i = 0; i < args.length; i++) { + for (Map.Entry<String, File> 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<Processor, Map<String, String>> entry : outputsMap.entrySet()) { + setOutputs(entry.getKey(), entry.getValue()); + } + } + + private static Field outputsField; + private static void setOutputs(Processor processor, Map<String, String> 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 37876a6de4..f15a2a842c 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 @@ package io.github.zekerzhayard.forgewrapper.installer; 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.detector.IFileDetector; 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 class Main { 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<String, Object> data = (Map<String, Object>) 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<String>) 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 d50daaf69e..4f1b7c3558 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,183 +62,4 @@ public interface IFileDetector { } 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<String> 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<String> args = new ArrayList<>(); - if (!element.equals(JsonNull.INSTANCE)) { - element.getAsJsonArray().iterator().forEachRemaining(je -> args.add(je.getAsString())); - } - return args; - }); - } - - default List<String> getExtraLibraries(String forgeGroup, String forgeArtifact, String forgeFullVersion) { - return this.getDataFromInstaller(forgeGroup, forgeArtifact, forgeFullVersion, "version.json", e -> { - List<String> 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> R getDataFromInstaller(String forgeGroup, String forgeArtifact, String forgeFullVersion, String entry, Function<JsonElement, R> 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<String, Path> libsMap = new HashMap<>(); - Map<String, String> hashMap = new HashMap<>(); - - // Get all "data/<name>/client" elements. - Pattern artifactPattern = Pattern.compile("^\\[(?<groupId>[^:]*):(?<artifactId>[^:]*):(?<version>[^:@]*)(:(?<classifier>[^@]*))?(@(?<type>[^]]*))?]$"); - for (Map.Entry<String, JsonElement> entry : jo.entrySet()) { - String clientStr = entry.getValue().getAsJsonObject().get("client").getAsString(); - if (entry.getKey().endsWith("_SHA")) { - Pattern p = Pattern.compile("^'(?<sha1>[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<String, Path> entry : libsMap.entrySet()) { - String sha1 = ""; - String entryKey = entry.getKey(); - // NOTE: only used on servers, it's busted - if(entryKey.equals("MC_UNPACKED")) { - continue; - } - /** - * NOTE: workaround for https://github.com/MultiMC/Launcher/issues/4400 - * We ignore the hash of the client file and instead just rely on it being 'correct, maybe' if it's present at all - */ - System.out.println("Checking: " + entryKey); - if(!entryKey.equals("PATCHED")) { - sha1 = hashMap.get(entryKey + "_SHA"); - } - checked = checkExtraFile(entry.getValue(), sha1); - 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) { - if (!isFile(path)) { - return false; - } - if(sha1 == null || sha1.isEmpty()) { - return true; - } - return 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; - } -} +}
\ No newline at end of file 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 c9ceeaf45c..0000000000 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/InstallerV1.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.zekerzhayard.forgewrapper.installer.util; - -import java.io.File; -import java.lang.reflect.Method; - -import net.minecraftforge.installer.actions.PostProcessors; -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) { - PostProcessors processors = new PostProcessors( - profile instanceof InstallV1 ? (InstallV1) profile : new InstallV1(profile), true, monitor); - - 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); - - if (method.getReturnType() == boolean.class) - return (boolean) result; - - return result != null; - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } -} |
