diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 599cabe41e9..04cb5d08c52 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ mixin = "0.8.7" bootstrap = "2.1.1" modlauncher = "10.2.1" securemodules = "2.2.20" +jarjar = "0.3.26" guava = "32.1.2-jre" mockito = "5.11.0" jline = "3.25.1" @@ -55,6 +56,7 @@ mockito-junitJupiter = { module = "org.mockito:mockito-junit-jupiter", version.r # vanilla forgeAutoRenamingTool = { module = "net.minecraftforge:ForgeAutoRenamingTool", version.ref = "forgeAutoRenamingTool" } +jarjar-fs = { module = "net.minecraftforge:JarJarFileSystems", version.ref = "jarjar" } jline-reader = { module = "org.jline:jline-reader", version.ref = "jline" } jline-terminal = { module = "org.jline:jline-terminal", version.ref = "jline" } jline-terminalJansi = { module = "org.jline:jline-terminal-jansi", version.ref = "jline" } diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index b9bbe8312d2..0eb68b91846 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -2448,6 +2448,9 @@ + + + diff --git a/vanilla/build.gradle.kts b/vanilla/build.gradle.kts index 66db7fdd3c5..ad08086299e 100644 --- a/vanilla/build.gradle.kts +++ b/vanilla/build.gradle.kts @@ -1,3 +1,5 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + plugins { id("org.spongepowered.gradle.vanilla") alias(libs.plugins.shadow) @@ -30,6 +32,7 @@ val gameLibrariesConfig: NamedDomainObjectProvider = configuratio val gameManagedLibrariesConfig: NamedDomainObjectProvider = configurations.register("spongeGameManagedLibraries") +val bootShadedLibrariesConfig: NamedDomainObjectProvider = configurations.register("spongeBootShadedLibraries") val gameShadedLibrariesConfig: NamedDomainObjectProvider = configurations.register("spongeGameShadedLibraries") val runTaskOnlyConfig: NamedDomainObjectProvider = configurations.register("runTaskOnly") @@ -185,11 +188,11 @@ minecraft { dependsOn(applaunchOutputs) environment("MOD_CLASSES", applaunchOutputs.joinToString(";") { "applaunch%%$it" }) - // Configure GAME and LANG resources - val resources = mutableListOf() - resources.addAll(gameManagedLibrariesConfig.get().files.map { files(it) }) + // Configure resources + val gameResources = mutableListOf() + gameResources.addAll(gameManagedLibrariesConfig.get().files.map { files(it) }) - resources.add(files( + gameResources.add(files( main.get().output, vanillaMain.output, mixins.get().output, vanillaMixins.output, accessors.get().output, vanillaAccessors.output, @@ -197,12 +200,14 @@ minecraft { gameShadedLibrariesConfig.get() )) + dependsOn(gameResources) + jvmArgs("-Dsponge.gameResources=" + gameResources.joinToString(";") { it.joinToString("&") }) + testPluginsProject?.also { - resources.add(it.sourceSets.getByName("main").output) + val plugins: FileCollection = it.sourceSets.getByName("main").output + dependsOn(plugins) + environment("SPONGE_PLUGINS", plugins.joinToString("&")) } - - dependsOn(resources) - environment("SPONGE_PLUGINS", resources.joinToString(";") { it.joinToString("&") }) } } } @@ -237,32 +242,29 @@ dependencies { installer(platform(apiLibs.configurate.bom)) installer(apiLibs.configurate.hocon) installer(apiLibs.configurate.core) - installer(libs.configurate.jackson) installer(libs.joptSimple) installer(libs.tinylog.api) installer(libs.tinylog.impl) - // Override ASM versions, and explicitly declare dependencies so ASM is excluded from the manifest. - val asmExclusions = sequenceOf(libs.asm.asProvider(), libs.asm.commons, libs.asm.tree, libs.asm.analysis) - .onEach { - installer(it) - }.toSet() + + installer(libs.asm.commons) + installer(libs.asm.tree) installer(libs.forgeAutoRenamingTool) { exclude(group = "net.sf.jopt-simple") - asmExclusions.forEach { exclude(group = it.get().group, module = it.get().name) } // Use our own ASM version - } - - // Add the API as a runtime dependency, just so it gets shaded into the jar - add(vanillaInstaller.runtimeOnlyConfigurationName, "org.spongepowered:spongeapi:$apiVersion") { - isTransitive = false + exclude(group = "org.ow2.asm") } val init = initLibrariesConfig.name - init(libs.bootstrap) init(libs.securemodules) init(libs.asm.commons) init(libs.asm.util) + init(libs.jarjar.fs) val boot = bootLibrariesConfig.name + boot(libs.securemodules) + boot(libs.asm.commons) + boot(libs.asm.util) + boot(libs.bootstrap) + boot(libs.modlauncher) { exclude(group = "org.apache.logging.log4j") } @@ -270,7 +272,6 @@ dependencies { exclude(group = "org.checkerframework", module = "checker-qual") // exclude(group = "com.google.code.gson", module = "gson") exclude(group = "org.apache.logging.log4j", module = "log4j-api") - // exclude(group = "org.apache.commons", module = "commons-lang3") } boot(libs.lmaxDisruptor) boot(apiLibs.checkerQual) @@ -296,6 +297,10 @@ dependencies { exclude(group = "org.spongepowered", module = "configurate-core") exclude(group = "org.checkerframework", module = "checker-qual") } + boot(libs.configurate.jackson) { + exclude(group = "org.spongepowered", module = "configurate-core") + exclude(group = "org.checkerframework", module = "checker-qual") + } boot(libs.mixin) boot(libs.asm.tree) @@ -304,9 +309,11 @@ dependencies { exclude(group = "org.checkerframework", module = "checker-qual") } - boot("com.mojang:authlib:6.0.54") { - exclude(group = "com.google.guava", module = "guava") - } + // All minecraft deps except itself + configurations.minecraft.get().resolvedConfiguration.resolvedArtifacts + .map { it.id.componentIdentifier.toString() } + .filter { !it.startsWith("net.minecraft:joined") } + .forEach { boot(it) { isTransitive = false } } boot(project(transformersProject.path)) @@ -319,15 +326,14 @@ dependencies { exclude(group = "org.checkerframework", module = "checker-qual") } game(libs.javaxInject) - game(libs.configurate.jackson) { - exclude(group = "org.spongepowered", module = "configurate-core") - exclude(group = "org.checkerframework", module = "checker-qual") - } game(libs.adventure.serializerAnsi) { exclude(group = "org.jetbrains", module = "annotations") exclude(group = "org.checkerframework", module = "checker-qual") } + val bootShadedLibraries = bootShadedLibrariesConfig.name + bootShadedLibraries(project(transformersProject.path)) { isTransitive = false } + val gameShadedLibraries = gameShadedLibrariesConfig.name gameShadedLibraries("org.spongepowered:spongeapi:$apiVersion") { isTransitive = false } @@ -382,15 +388,12 @@ tasks { manifest{ from(vanillaManifest) attributes( - "Premain-Class" to "org.spongepowered.vanilla.installer.Agent", - "Agent-Class" to "org.spongepowered.vanilla.installer.Agent", - "Launcher-Agent-Class" to "org.spongepowered.vanilla.installer.Agent", - "Multi-Release" to true + "Main-Class" to "org.spongepowered.vanilla.installer.InstallerMain", + "Multi-Release" to true ) } from(vanillaInstaller.output) } - val vanillaAppLaunchJar by registering(Jar::class) { archiveClassifier.set("applaunch") manifest.from(vanillaManifest) @@ -420,11 +423,16 @@ tasks { val installerResources = project.layout.buildDirectory.dir("generated/resources/installer") vanillaInstaller.resources.srcDir(installerResources) + val downloadNotNeeded = configurations.register("downloadNotNeeded") { + extendsFrom(configurations.minecraft.get()) + extendsFrom(gameShadedLibrariesConfig.get()) + } + val emitDependencies by registering(org.spongepowered.gradle.impl.OutputDependenciesToJson::class) { group = "sponge" this.dependencies("bootstrap", bootLibrariesConfig) this.dependencies("main", gameManagedLibrariesConfig) - this.excludedDependencies(gameShadedLibrariesConfig) + this.excludedDependencies(downloadNotNeeded) outputFile.set(installerResources.map { it.file("libraries.json") }) } @@ -432,54 +440,91 @@ tasks { dependsOn(emitDependencies) } - shadowJar { + val vanillaBootShadowJar by register("bootShadowJar", ShadowJar::class) { + group = "shadow" + archiveClassifier.set("boot") + mergeServiceFiles() + configurations = listOf(bootShadedLibrariesConfig.get()) + + manifest { + from(vanillaManifest) + attributes("Automatic-Module-Name" to "spongevanilla.boot") + } - configurations = listOf(project.configurations.getByName(vanillaInstaller.runtimeClasspathConfigurationName)) + from(commonProject.sourceSets.named("applaunch").map { it.output }) + from(vanillaAppLaunch.output) + } + + val installerShadowJar by register("installerShadowJar", ShadowJar::class) { + group = "shadow" + archiveClassifier.set("installer-shadow") + + mergeServiceFiles() + configurations = listOf(installerLibrariesConfig.get(), initLibrariesConfig.get()) + exclude("META-INF/INDEX.LIST", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "module-info.class") - archiveClassifier.set("universal") manifest { - attributes(mapOf( - "Superclass-Transformer" to "common.superclasschange,vanilla.superclasschange", - "Access-Widener" to "common.accesswidener", - "MixinConfigs" to mixinConfigs.joinToString(","), - "Main-Class" to "org.spongepowered.vanilla.installer.InstallerMain", - "Launch-Target" to "sponge_server_prod", - "Multi-Release" to true, - "Premain-Class" to "org.spongepowered.vanilla.installer.Agent", - "Agent-Class" to "org.spongepowered.vanilla.installer.Agent", - "Launcher-Agent-Class" to "org.spongepowered.vanilla.installer.Agent" - )) + from(vanillaManifest) attributes( - mapOf("Implementation-Version" to libs.versions.asm.get()), - "org/objectweb/asm/" + "Main-Class" to "org.spongepowered.vanilla.installer.InstallerMain", + "Automatic-Module-Name" to "spongevanilla.installer", + "Launch-Target" to "sponge_server_prod", + "Multi-Release" to true ) + attributes(mapOf("Implementation-Version" to libs.versions.asm.get()), "org/objectweb/asm/") + } + + from(vanillaInstaller.output) + } + + shadowJar { + group = "shadow" + archiveClassifier.set("mod") + + mergeServiceFiles() + configurations = listOf(gameShadedLibrariesConfig.get()) + + manifest { from(vanillaManifest) + attributes( + "Superclass-Transformer" to "common.superclasschange,vanilla.superclasschange", + "Access-Widener" to "common.accesswidener", + "MixinConfigs" to mixinConfigs.joinToString(","), + "Multi-Release" to true + ) } + from(commonProject.sourceSets.main.map { it.output }) - from(commonProject.sourceSets.named("mixins").map {it.output }) - from(commonProject.sourceSets.named("accessors").map {it.output }) - from(commonProject.sourceSets.named("launch").map {it.output }) - from(commonProject.sourceSets.named("applaunch").map {it.output }) - from(sourceSets.main.map {it.output }) - from(vanillaInstaller.output) - from(vanillaAppLaunch.output) + from(commonProject.sourceSets.named("mixins").map { it.output }) + from(commonProject.sourceSets.named("accessors").map { it.output }) + from(commonProject.sourceSets.named("launch").map { it.output }) + from(vanillaLaunch.output) from(vanillaAccessors.output) from(vanillaMixins.output) - /*dependencies { - // include(project(":")) - include("org.spongepowered:spongeapi:$apiVersion") - } */ + } + + val universalJar = register("universalJar", Jar::class) { + group = "build" + archiveClassifier.set("universal") + + manifest.from(installerShadowJar.manifest) + + from(installerShadowJar.archiveFile.map { zipTree(it) }) + + into("jars") { + from(shadowJar) + rename("spongevanilla-(.*)-mod.jar", "spongevanilla-mod.jar") - // We cannot have modules in a shaded jar - exclude("META-INF/versions/*/module-info.class") - exclude("module-info.class") + from(vanillaBootShadowJar) + rename("spongevanilla-(.*)-boot.jar", "spongevanilla-boot.jar") + } } + assemble { - dependsOn(shadowJar) + dependsOn(universalJar) } - } indraSpotlessLicenser { diff --git a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/Constants.java b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/Constants.java index d133eccd84c..969dea1eb7c 100644 --- a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/Constants.java +++ b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/Constants.java @@ -28,7 +28,6 @@ public final class Constants { public static final class ManifestAttributes { public static final String ACCESS_WIDENER = "Access-Widener"; - public static final String LAUNCH_TARGET = "Launch-Target"; public static final String SUPERCLASS_CHANGE = "Superclass-Transformer"; } } diff --git a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/handler/AbstractVanillaLaunchHandler.java b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/handler/AbstractVanillaLaunchHandler.java index e830a6ddf6e..269bf6c5ea9 100644 --- a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/handler/AbstractVanillaLaunchHandler.java +++ b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/handler/AbstractVanillaLaunchHandler.java @@ -30,6 +30,8 @@ import org.apache.logging.log4j.Logger; import org.spongepowered.vanilla.applaunch.AppLaunchTarget; +import java.util.NoSuchElementException; + /** * The common Sponge {@link ILaunchHandlerService launch handler} for development * and production environments. @@ -45,7 +47,10 @@ public String name() { @Override public ServiceRunner launchService(final String[] arguments, final ModuleLayer gameLayer) { this.logger.info("Transitioning to Sponge launch, please wait..."); - return () -> this.launchSponge(gameLayer.findModule("spongevanilla").orElseThrow(), arguments); + return () -> { + final Module module = gameLayer.findModule("spongevanilla").orElseThrow(() -> new NoSuchElementException("Module spongevanilla")); + this.launchSponge(module, arguments); + }; } public abstract AppLaunchTarget target(); diff --git a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/VanillaPluginPlatform.java b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/VanillaPluginPlatform.java index 4384e2886dd..834760ddb9e 100644 --- a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/VanillaPluginPlatform.java +++ b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/VanillaPluginPlatform.java @@ -36,7 +36,7 @@ import org.spongepowered.plugin.blackboard.Keys; import org.spongepowered.plugin.builtin.StandardEnvironment; import org.spongepowered.plugin.builtin.jvm.JVMKeys; -import org.spongepowered.vanilla.applaunch.plugin.locator.SecureJarPluginResource; +import org.spongepowered.vanilla.applaunch.plugin.resource.SecureJarPluginResource; import java.nio.file.Path; import java.util.Collections; diff --git a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/VanillaTransformationService.java b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/VanillaTransformationService.java index 6c9aa0135dc..c2d44a79c6f 100644 --- a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/VanillaTransformationService.java +++ b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/VanillaTransformationService.java @@ -43,7 +43,7 @@ import org.spongepowered.transformers.modlauncher.AccessWidenerTransformationService; import org.spongepowered.transformers.modlauncher.SuperclassChanger; import org.spongepowered.vanilla.applaunch.Constants; -import org.spongepowered.vanilla.applaunch.plugin.locator.SecureJarPluginResource; +import org.spongepowered.vanilla.applaunch.plugin.resource.SecureJarPluginResource; import java.io.IOException; import java.nio.file.Files; diff --git a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/locator/GameResourceLocatorService.java b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/locator/GameResourceLocatorService.java new file mode 100644 index 00000000000..0a1791fb459 --- /dev/null +++ b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/locator/GameResourceLocatorService.java @@ -0,0 +1,90 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.vanilla.applaunch.plugin.locator; + +import org.spongepowered.plugin.Environment; +import org.spongepowered.plugin.builtin.jvm.JVMPluginResource; +import org.spongepowered.plugin.builtin.jvm.locator.JVMPluginResourceLocatorService; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +public final class GameResourceLocatorService implements JVMPluginResourceLocatorService { + + @Override + public String name() { + return "game"; + } + + @Override + public Set locatePluginResources(final Environment environment) { + environment.logger().info("Locating '{}' resources...", this.name()); + + final Set resources = new HashSet<>(); + final String resourcesProp = System.getProperty("sponge.gameResources"); + if (resourcesProp != null) { + for (final String entry : resourcesProp.split(";")) { + if (entry.isBlank()) { + continue; + } + + final Path[] paths = Stream.of(entry.split("&")).map(Path::of).toArray(Path[]::new); + resources.add(JVMPluginResource.create(environment, this.name(), paths)); + } + } + + final String fsProp = System.getProperty("sponge.rootJarFS"); + if (fsProp != null) { + try { + final FileSystem fs = FileSystems.getFileSystem(new URI(fsProp)); + final Path spongeMod = newJarInJar(fs.getPath("jars", "spongevanilla-mod.jar")); + resources.add(JVMPluginResource.create(environment, this.name(), spongeMod)); + } catch (final Exception e) { + environment.logger().error("Failed to locate spongevanilla-mod jar."); + } + } + + environment.logger().info("Located [{}] resource(s) for '{}'...", resources.size(), this.name()); + + return resources; + } + + private static Path newJarInJar(final Path jar) { + try { + URI jij = new URI("jij:" + jar.toAbsolutePath().toUri().getRawSchemeSpecificPart()).normalize(); + final Map env = Map.of("packagePath", jar); + FileSystem jijFS = FileSystems.newFileSystem(jij, env); + return jijFS.getPath("/"); // root of the archive to load + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/locator/PluginJarMetadata.java b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/resource/PluginJarMetadata.java similarity index 98% rename from vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/locator/PluginJarMetadata.java rename to vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/resource/PluginJarMetadata.java index ff80fbb80f5..6c19e2e2daf 100644 --- a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/locator/PluginJarMetadata.java +++ b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/resource/PluginJarMetadata.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.vanilla.applaunch.plugin.locator; +package org.spongepowered.vanilla.applaunch.plugin.resource; import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; diff --git a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/locator/SecureJarPluginResource.java b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/resource/SecureJarPluginResource.java similarity index 98% rename from vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/locator/SecureJarPluginResource.java rename to vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/resource/SecureJarPluginResource.java index 5fb7fc43e9f..d06c90af96a 100644 --- a/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/locator/SecureJarPluginResource.java +++ b/vanilla/src/applaunch/java/org/spongepowered/vanilla/applaunch/plugin/resource/SecureJarPluginResource.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.vanilla.applaunch.plugin.locator; +package org.spongepowered.vanilla.applaunch.plugin.resource; import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; diff --git a/vanilla/src/applaunch/resources/META-INF/services/org.spongepowered.plugin.PluginResourceLocatorService b/vanilla/src/applaunch/resources/META-INF/services/org.spongepowered.plugin.PluginResourceLocatorService new file mode 100644 index 00000000000..330fcb92be8 --- /dev/null +++ b/vanilla/src/applaunch/resources/META-INF/services/org.spongepowered.plugin.PluginResourceLocatorService @@ -0,0 +1 @@ +org.spongepowered.vanilla.applaunch.plugin.locator.GameResourceLocatorService \ No newline at end of file diff --git a/vanilla/src/installer/java-templates/org/spongepowered/vanilla/installer/Constants.java b/vanilla/src/installer/java-templates/org/spongepowered/vanilla/installer/Constants.java index 0cba3447553..c7a1027467f 100644 --- a/vanilla/src/installer/java-templates/org/spongepowered/vanilla/installer/Constants.java +++ b/vanilla/src/installer/java-templates/org/spongepowered/vanilla/installer/Constants.java @@ -24,8 +24,6 @@ */ package org.spongepowered.vanilla.installer; -import org.objectweb.asm.Opcodes; - public final class Constants { public static final class Libraries { @@ -40,4 +38,8 @@ public static final class Libraries { public static final String SPONGE_NEXUS_DOWNLOAD_URL = "https://repo.spongepowered.org/service/rest/v1/search/assets?md5=%s&maven" + ".groupId=%s&maven.artifactId=%s&maven.baseVersion=%s&maven.extension=jar"; } + + public static final class ManifestAttributes { + public static final String LAUNCH_TARGET = "Launch-Target"; + } } diff --git a/vanilla/src/installer/java/org/spongepowered/vanilla/installer/Agent.java b/vanilla/src/installer/java/org/spongepowered/vanilla/installer/Agent.java deleted file mode 100644 index 59b62592420..00000000000 --- a/vanilla/src/installer/java/org/spongepowered/vanilla/installer/Agent.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.vanilla.installer; - -import org.checkerframework.checker.nullness.qual.Nullable; -import org.tinylog.Logger; - -import java.io.File; -import java.io.IOException; -import java.lang.instrument.Instrumentation; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Map; -import java.util.Set; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -/** - * Agent, used to add downloaded jars to the system classpath and open modules - * for deep reflection. - * - *

This needs to be compiled for exactly java 9, since it runs before we have - * an opportunity to provide a friendly warning message.

- */ -public class Agent { - - private static Instrumentation instrumentation; - private static boolean usingFallback; - - public static void premain(final String agentArgs, final Instrumentation instrumentation) { - Agent.instrumentation = instrumentation; - } - - public static void agentmain(final String agentArgs, final Instrumentation instrumentation) { - Agent.instrumentation = instrumentation; - } - - static void addJarToClasspath(final Path jar) { - if (Agent.instrumentation == null) { - throw new IllegalStateException("The SpongeVanilla jar must be run as a java agent in order to add downloaded libraries to the classpath!"); - } - try { - final Path normalized = Paths.get(jar.toRealPath().toUri().toURL().toURI()); - - if (Agent.usingFallback) { - Fallback.addToSystemClasspath(jar); - return; - } - - try { - // x.X The URL escaping done by appendToSystemClassLoaderSearch differs from the - try (final JarFile jf = new JarFile(new File(normalized.toUri()))) { - Agent.instrumentation.appendToSystemClassLoaderSearch(jf); - } - } catch (final IllegalArgumentException ex) { - // For some reason, the Agent method on Windows can't handle some non-ASCII characters - // This is fairly awful, but it makes things work (and hopefully won't be reached often) - Logger.debug(ex, "Failed to add library {} to classpath, transitioning to fallback (more unsafe!) method", jar); - Agent.usingFallback = true; - Fallback.addToSystemClasspath(jar); - } - } catch (final IOException | URISyntaxException ex) { - Logger.error(ex, "Failed to create jar file for archive '{}'!", jar); - } - } - - static void crackModules() { - final Set systemUnnamed = Set.of(ClassLoader.getSystemClassLoader().getUnnamedModule()); - Agent.instrumentation.redefineModule( - Manifest.class.getModule(), - Set.of(), - Map.of("sun.security.util", systemUnnamed), // ModLauncher - Map.of( - // ModLauncher -- needs Manifest.jv, and various JarVerifier methods - "java.util.jar", systemUnnamed - ), - Set.of(), - Map.of() - ); - } - - static final class Fallback { - - private static final Object SYSTEM_CLASS_PATH; /* a URLClassPath */ - private static final Method ADD_URL; /* URLClassPath.addURL(java.net.URL) */ - - static { - Logger.debug("Initializing fallback classpath modification. This is only expected when using non-ASCII characters in file paths on Windows"); - // Crack the java.base module to allow us to use reflection - final Set systemUnnamed = Set.of(ClassLoader.getSystemClassLoader().getUnnamedModule()); - Agent.instrumentation.redefineModule( - ClassLoader.class.getModule(), /* java.base */ - Set.of(), - Map.of("jdk.internal.loader", systemUnnamed), - Map.of("jdk.internal.loader", systemUnnamed), - Set.of(), - Map.of() - ); - - final ClassLoader loader = ClassLoader.getSystemClassLoader(); - - Field ucp = Fallback.fieldOrNull(loader.getClass(), "ucp"); - if (ucp == null) { - ucp = Fallback.fieldOrNull(loader.getClass().getSuperclass(), "ucp"); - } - - if (ucp == null) { - // Did they change something? - throw new ExceptionInInitializerError("Unable to initialize fallback classpath handling on your system. Perhaps try a different Java version?"); - } - - try { - SYSTEM_CLASS_PATH = ucp.get(loader); - ADD_URL = Fallback.SYSTEM_CLASS_PATH.getClass().getDeclaredMethod("addURL", URL.class); - } catch (final NoSuchMethodException | IllegalAccessException ex) { - throw new ExceptionInInitializerError(ex); - } - } - - private static @Nullable Field fieldOrNull(final @Nullable Class clazz, final String name) { - if (clazz == null) { - return null; - } - - try { - final Field f = clazz.getDeclaredField(name); - f.setAccessible(true); - return f; - } catch (final NoSuchFieldException ex) { - return null; - } - } - - static void addToSystemClasspath(final Path file) { - try { - Fallback.ADD_URL.invoke(Fallback.SYSTEM_CLASS_PATH, file.toUri().toURL()); - } catch (final IllegalAccessException | InvocationTargetException | IOException ex) { - Logger.error(ex, "Failed to add file {} to the system classpath", file); - throw new RuntimeException(ex); - } - } - - } - -} diff --git a/vanilla/src/installer/java/org/spongepowered/vanilla/installer/InstallerMain.java b/vanilla/src/installer/java/org/spongepowered/vanilla/installer/InstallerMain.java index 3e6f96caa00..992785c98d9 100644 --- a/vanilla/src/installer/java/org/spongepowered/vanilla/installer/InstallerMain.java +++ b/vanilla/src/installer/java/org/spongepowered/vanilla/installer/InstallerMain.java @@ -43,9 +43,14 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; import java.net.URL; +import java.net.URLClassLoader; import java.net.URLConnection; import java.nio.file.AccessDeniedException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -63,12 +68,13 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.jar.JarFile; +import java.util.jar.Manifest; import java.util.zip.ZipEntry; public final class InstallerMain { - private static final String COLLECTION_BOOTSTRAP = "bootstrap"; // goes on app - private static final String COLLECTION_MAIN = "main"; // goes on TCL + private static final String COLLECTION_BOOTSTRAP = "bootstrap"; // boot layer + private static final String COLLECTION_MAIN = "main"; // game layer private static final int MAX_TRIES = 2; private final Installer installer; @@ -126,41 +132,72 @@ public void downloadAndRun() throws Exception { } assert remappedMinecraftJar != null; // always assigned or thrown - // MC itself and mojang dependencies are on the main layer, other libs are only on the bootstrap layer + // Minecraft itself is on the main layer libraryManager.addLibrary(InstallerMain.COLLECTION_MAIN, new LibraryManager.Library("minecraft", remappedMinecraftJar.server())); - for (final Map.Entry library : remappedMinecraftJar.libraries().entrySet()) { - if (library.getKey().group().equals("com.mojang") || library.getKey().group().equals("net.minecraft")) { - libraryManager.addLibrary(InstallerMain.COLLECTION_MAIN, new LibraryManager.Library(library.getKey().toString(), library.getValue())); - } else { - libraryManager.addLibrary(InstallerMain.COLLECTION_BOOTSTRAP, new LibraryManager.Library(library.getKey().toString(), library.getValue())); - } + + // Other libs are on the bootstrap layer + for (final Map.Entry entry : remappedMinecraftJar.libraries().entrySet()) { + final GroupArtifactVersion artifact = entry.getKey(); + final Path path = entry.getValue(); + + libraryManager.addLibrary(InstallerMain.COLLECTION_BOOTSTRAP, new LibraryManager.Library(artifact.toString(), path)); } + this.installer.getLibraryManager().finishedProcessing(); Logger.info("Environment has been verified."); final Set seenLibs = new HashSet<>(); - this.installer.getLibraryManager().getAll(InstallerMain.COLLECTION_BOOTSTRAP).stream() - .peek(lib -> seenLibs.add(lib.getName())) - .map(LibraryManager.Library::getFile) - .forEach(path -> { - Logger.debug("Adding jar {} to bootstrap classpath", path); - Agent.addJarToClasspath(path); - }); - - final Path[] transformableLibs = this.installer.getLibraryManager().getAll(InstallerMain.COLLECTION_MAIN).stream() - .filter(lib -> !seenLibs.contains(lib.getName())) - .map(LibraryManager.Library::getFile) + final Path[] bootLibs = this.installer.getLibraryManager().getAll(InstallerMain.COLLECTION_BOOTSTRAP).stream() + .peek(lib -> seenLibs.add(lib.name())) + .map(LibraryManager.Library::file) .toArray(Path[]::new); + final Path[] gameLibs = this.installer.getLibraryManager().getAll(InstallerMain.COLLECTION_MAIN).stream() + .filter(lib -> !seenLibs.contains(lib.name())) + .map(LibraryManager.Library::file) + .toArray(Path[]::new); + + final URL rootJar = InstallerMain.class.getProtectionDomain().getCodeSource().getLocation(); + final URI fsURI = new URI("jar", rootJar.toString(), null); + System.setProperty("sponge.rootJarFS", fsURI.toString()); + + final FileSystem fs = FileSystems.newFileSystem(fsURI, Map.of()); + final Path spongeBoot = newJarInJar(fs.getPath("jars", "spongevanilla-boot.jar")); + + String launchTarget = LauncherCommandLine.launchTarget; + if (launchTarget == null) { + final Path manifestFile = fs.getPath("META-INF", "MANIFEST.MF"); + try (final InputStream stream = Files.newInputStream(manifestFile)) { + final Manifest manifest = new Manifest(stream); + launchTarget = manifest.getMainAttributes().getValue(Constants.ManifestAttributes.LAUNCH_TARGET); + } + } + + final StringBuilder gameLibsEnv = new StringBuilder(); + for (final Path lib : gameLibs) { + gameLibsEnv.append(lib.toAbsolutePath()).append(';'); + } + gameLibsEnv.setLength(gameLibsEnv.length() - 1); + System.setProperty("sponge.gameResources", gameLibsEnv.toString()); + final List gameArgs = new ArrayList<>(LauncherCommandLine.remainingArgs); + gameArgs.add("--launchTarget"); + gameArgs.add(launchTarget); Collections.addAll(gameArgs, this.installer.getLauncherConfig().args.split(" ")); - // Suppress illegal reflection warnings on newer java - Agent.crackModules(); + InstallerMain.bootstrap(bootLibs, spongeBoot, gameArgs.toArray(new String[0])); + } - final String className = "org.spongepowered.vanilla.applaunch.Main"; - InstallerMain.invokeMain(className, gameArgs.toArray(new String[0]), transformableLibs); + private static Path newJarInJar(final Path jar) { + try { + URI jij = new URI("jij:" + jar.toAbsolutePath().toUri().getRawSchemeSpecificPart()).normalize(); + final Map env = Map.of("packagePath", jar); + FileSystem jijFS = FileSystems.newFileSystem(jij, env); + return jijFS.getPath("/"); // root of the archive to load + } catch (Exception e) { + throw new RuntimeException(e); + } } private ServerAndLibraries recoverFromMinecraftDownloadError(final T ex) throws T { @@ -175,17 +212,33 @@ private ServerAndLibraries recoverFromMinecraftDownloadErr } } - private static void invokeMain(final String className, final String[] args, final Path[] extraCpEntries) { + private static void bootstrap(final Path[] bootLibs, final Path spongeBoot, final String[] args) throws Exception { + final URL[] urls = new URL[bootLibs.length]; + for (int i = 0; i < bootLibs.length; i++) { + urls[i] = bootLibs[i].toAbsolutePath().toUri().toURL(); + } + + final List classpath = new ArrayList<>(); + for (final Path lib : bootLibs) { + classpath.add(new Path[] { lib }); + } + classpath.add(new Path[] { spongeBoot }); + + URLClassLoader loader = new URLClassLoader(urls, ClassLoader.getPlatformClassLoader()); + ClassLoader previousLoader = Thread.currentThread().getContextClassLoader(); try { - Class.forName(className) - .getMethod("main", String[].class, Path[].class) - .invoke(null, args, extraCpEntries); - } catch (final InvocationTargetException ex) { - Logger.error(ex.getCause(), "Failed to invoke main class {} due to an error", className); - System.exit(1); - } catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException ex) { - Logger.error(ex, "Failed to invoke main class {} due to an error", className); + Thread.currentThread().setContextClassLoader(loader); + final Class cl = Class.forName("net.minecraftforge.bootstrap.Bootstrap", false, loader); + final Object instance = cl.getDeclaredConstructor().newInstance(); + final Method m = cl.getDeclaredMethod("bootstrapMain", String[].class, List.class); + m.setAccessible(true); + m.invoke(instance, args, classpath); + } catch (final Exception ex) { + final Throwable cause = ex instanceof InvocationTargetException ? ex.getCause() : ex; + Logger.error(cause, "Failed to invoke bootstrap main due to an error"); System.exit(1); + } finally { + Thread.currentThread().setContextClassLoader(previousLoader); } } @@ -391,47 +444,46 @@ private ServerAndLibraries remapMinecraft(final ServerAndLibraries minecraft, fi final Renamer.Builder renamerBuilder = Renamer.builder() .add(Transformer.parameterAnnotationFixerFactory()) .add(ctx -> { - final Transformer backing = Transformer.renamerFactory(mappings, false).create(ctx); - return new Transformer() { - - @Override - public ClassEntry process(final ClassEntry entry) { - final String name = entry.getName(); - if (name.startsWith("it/unimi") - || name.startsWith("com/google") - || name.startsWith("com/mojang/datafixers") - || name.startsWith("com/mojang/brigadier") - || name.startsWith("org/apache")) { - return entry; + final Transformer backing = Transformer.renamerFactory(mappings, false).create(ctx); + return new Transformer() { + @Override + public ClassEntry process(final ClassEntry entry) { + final String name = entry.getName(); + if (name.startsWith("it/unimi") + || name.startsWith("com/google") + || name.startsWith("com/mojang/datafixers") + || name.startsWith("com/mojang/brigadier") + || name.startsWith("org/apache")) { + return entry; + } + return backing.process(entry); } - return backing.process(entry); - } - - @Override - public ManifestEntry process(final ManifestEntry entry) { - return backing.process(entry); - } - @Override - public ResourceEntry process(final ResourceEntry entry) { - return backing.process(entry); - } + @Override + public ManifestEntry process(final ManifestEntry entry) { + return backing.process(entry); + } - @Override - public Collection getExtras() { - return backing.getExtras(); - } + @Override + public ResourceEntry process(final ResourceEntry entry) { + return backing.process(entry); + } - }; + @Override + public Collection getExtras() { + return backing.getExtras(); + } + }; }) .add(Transformer.recordFixerFactory()) .add(Transformer.parameterAnnotationFixerFactory()) .add(Transformer.sourceFixerFactory(SourceFixerConfig.JAVA)) .add(Transformer.signatureStripperFactory(SignatureStripperConfig.ALL)) .logger(Logger::debug); // quiet - try (final Renamer ren = renamerBuilder.build()) { - ren.run(minecraft.server.toFile(), tempOutput.toFile()); - } + + try (final Renamer ren = renamerBuilder.build()) { + ren.run(minecraft.server.toFile(), tempOutput.toFile()); + } // Restore file try { diff --git a/vanilla/src/installer/java/org/spongepowered/vanilla/installer/LauncherCommandLine.java b/vanilla/src/installer/java/org/spongepowered/vanilla/installer/LauncherCommandLine.java index f52c34a3169..89cc81e3075 100644 --- a/vanilla/src/installer/java/org/spongepowered/vanilla/installer/LauncherCommandLine.java +++ b/vanilla/src/installer/java/org/spongepowered/vanilla/installer/LauncherCommandLine.java @@ -39,12 +39,16 @@ public final class LauncherCommandLine { private static final OptionParser PARSER = new OptionParser(); - private static final ArgumentAcceptingOptionSpec INSTALLER_DIRECTORY_ARG = LauncherCommandLine.PARSER.accepts("installerDir", - "Alternative installer directory").withRequiredArg().withValuesConvertedBy(new PathConverter(PathProperties.DIRECTORY_EXISTING)) + private static final ArgumentAcceptingOptionSpec INSTALLER_DIRECTORY_ARG = LauncherCommandLine.PARSER + .accepts("installerDir", "Alternative installer directory").withRequiredArg() + .withValuesConvertedBy(new PathConverter(PathProperties.DIRECTORY_EXISTING)) .defaultsTo(Paths.get(".")); - private static final ArgumentAcceptingOptionSpec LIBRARIES_DIRECTORY_ARG = LauncherCommandLine.PARSER.accepts("librariesDir", - "Alternative libraries directory").withRequiredArg().withValuesConvertedBy(new PathConverter(PathProperties.DIRECTORY_EXISTING)) + private static final ArgumentAcceptingOptionSpec LIBRARIES_DIRECTORY_ARG = LauncherCommandLine.PARSER + .accepts("librariesDir", "Alternative libraries directory").withRequiredArg() + .withValuesConvertedBy(new PathConverter(PathProperties.DIRECTORY_EXISTING)) .defaultsTo(Paths.get("libraries")); + private static final ArgumentAcceptingOptionSpec LAUNCH_TARGET_ARG = LauncherCommandLine.PARSER + .accepts("launchTarget", "Launch target").withRequiredArg(); private static final NonOptionArgumentSpec REMAINDER = LauncherCommandLine.PARSER.nonOptions().ofType(String.class); static { @@ -52,6 +56,7 @@ public final class LauncherCommandLine { } public static Path installerDirectory, librariesDirectory; + public static String launchTarget; public static List remainingArgs; private LauncherCommandLine() { @@ -61,6 +66,7 @@ public static void configure(final String[] args) { final OptionSet options = LauncherCommandLine.PARSER.parse(args); LauncherCommandLine.installerDirectory = options.valueOf(LauncherCommandLine.INSTALLER_DIRECTORY_ARG); LauncherCommandLine.librariesDirectory = options.valueOf(LauncherCommandLine.LIBRARIES_DIRECTORY_ARG); + LauncherCommandLine.launchTarget = options.valueOf(LauncherCommandLine.LAUNCH_TARGET_ARG); LauncherCommandLine.remainingArgs = UnmodifiableCollections.copyOf(options.valuesOf(LauncherCommandLine.REMAINDER)); } } diff --git a/vanilla/src/installer/java/org/spongepowered/vanilla/installer/LibraryManager.java b/vanilla/src/installer/java/org/spongepowered/vanilla/installer/LibraryManager.java index c7147a310fd..8c939f4c4e9 100644 --- a/vanilla/src/installer/java/org/spongepowered/vanilla/installer/LibraryManager.java +++ b/vanilla/src/installer/java/org/spongepowered/vanilla/installer/LibraryManager.java @@ -87,7 +87,7 @@ public Set getAll(final String collection) { return Collections.unmodifiableSet(this.libraries.getOrDefault(collection, Collections.emptySet())); } - protected void addLibrary(final String set, final Library library) { + void addLibrary(final String set, final Library library) { this.libraries.computeIfAbsent(set, $ -> Collections.synchronizedSet(new LinkedHashSet<>())).add(library); } @@ -244,24 +244,7 @@ public void finishedProcessing() { } } - public static class Library { - - private final String name; - private final Path file; - - public Library(final String name, final Path file) { - this.name = name; - this.file = file; - } - - public String getName() { - return this.name; - } - - public Path getFile() { - return this.file; - } - } + public record Library(String name, Path file) {} private static String asId(final Dependency dep) { return dep.group + ':' + dep.module + ':' + dep.version;