From 5fb3f01768c8a9bb1587ea0884e6d01127dbc4ac Mon Sep 17 00:00:00 2001 From: ZekerZhayard Date: Fri, 23 Jul 2021 01:47:28 +0800 Subject: [PATCH] Full support the installation of forge-1.16.5-36.1.66+ and partial support forge-1.17.1 --- .github/workflows/build.yml | 6 +- .github/workflows/maven.bat | 13 -- .github/workflows/publication.yml | 13 +- .gitignore | 2 +- README.md | 4 + build.gradle | 38 ++-- converter/build.gradle | 48 +++++ .../forgewrapper/converter/Converter.java | 6 +- .../forgewrapper/converter/Main.java | 0 .../src}/main/resources/mmc-pack.json | 2 +- .../resources/patches/net.minecraftforge.json | 2 +- gradle.properties | 2 +- jigsaw/build.gradle | 28 +++ .../installer/util/ModuleUtil.java | 195 ++++++++++++++++++ legacy/build.gradle | 2 +- settings.gradle | 4 +- .../forgewrapper/installer/Bootstrap.java | 78 +++++++ .../forgewrapper/installer/Main.java | 6 +- .../forgewrapper/installer/MainV2.java | 15 -- .../installer/detector/IFileDetector.java | 52 ++++- .../installer/util/ModuleUtil.java | 21 ++ ...gewrapper.installer.detector.IFileDetector | 2 +- .../services/java.util.function.Consumer | 1 - 23 files changed, 458 insertions(+), 82 deletions(-) delete mode 100644 .github/workflows/maven.bat create mode 100644 converter/build.gradle rename {src => converter/src}/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java (96%) rename {src => converter/src}/main/java/io/github/zekerzhayard/forgewrapper/converter/Main.java (100%) rename {src => converter/src}/main/resources/mmc-pack.json (98%) rename {src => converter/src}/main/resources/patches/net.minecraftforge.json (99%) create mode 100644 jigsaw/build.gradle create mode 100644 jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java create mode 100644 src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java delete mode 100644 src/main/java/io/github/zekerzhayard/forgewrapper/installer/MainV2.java create mode 100644 src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java delete mode 100644 src/main/resources/META-INF/services/java.util.function.Consumer diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d331b5..9e2ae64 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: windows-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -18,7 +18,9 @@ jobs: java-version: '8.0.302' architecture: x64 - name: Build with Gradle - run: ./gradlew.bat build -iS + run: | + chmod +x ./gradlew + ./gradlew build -iS - uses: actions/upload-artifact@v2 with: name: Package diff --git a/.github/workflows/maven.bat b/.github/workflows/maven.bat deleted file mode 100644 index 0d3f914..0000000 --- a/.github/workflows/maven.bat +++ /dev/null @@ -1,13 +0,0 @@ -@ECHO OFF - -git clone -b maven https://github.com/ZekerZhayard/ForgeWrapper.git .\maven - -xcopy ".\build\maven\" ".\maven\" /S /Y -cd ".\maven\" - -git config --local user.name "GitHub Actions" -git config --local user.email "actions@github.com" - -git add . -git commit -m "%GITHUB_SHA%" -git push https://${GITHUB_TOKEN}@github.com/ZekerZhayard/ForgeWrapper.git maven diff --git a/.github/workflows/publication.yml b/.github/workflows/publication.yml index 0f6f0eb..15a9161 100644 --- a/.github/workflows/publication.yml +++ b/.github/workflows/publication.yml @@ -7,7 +7,7 @@ on: jobs: build: - runs-on: windows-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -19,7 +19,9 @@ jobs: - name: Build with Gradle env: IS_PUBLICATION: true - run: ./gradlew.bat publish -iS + run: | + chmod +x ./gradlew + ./gradlew publish -iS - uses: actions/upload-artifact@v2 with: name: Package @@ -49,6 +51,7 @@ jobs: with: asset_paths: '["./build/libs/*"]' - name: Upload to Maven - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: ./.github/workflows/maven.bat + uses: JamesIves/github-pages-deploy-action@4.1.4 + with: + branch: maven + folder: build/maven diff --git a/.gitignore b/.gitignore index 007587d..2bcd13e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ bin *.iml *.ipr *.iws -out \ No newline at end of file +out diff --git a/README.md b/README.md index 21e2b96..d05b57d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ Allow [MultiMC](https://github.com/MultiMC/MultiMC5) to launch Minecraft 1.13+ w 1. Download Forge installer for Minecraft 1.13+ [here](https://files.minecraftforge.net/). 2. Download ForgeWrapper jar file at the [release](https://github.com/ZekerZhayard/ForgeWrapper/releases) page. +3. Since ForgeWrapper 1.5.1, it no longer includes the json converter, so you need to build it by yourself: + - [Download](https://github.com/ZekerZhayard/ForgeWrapper/archive/refs/heads/master.zip) ForgeWrapper sources. + - Extract the zip and open terminal in the extracted folder. + - Run `./gradlew build` command in terminal and get the jar from `./converter/build/libs` 3. Run the below command in terminal: ``` java -jar --installer= [--instance=] diff --git a/build.gradle b/build.gradle index 36c6406..d975a08 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,9 @@ plugins { } sourceCompatibility = targetCompatibility = 1.8 +compileJava { + sourceCompatibility = targetCompatibility = 1.8 +} version = "${fw_version}${-> getVersionSuffix()}" group = "io.github.zekerzhayard" @@ -15,6 +18,9 @@ configurations { provided { compileOnly.extendsFrom provided } + multirelase { + compileOnly.extendsFrom multirelase + } } repositories { @@ -26,12 +32,13 @@ repositories { } dependencies { - compileOnly "com.google.code.gson:gson:2.8.7" + compileOnly "com.google.code.gson:gson:2.8.5" compileOnly "cpw.mods:modlauncher:8.0.9" compileOnly "net.minecraftforge:installer:2.1.4" compileOnly "net.sf.jopt-simple:jopt-simple:5.0.4" provided project(":legacy") + multirelase project(":jigsaw") } java { @@ -48,6 +55,7 @@ jar { "Implementation-Vendor" :"ZekerZhayard", "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), "Automatic-Module-Name": "${project.group}.${project.archivesBaseName}".toString().toLowerCase(), + "Multi-Release": "true", "Main-Class": "io.github.zekerzhayard.forgewrapper.converter.Main", "GitCommit": String.valueOf(System.getenv("GITHUB_SHA")) ]) @@ -55,30 +63,12 @@ jar { from configurations.provided.files.collect { zipTree(it) } -} -/*task sourcesJar(type: Jar) { - manifest { - attributes(jar.manifest.attributes) - } - from sourceSets.main.allSource - archiveFileName = "${archivesBaseName}-${archiveVersion.get()}-sources.${archiveExtension.get()}" -} - -artifacts { - archives sourcesJar -}*/ - -processResources { - inputs.property "version", project.version - from(sourceSets.main.resources.srcDirs) { - duplicatesStrategy = DuplicatesStrategy.INCLUDE - include "patches/net.minecraftforge.json" - expand "version": project.version - } - from(sourceSets.main.resources.srcDirs) { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - exclude "patches/net.minecraftforge.json" + into "META-INF/versions/9", { + from configurations.multirelase.files.collect { + zipTree(it) + } + exclude "META-INF/**" } } diff --git a/converter/build.gradle b/converter/build.gradle new file mode 100644 index 0000000..93be5a1 --- /dev/null +++ b/converter/build.gradle @@ -0,0 +1,48 @@ + +plugins { + id "java" + id "eclipse" +} + +version = "${rootProject.fw_version}${-> getVersionSuffix()}" +group = "io.github.zekerzhayard" +archivesBaseName = rootProject.name + "Converter" + +configurations { + provided { + compileOnly.extendsFrom provided + } +} + +repositories { + mavenCentral() +} + +dependencies { + compileOnly "com.google.code.gson:gson:2.8.5" + provided rootProject +} + +jar { + manifest.attributes rootProject.jar.manifest.attributes + manifest.attributes([ + "Main-Class": "io.github.zekerzhayard.forgewrapper.converter.Main" + ]) + + from configurations.provided.files.collect { + zipTree(it) + } +} + +processResources { + inputs.property "version", project.version + from(sourceSets.main.resources.srcDirs) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include "patches/net.minecraftforge.json" + expand "version": project.version + } + from(sourceSets.main.resources.srcDirs) { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + exclude "patches/net.minecraftforge.json" + } +} diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java b/converter/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java similarity index 96% rename from src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java rename to converter/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java index 403488b..0fdfc13 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java +++ b/converter/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Converter.java @@ -120,11 +120,11 @@ private static JsonObject convertPatchesJson(JsonObject installer, JsonObject in } Map additionalUrls = new HashMap<>(); String path = String.format("net/minecraftforge/forge/%s-%s/forge-%s-%s", mcVersion, forgeVersion, mcVersion, forgeVersion); - additionalUrls.put(path + "-universal.jar", "https://files.minecraftforge.net/maven/" + path + "-universal.jar"); + additionalUrls.put(path + "-universal.jar", "https://maven.minecraftforge.net/" + path + "-universal.jar"); transformLibraries(getElement(installProfile, "libraries").getAsJsonArray(), mavenFiles, additionalUrls); additionalUrls.clear(); - additionalUrls.put(path + ".jar", "https://files.minecraftforge.net/maven/" + path + "-launcher.jar"); - transformLibraries(getElement(installer ,"libraries").getAsJsonArray(), libraries, additionalUrls); + additionalUrls.put(path + ".jar", "https://maven.minecraftforge.net/" + path + "-launcher.jar"); + transformLibraries(getElement(installer, "libraries").getAsJsonArray(), libraries, additionalUrls); patches.addProperty("version", forgeVersion); for (JsonElement require : getElement(patches, "requires").getAsJsonArray()) { diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Main.java b/converter/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Main.java similarity index 100% rename from src/main/java/io/github/zekerzhayard/forgewrapper/converter/Main.java rename to converter/src/main/java/io/github/zekerzhayard/forgewrapper/converter/Main.java diff --git a/src/main/resources/mmc-pack.json b/converter/src/main/resources/mmc-pack.json similarity index 98% rename from src/main/resources/mmc-pack.json rename to converter/src/main/resources/mmc-pack.json index 3622087..87db08a 100644 --- a/src/main/resources/mmc-pack.json +++ b/converter/src/main/resources/mmc-pack.json @@ -10,4 +10,4 @@ "uid": "net.minecraftforge" } ] -} \ No newline at end of file +} diff --git a/src/main/resources/patches/net.minecraftforge.json b/converter/src/main/resources/patches/net.minecraftforge.json similarity index 99% rename from src/main/resources/patches/net.minecraftforge.json rename to converter/src/main/resources/patches/net.minecraftforge.json index d170dd2..8b09773 100644 --- a/src/main/resources/patches/net.minecraftforge.json +++ b/converter/src/main/resources/patches/net.minecraftforge.json @@ -25,4 +25,4 @@ "MMC-filename": "ForgeWrapper-${version}.jar" } ] -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index 38e3501..a0a9a4a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.daemon = false -fw_version = 1.5 \ No newline at end of file +fw_version = 1.5.1 diff --git a/jigsaw/build.gradle b/jigsaw/build.gradle new file mode 100644 index 0000000..a555ec3 --- /dev/null +++ b/jigsaw/build.gradle @@ -0,0 +1,28 @@ + +plugins { + id "java" + id "eclipse" +} + +compileJava { + if (JavaVersion.current() < JavaVersion.VERSION_1_9) { + javaCompiler = javaToolchains.compilerFor { + languageVersion = JavaLanguageVersion.of(9) + } + } + sourceCompatibility = 9 + targetCompatibility = 9 +} + +configurations { + apiElements { + attributes { + attribute TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8 + } + } + runtimeElements { + attributes { + attribute TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8 + } + } +} 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 new file mode 100644 index 0000000..ce12029 --- /dev/null +++ b/jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java @@ -0,0 +1,195 @@ +package io.github.zekerzhayard.forgewrapper.installer.util; + +import java.io.File; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import sun.misc.Unsafe; + +public class ModuleUtil { + private final static MethodHandles.Lookup IMPL_LOOKUP = getImplLookup(); + + private static MethodHandles.Lookup getImplLookup() { + try { + // Get theUnsafe + Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + Unsafe unsafe = (Unsafe) unsafeField.get(null); + + // Get IMPL_LOOKUP + Field implLookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + return (MethodHandles.Lookup) unsafe.getObject(unsafe.staticFieldBase(implLookupField), unsafe.staticFieldOffset(implLookupField)); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + /** + * add module-path at runtime + */ + @SuppressWarnings("unchecked") + public static void addModules(String modulePath) throws Throwable { + // Find all extra modules + ModuleFinder finder = ModuleFinder.of(Arrays.stream(modulePath.split(File.pathSeparator)).map(Paths::get).toArray(Path[]::new)); + MethodHandle loadModuleMH = IMPL_LOOKUP.findVirtual(Class.forName("jdk.internal.loader.BuiltinClassLoader"), "loadModule", MethodType.methodType(void.class, ModuleReference.class)); + + // Resolve modules to a new config + Configuration config = Configuration.resolveAndBind(finder, List.of(ModuleLayer.boot().configuration()), finder, finder.findAll().stream().peek(mref -> { + try { + // Load all extra modules in system class loader (unnamed modules for now) + loadModuleMH.invokeWithArguments(ClassLoader.getSystemClassLoader(), mref); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }).map(ModuleReference::descriptor).map(ModuleDescriptor::name).collect(Collectors.toList())); + + // 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((k, v) -> { + try { + cfSetter.invokeWithArguments(k, ModuleLayer.boot().configuration()); + v.forEach(m -> { + try { + cfSetter.invokeWithArguments(m, ModuleLayer.boot().configuration()); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }); + graphMap.putAll((Map>) graphGetter.invokeWithArguments(ModuleLayer.boot().configuration())); + IMPL_LOOKUP.findSetter(Configuration.class, "graph", Map.class).invokeWithArguments(ModuleLayer.boot().configuration(), new HashMap<>(graphMap)); + + // Reset boot module layer resolved modules as new config resolved modules to prepare define modules + Set oldBootModules = ModuleLayer.boot().configuration().modules(); + MethodHandle modulesSetter = IMPL_LOOKUP.findSetter(Configuration.class, "modules", Set.class); + HashSet modulesSet = new HashSet<>(config.modules()); + modulesSetter.invokeWithArguments(ModuleLayer.boot().configuration(), new HashSet<>(modulesSet)); + + // Prepare to add all of the new config "nameToModule" to boot module layer config + MethodHandle nameToModuleGetter = IMPL_LOOKUP.findGetter(Configuration.class, "nameToModule", Map.class); + HashMap nameToModuleMap = new HashMap<>((Map) nameToModuleGetter.invokeWithArguments(ModuleLayer.boot().configuration())); + nameToModuleMap.putAll((Map) nameToModuleGetter.invokeWithArguments(config)); + IMPL_LOOKUP.findSetter(Configuration.class, "nameToModule", Map.class).invokeWithArguments(ModuleLayer.boot().configuration(), new HashMap<>(nameToModuleMap)); + + // Define all extra modules and add all of the new config "nameToModule" to boot module layer config + ((Map) IMPL_LOOKUP.findGetter(ModuleLayer.class, "nameToModule", Map.class).invokeWithArguments(ModuleLayer.boot())).putAll((Map) IMPL_LOOKUP.findStatic(Module.class, "defineModules", MethodType.methodType(Map.class, Configuration.class, Function.class, ModuleLayer.class)).invokeWithArguments(ModuleLayer.boot().configuration(), (Function) name -> ClassLoader.getSystemClassLoader(), ModuleLayer.boot())); + + // Add all of resolved modules + modulesSet.addAll(oldBootModules); + modulesSetter.invokeWithArguments(ModuleLayer.boot().configuration(), new HashSet<>(modulesSet)); + + // Reset cache of boot module layer + IMPL_LOOKUP.findSetter(ModuleLayer.class, "modules", Set.class).invokeWithArguments(ModuleLayer.boot(), null); + IMPL_LOOKUP.findSetter(ModuleLayer.class, "servicesCatalog", Class.forName("jdk.internal.module.ServicesCatalog")).invokeWithArguments(ModuleLayer.boot(), null); + + // 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(bm -> { + try { + implAddReadsMH.invokeWithArguments(m, bm); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + })))); + } + + public static void addExports(List exports) throws Throwable { + MethodHandle implAddExportsMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddExports", MethodType.methodType(void.class, String.class, Module.class)); + MethodHandle implAddExportsToAllUnnamedMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddExportsToAllUnnamed", MethodType.methodType(void.class, String.class)); + + addExtra(exports, implAddExportsMH, implAddExportsToAllUnnamedMH); + } + + public static void addOpens(List opens) throws Throwable { + MethodHandle implAddOpensMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddOpens", MethodType.methodType(void.class, String.class, Module.class)); + MethodHandle implAddOpensToAllUnnamedMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddOpensToAllUnnamed", MethodType.methodType(void.class, String.class)); + + addExtra(opens, implAddOpensMH, implAddOpensToAllUnnamedMH); + } + + private static void addExtra(List extras, MethodHandle implAddExtraMH, MethodHandle implAddExtraToAllUnnamedMH) { + extras.forEach(extra -> { + ParserData data = parseModuleExtra(extra); + if (data != null) { + ModuleLayer.boot().findModule(data.module).ifPresent(m -> { + try { + if ("ALL-UNNAMED".equals(data.target)) { + implAddExtraToAllUnnamedMH.invokeWithArguments(m, data.packages); + } else { + ModuleLayer.boot().findModule(data.target).ifPresent(tm -> { + try { + implAddExtraMH.invokeWithArguments(m, data.packages, tm); + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + } + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + } + }); + } + + // /= + private static ParserData parseModuleExtra(String extra) { + String[] all = extra.split("=", 2); + if (all.length < 2) { + return null; + } + + String[] source = all[0].split("/", 2); + if (source.length < 2) { + return null; + } + return new ParserData(source[0], source[1], all[1]); + } + + private static class ParserData { + final String module; + final String packages; + final String target; + + ParserData(String module, String packages, String target) { + this.module = module; + this.packages = packages; + this.target = target; + } + } + + // ForgeWrapper need some extra settings to invoke BootstrapLauncher. + public static void setupBootstrapLauncher() throws Throwable { + MethodHandle implAddOpensMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddOpens", MethodType.methodType(void.class, String.class, Module.class)); + ModuleLayer.boot().findModule("cpw.mods.bootstraplauncher").ifPresent(m -> { + try { + implAddOpensMH.invokeWithArguments(m, "cpw.mods.bootstraplauncher", ModuleUtil.class.getModule()); + } catch (Throwable t) { + throw new RuntimeException(t); + } + }); + } +} diff --git a/legacy/build.gradle b/legacy/build.gradle index 5036489..8824390 100644 --- a/legacy/build.gradle +++ b/legacy/build.gradle @@ -14,4 +14,4 @@ repositories { dependencies { compileOnly "net.minecraftforge:installer:2.0.24" -} \ No newline at end of file +} diff --git a/settings.gradle b/settings.gradle index c2cb153..2d5aa2f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,5 @@ rootProject.name = 'ForgeWrapper' -include 'legacy' \ No newline at end of file +include 'converter' +include 'jigsaw' +include 'legacy' diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java new file mode 100644 index 0000000..98e8dda --- /dev/null +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Bootstrap.java @@ -0,0 +1,78 @@ +package io.github.zekerzhayard.forgewrapper.installer; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import io.github.zekerzhayard.forgewrapper.installer.util.ModuleUtil; + +public class Bootstrap { + public static void bootstrap(List 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)); + } + jvmArgs = replacedJvmArgs; + + String modulePath = null; + List addExports = new ArrayList<>(); + List addOpens = new ArrayList<>(); + for (int i = 0; i < jvmArgs.size(); i++) { + String arg = jvmArgs.get(i); + + if (arg.equals("-p") || arg.equals("--module-path")) { + modulePath = jvmArgs.get(i + 1); + } else if (arg.startsWith("--module-path=")) { + modulePath = arg.split("=", 2)[1]; + } + + if (arg.equals("--add-exports")) { + addExports.add(jvmArgs.get(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)); + } 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 is too broad and may cause some problems, so we define it more precisely. + String[] ignores = (prop[1] + ",NewLaunch.jar,ForgeWrapper-," + minecraftJar).split(","); + List ignoreList = new ArrayList<>(); + for (String classPathName : System.getProperty("java.class.path").replace(File.separator, "/").split(File.pathSeparator)) { + Path classPath = Paths.get(classPathName); + String fileName = classPath.getFileName().toString(); + if (Stream.of(ignores).anyMatch(fileName::contains)) { + String absolutePath = classPath.toAbsolutePath().toString(); + if (absolutePath.contains(",")) { + absolutePath = absolutePath.substring(absolutePath.lastIndexOf(",")); + } + ignoreList.add(absolutePath.replace(File.separator, "/")); + } + } + System.setProperty(prop[0], String.join(",", ignoreList)); + } else { + System.setProperty(prop[0], prop[1]); + } + } + } + + if (modulePath != null) { + ModuleUtil.addModules(modulePath); + } + ModuleUtil.addExports(addExports); + ModuleUtil.addOpens(addOpens); + ModuleUtil.setupBootstrapLauncher(); + } +} 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 78c2d58..99ead7c 100644 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/Main.java @@ -5,6 +5,7 @@ import java.net.URLClassLoader; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -13,13 +14,14 @@ import io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector; public class Main { - public static void main(String[] args) throws Exception { + public static void main(String[] args) throws Throwable { List argsList = Stream.of(args).collect(Collectors.toList()); String mcVersion = argsList.get(argsList.indexOf("--fml.mcVersion") + 1); String forgeVersion = argsList.get(argsList.indexOf("--fml.forgeVersion") + 1); String forgeFullVersion = mcVersion + "-" + forgeVersion; IFileDetector detector = DetectorLoader.loadDetector(); + Bootstrap.bootstrap(detector.getJvmArgs(forgeFullVersion), detector.getMinecraftJar(mcVersion).getFileName().toString(), detector.getLibraryDir().toAbsolutePath().toString()); if (!detector.checkExtraFiles(forgeFullVersion)) { System.out.println("Some extra libraries are missing! Run the installer to generate them now."); @@ -47,7 +49,7 @@ public static void main(String[] args) throws Exception { } } - Launcher.main(args); // TODO: this will be broken in forge 1.17 + Class.forName(detector.getMainClass(forgeFullVersion)).getMethod("main", String[].class).invoke(null, new Object[] { args }); } // https://github.com/MinecraftForge/Installer/blob/fe18a164b5ebb15b5f8f33f6a149cc224f446dc2/src/main/java/net/minecraftforge/installer/actions/PostProcessors.java#L287-L303 diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/MainV2.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/MainV2.java deleted file mode 100644 index 230d9fb..0000000 --- a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/MainV2.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.github.zekerzhayard.forgewrapper.installer; - -import java.util.function.Consumer; - -// to support forge 1.17 (bootstraplauncher) -public class MainV2 implements Consumer { - @Override - public void accept(String[] args) { - try { - Main.main(args); - } catch (Throwable t) { - throw new RuntimeException(t); - } - } -} 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 b816708..2f8c976 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 @@ -12,10 +12,15 @@ import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.AbstractMap; +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.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; @@ -81,32 +86,55 @@ default Path getMinecraftJar(String mcVersion) { return null; } + /** + * @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0). + * @return The list of jvm args. + */ + default List getJvmArgs(String forgeFullVersion) { + return this.getDataFromInstaller(forgeFullVersion, "version.json", e -> { + JsonElement element = getElement(e.getAsJsonObject().getAsJsonObject("arguments"), "jvm"); + List args = new ArrayList<>(); + if (!element.equals(JsonNull.INSTANCE)) { + element.getAsJsonArray().iterator().forEachRemaining(je -> args.add(je.getAsString())); + } + return args; + }); + } + + /** + * @param forgeFullVersion Forge full version (e.g. 1.14.4-28.2.0). + * @return The main class. + */ + default String getMainClass(String forgeFullVersion) { + return this.getDataFromInstaller(forgeFullVersion, "version.json", e -> e.getAsJsonObject().getAsJsonPrimitive("mainClass").getAsString()); + } + /** * @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 forgeFullVersion) { + return this.getDataFromInstaller(forgeFullVersion, "install_profile.json", e -> e.getAsJsonObject().getAsJsonObject("data")); + } + + default R getDataFromInstaller(String forgeFullVersion, String entry, Function function) { Path installer = this.getInstallerJar(forgeFullVersion); if (isFile(installer)) { try (ZipFile zf = new ZipFile(installer.toFile())) { - ZipEntry ze = zf.getEntry("install_profile.json"); + ZipEntry ze = zf.getEntry(entry); if (ze != null) { try ( InputStream is = zf.getInputStream(ze); InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8) ) { - for (Map.Entry entry : new JsonParser().parse(isr).getAsJsonObject().entrySet()) { - if (entry.getKey().equals("data")) { - return entry.getValue().getAsJsonObject(); - } - } + return function.apply(new JsonParser().parse(isr)); } } } catch (IOException e) { e.printStackTrace(); } } else { - throw new RuntimeException("Can't detect the forge installer!"); + throw new RuntimeException("Unable to detect the forge installer!"); } return null; } @@ -123,6 +151,7 @@ default boolean checkExtraFiles(String forgeFullVersion) { Map hashMap = new HashMap<>(); // Get all "data//client" elements. + Pattern artifactPattern = Pattern.compile("^\\[(?[^:]*):(?[^:]*):(?[^:@]*)(:(?[^@]*))?(@(?[^]]*))?]$"); for (Map.Entry entry : jo.entrySet()) { String clientStr = getElement(entry.getValue().getAsJsonObject(), "client").getAsString(); if (entry.getKey().endsWith("_SHA")) { @@ -132,8 +161,7 @@ default boolean checkExtraFiles(String forgeFullVersion) { hashMap.put(entry.getKey(), m.group("sha1")); } } else { - Pattern p = Pattern.compile("^\\[(?[^:]*):(?[^:]*):(?[^:@]*)(:(?[^@]*))?(@(?[^]]*))?]$"); - Matcher m = p.matcher(clientStr); + Matcher m = artifactPattern.matcher(clientStr); if (m.find()) { String groupId = nullToDefault(m.group("groupId"), ""); String artifactId = nullToDefault(m.group("artifactId"), ""); @@ -152,7 +180,11 @@ default boolean checkExtraFiles(String forgeFullVersion) { // Check all cached libraries. boolean checked = true; for (Map.Entry entry : libsMap.entrySet()) { - checked = checked && this.checkExtraFile(entry.getValue(), hashMap.get(entry.getKey() + "_SHA")); + checked = this.checkExtraFile(entry.getValue(), hashMap.get(entry.getKey() + "_SHA")); + if (!checked) { + System.out.println("Missing: " + entry.getValue()); + break; + } } return checked; } diff --git a/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java new file mode 100644 index 0000000..0b37945 --- /dev/null +++ b/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java @@ -0,0 +1,21 @@ +package io.github.zekerzhayard.forgewrapper.installer.util; + +import java.util.List; + +public class ModuleUtil { + public static void addModules(String modulePath) { + // nothing to do with Java 8 + } + + public static void addExports(List exports) { + // nothing to do with Java 8 + } + + public static void addOpens(List opens) { + // nothing to do with Java 8 + } + + public static void setupBootstrapLauncher() { + // nothing to do with Java 8 + } +} diff --git a/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector b/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector index 44375f8..31f2c4e 100644 --- a/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector +++ b/src/main/resources/META-INF/services/io.github.zekerzhayard.forgewrapper.installer.detector.IFileDetector @@ -1 +1 @@ -io.github.zekerzhayard.forgewrapper.installer.detector.MultiMCFileDetector \ No newline at end of file +io.github.zekerzhayard.forgewrapper.installer.detector.MultiMCFileDetector diff --git a/src/main/resources/META-INF/services/java.util.function.Consumer b/src/main/resources/META-INF/services/java.util.function.Consumer deleted file mode 100644 index 64cc969..0000000 --- a/src/main/resources/META-INF/services/java.util.function.Consumer +++ /dev/null @@ -1 +0,0 @@ -io.github.zekerzhayard.forgewrapper.installer.MainV2 \ No newline at end of file