summaryrefslogtreecommitdiff
path: root/forgewrapper/src
diff options
context:
space:
mode:
authorMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:42:14 +0300
committerMehmet Samet Duman <yongdohyun@projecttick.org>2026-04-02 18:42:14 +0300
commit9affe6b2ee9d4bd1c474d1d7c32ea2cc81ce5cee (patch)
tree0dcfc70783bc623b016d05515aee309e0ca5c2d3 /forgewrapper/src
parent3d2121f5d6555744ce5aa502088fc2b34dc26d38 (diff)
parent959f7668509a90d524438903ad8bda55d9e24e9d (diff)
downloadProject-Tick-9affe6b2ee9d4bd1c474d1d7c32ea2cc81ce5cee.tar.gz
Project-Tick-9affe6b2ee9d4bd1c474d1d7c32ea2cc81ce5cee.zip
Add 'forgewrapper/' from commit '959f7668509a90d524438903ad8bda55d9e24e9d'
git-subtree-dir: forgewrapper git-subtree-mainline: 3d2121f5d6555744ce5aa502088fc2b34dc26d38 git-subtree-split: 959f7668509a90d524438903ad8bda55d9e24e9d
Diffstat (limited to 'forgewrapper/src')
-rw-r--r--forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java75
-rw-r--r--forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java182
-rw-r--r--forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java74
-rw-r--r--forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/DetectorLoader.java35
-rw-r--r--forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java99
-rw-r--r--forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/MultiMCFileDetector.java52
-rw-r--r--forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java40
-rw-r--r--forgewrapper/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector1
8 files changed, 558 insertions, 0 deletions
diff --git a/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java
new file mode 100644
index 0000000000..88c3a00742
--- /dev/null
+++ b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java
@@ -0,0 +1,75 @@
+package io.github.zekerzhayard.forgewrapper.installer;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.github.zekerzhayard.forgewrapper.installer.util.ModuleUtil;
+
+public class Bootstrap {
+ public static void bootstrap(String[] jvmArgs, String minecraftJar, String libraryDir) throws Throwable {
+ // Replace all placeholders
+ 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;
+
+ // Remove NewLaunch.jar from property to prevent Forge from adding it to the module path
+ StringBuilder newCP = new StringBuilder();
+ for (String path : System.getProperty("java.class.path").split(File.pathSeparator)) {
+ if (!path.endsWith("NewLaunch.jar")) {
+ newCP.append(path).append(File.pathSeparator);
+ }
+ }
+ System.setProperty("java.class.path", newCP.substring(0, newCP.length() - 1));
+
+ String modulePath = null;
+ List<String> addExports = new ArrayList<>();
+ List<String> addOpens = new ArrayList<>();
+ for (int i = 0; i < jvmArgs.length; i++) {
+ String arg = jvmArgs[i];
+
+ if (arg.equals("-p") || arg.equals("--module-path")) {
+ modulePath = jvmArgs[i + 1];
+ } else if (arg.startsWith("--module-path=")) {
+ modulePath = arg.split("=", 2)[1];
+ }
+
+ if (arg.equals("--add-exports")) {
+ 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[i + 1]);
+ } else if (arg.startsWith("--add-opens=")) {
+ addOpens.add(arg.split("=", 2)[1]);
+ }
+
+ // Java properties
+ if (arg.startsWith("-D")) {
+ String[] prop = arg.substring(2).split("=", 2);
+
+ if (prop[0].equals("ignoreList")) {
+ // The default ignoreList may cause some problems, so we define it more precisely.
+ System.setProperty(prop[0], prop[1] + ",NewLaunch.jar,ForgeWrapper-," + minecraftJar);
+ } else {
+ System.setProperty(prop[0], prop[1]);
+ }
+ }
+ }
+
+ if (System.getProperty("libraryDirectory") == null) {
+ System.setProperty("libraryDirectory", libraryDir);
+ }
+
+ if (modulePath != null) {
+ ModuleUtil.addModules(modulePath);
+ }
+ ModuleUtil.addExports(addExports);
+ ModuleUtil.addOpens(addOpens);
+ }
+}
diff --git a/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java
new file mode 100644
index 0000000000..98b9d1afa3
--- /dev/null
+++ b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Installer.java
@@ -0,0 +1,182 @@
+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 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 {
+ 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);
+ if (System.getProperty("java.net.preferIPv4Stack") == null) {
+ System.setProperty("java.net.preferIPv4Stack", "true");
+ }
+ String vendor = System.getProperty("java.vendor", "missing vendor");
+ String javaVersion = System.getProperty("java.version", "missing java version");
+ 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"));
+ 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);
+ }
+ // It can also be defined by JVM argument "-Dforgewrapper.skipHashCheck=true".
+ if (Boolean.getBoolean("forgewrapper.skipHashCheck")){
+ processor.forEach(proc -> proc.getOutputs().clear());
+ }
+ 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);
+ }
+ }
+ }
+
+ 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/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java
new file mode 100644
index 0000000000..2c0f4cfc29
--- /dev/null
+++ b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java
@@ -0,0 +1,74 @@
+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;
+
+import io.github.zekerzhayard.forgewrapper.installer.detector.DetectorLoader;
+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
+
+ List<String> argsList = Stream.of(args).collect(Collectors.toList());
+ // NOTE: this is only true for NeoForge versions past 20.2.x
+ // early versions of NeoForge (for 1.20.1) are not supposed to be covered here
+ boolean isNeoForge = argsList.contains("--fml.neoForgeVersion");
+
+ String mcVersion = argsList.get(argsList.indexOf("--fml.mcVersion") + 1);
+ String forgeGroup = argsList.contains("--fml.forgeGroup") ? argsList.get(argsList.indexOf("--fml.forgeGroup") + 1) : "net.neoforged";
+ String forgeArtifact = isNeoForge ? "neoforge" : "forge";
+ String forgeVersionKey = isNeoForge ? "--fml.neoForgeVersion" : "--fml.forgeVersion";
+ String forgeVersion = argsList.get(argsList.indexOf(forgeVersionKey) + 1);
+ String forgeFullVersion = isNeoForge ? forgeVersion : mcVersion + "-" + forgeVersion;
+
+ IFileDetector detector = DetectorLoader.loadDetector();
+ // Check installer jar.
+ Path installerJar = detector.getInstallerJar(forgeGroup, forgeArtifact, forgeFullVersion);
+ if (!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(),
+ installerJar.toUri().toURL()
+ }, ModuleUtil.getPlatformClassLoader())) {
+ Class<?> installer = ucl.loadClass("io.github.zekerzhayard.forgewrapper.installer.Installer");
+
+ 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();
+ }
+
+ 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});
+ }
+ }
+
+ private static boolean isFile(Path path) {
+ return path != null && Files.isRegularFile(path);
+ }
+}
diff --git a/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/DetectorLoader.java b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/DetectorLoader.java
new file mode 100644
index 0000000000..369c4b555d
--- /dev/null
+++ b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/DetectorLoader.java
@@ -0,0 +1,35 @@
+package io.github.zekerzhayard.forgewrapper.installer.detector;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+public class DetectorLoader {
+ public static IFileDetector loadDetector() {
+ ServiceLoader<IFileDetector> sl = ServiceLoader.load(IFileDetector.class);
+ HashMap<String, IFileDetector> detectors = new HashMap<>();
+ for (IFileDetector detector : sl) {
+ detectors.put(detector.name(), detector);
+ }
+
+ boolean enabled = false;
+ IFileDetector temp = null;
+ for (Map.Entry<String, IFileDetector> detector : detectors.entrySet()) {
+ HashMap<String, IFileDetector> others = new HashMap<>(detectors);
+ others.remove(detector.getKey());
+ if (!enabled) {
+ enabled = detector.getValue().enabled(others);
+ if (enabled) {
+ temp = detector.getValue();
+ }
+ } else if (detector.getValue().enabled(others)) {
+ throw new RuntimeException("There are two or more file detectors are enabled! (" + temp.toString() + ", " + detector.toString() + ")");
+ }
+ }
+
+ if (temp == null) {
+ throw new RuntimeException("No file detector is enabled!");
+ }
+ return temp;
+ }
+}
diff --git a/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java
new file mode 100644
index 0000000000..6f695ff884
--- /dev/null
+++ b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/IFileDetector.java
@@ -0,0 +1,99 @@
+package io.github.zekerzhayard.forgewrapper.installer.detector;
+
+import java.net.URL;
+import java.net.URISyntaxException;
+import java.net.MalformedURLException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+public interface IFileDetector {
+ /**
+ * @return The name of the detector.
+ */
+ String name();
+
+ /**
+ * If there are two or more detectors are enabled, an exception will be thrown. Removing anything from the map is in vain.
+ * @param others Other detectors.
+ * @return True represents enabled.
+ */
+ boolean enabled(HashMap<String, IFileDetector> others);
+
+ /**
+ * @return The ".minecraft/libraries" folder for normal. It can also be defined by JVM argument "-Dforgewrapper.librariesDir=&lt;libraries-path&gt;".
+ */
+ default Path getLibraryDir() {
+ String libraryDir = System.getProperty("forgewrapper.librariesDir");
+ if (libraryDir != null) {
+ return Paths.get(libraryDir).toAbsolutePath();
+ }
+ try {
+ URL launcherLocation = null;
+ String[] classNames = {
+ "cpw/mods/modlauncher/Launcher.class",
+ "net/neoforged/fml/loading/FMLLoader.class"
+ };
+
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ for (String classResource : classNames) {
+ URL url = cl.getResource(classResource);
+ if (url != null) {
+ String path = url.toString();
+ if (path.startsWith("jar:") && path.contains("!")) {
+ path = path.substring(4, path.indexOf('!'));
+ try {
+ launcherLocation = new URL(path);
+ break;
+ } catch (MalformedURLException e) {
+ // ignore and try next
+ }
+ }
+ }
+ }
+
+ if (launcherLocation == null) {
+ throw new UnsupportedOperationException("Could not detect the libraries folder - it can be manually specified with `-Dforgewrapper.librariesDir=` (Java runtime argument)");
+ }
+ Path launcher = Paths.get(launcherLocation.toURI());
+
+ while (!launcher.getFileName().toString().equals("libraries")) {
+ launcher = launcher.getParent();
+
+ if (launcher == null || launcher.getFileName() == null) {
+ throw new UnsupportedOperationException("Could not detect the libraries folder - it can be manually specified with `-Dforgewrapper.librariesDir=` (Java runtime argument)");
+ }
+ }
+
+ return launcher.toAbsolutePath();
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @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 forge installer jar path. It can also be defined by JVM argument "-Dforgewrapper.installer=&lt;installer-path&gt;".
+ */
+ default Path getInstallerJar(String forgeGroup, String forgeArtifact, String forgeFullVersion) {
+ String installer = System.getProperty("forgewrapper.installer");
+ if (installer != null) {
+ return Paths.get(installer).toAbsolutePath();
+ }
+ return null;
+ }
+
+ /**
+ * @param mcVersion Minecraft version (e.g. 1.14.4).
+ * @return The minecraft client jar path. It can also be defined by JVM argument "-Dforgewrapper.minecraft=&lt;minecraft-path&gt;".
+ */
+ default Path getMinecraftJar(String mcVersion) {
+ String minecraft = System.getProperty("forgewrapper.minecraft");
+ if (minecraft != null) {
+ return Paths.get(minecraft).toAbsolutePath();
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/MultiMCFileDetector.java b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/MultiMCFileDetector.java
new file mode 100644
index 0000000000..a1216f0947
--- /dev/null
+++ b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/detector/MultiMCFileDetector.java
@@ -0,0 +1,52 @@
+package io.github.zekerzhayard.forgewrapper.installer.detector;
+
+import java.nio.file.Path;
+import java.util.HashMap;
+
+public class MultiMCFileDetector implements IFileDetector {
+ protected Path libraryDir = null;
+ protected Path installerJar = null;
+ protected Path minecraftJar = null;
+
+ @Override
+ public String name() {
+ return "MultiMC";
+ }
+
+ @Override
+ public boolean enabled(HashMap<String, IFileDetector> others) {
+ return others.size() == 0;
+ }
+
+ @Override
+ public Path getLibraryDir() {
+ if (this.libraryDir == null) {
+ this.libraryDir = IFileDetector.super.getLibraryDir();
+ }
+ return this.libraryDir;
+ }
+
+ @Override
+ public Path getInstallerJar(String forgeGroup, String forgeArtifact, String forgeFullVersion) {
+ Path path = IFileDetector.super.getInstallerJar(forgeGroup, forgeArtifact, forgeFullVersion);
+ if (path == null) {
+ if (this.installerJar == null) {
+ Path installerBase = this.getLibraryDir();
+ for (String dir : forgeGroup.split("\\."))
+ installerBase = installerBase.resolve(dir);
+ this.installerJar = installerBase.resolve(forgeArtifact).resolve(forgeFullVersion).resolve(forgeArtifact + "-" + forgeFullVersion + "-installer.jar").toAbsolutePath();
+ }
+ return this.installerJar;
+ }
+ return path;
+ }
+
+ @Override
+ public Path getMinecraftJar(String mcVersion) {
+ Path path = IFileDetector.super.getMinecraftJar(mcVersion);
+ if (path == null) {
+ return this.minecraftJar != null ? this.minecraftJar : (this.minecraftJar = this.getLibraryDir().resolve("com").resolve("mojang").resolve("minecraft").resolve(mcVersion).resolve("minecraft-" + mcVersion + "-client.jar").toAbsolutePath());
+ }
+ return path;
+ }
+}
diff --git a/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java
new file mode 100644
index 0000000000..9eee574cf3
--- /dev/null
+++ b/forgewrapper/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java
@@ -0,0 +1,40 @@
+package io.github.zekerzhayard.forgewrapper.installer.util;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Path;
+import java.util.List;
+
+public class ModuleUtil {
+ public static void addModules(String modulePath) {
+ // nothing to do with Java 8
+ }
+
+ public static void addExports(List<String> exports) {
+ // nothing to do with Java 8
+ }
+
+ public static void addOpens(List<String> opens) {
+ // nothing to do with Java 8
+ }
+
+ public static void setupClassPath(Path libraryDir, List<String> paths) throws Throwable {
+ Method addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+ addURLMethod.setAccessible(true);
+ for (String path : paths) {
+ addURLMethod.invoke(ClassLoader.getSystemClassLoader(), libraryDir.resolve(path).toUri().toURL());
+ }
+ }
+
+ public static Class<?> setupBootstrapLauncher(Class<?> mainClass) {
+ // nothing to do with Java 8
+ return mainClass;
+ }
+
+ public static ClassLoader getPlatformClassLoader() {
+ // PlatformClassLoader does not exist in Java 8
+ return null;
+ }
+}
diff --git a/forgewrapper/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector b/forgewrapper/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector
new file mode 100644
index 0000000000..31f2c4e711
--- /dev/null
+++ b/forgewrapper/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector
@@ -0,0 +1 @@
+io.github.zekerzhayard.forgewrapper.installer.detector.MultiMCFileDetector