diff --git a/.editorconfig b/.editorconfig index ef62c9e2..71b34f90 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,6 @@ -[*.{gradle,java,kotlin}] +[*.{gradle,groovy,java,kotlin}] indent_style = tab ij_continuation_indent_size = 8 ij_java_imports_layout = $*,|,java.**,|,javax.**,|,*,|,net.fabricmc.** ij_java_class_count_to_use_import_on_demand = 999 +ij_groovy_imports_layout = java.**,|,javax.**,|,*,|,net.fabricmc.**,|,$* diff --git a/.github/workflows/test-push.yml b/.github/workflows/test-push.yml index b8cd9075..2a0eea83 100644 --- a/.github/workflows/test-push.yml +++ b/.github/workflows/test-push.yml @@ -1,5 +1,17 @@ name: Run Tests -on: [push, pull_request] +on: + push: + pull_request: + workflow_dispatch: + inputs: + extended_tests: + description: 'Extended tests' + required: false + default: 'false' + type: choice + options: + - 'false' + - 'true' concurrency: group: build-${{ github.event.pull_request.number || github.ref }} @@ -24,6 +36,8 @@ jobs: build/reports/checkstyle/*.xml build_windows: + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.extended_tests == 'true' }} + runs-on: windows-2022 steps: - uses: actions/checkout@v4 @@ -77,6 +91,7 @@ jobs: - run: ./gradlew printActionsTestName --name="${{ matrix.test }}" test --tests ${{ matrix.test }} --stacktrace --warning-mode fail env: TEST_WARNING_MODE: fail + EXTENDED_TESTS: ${{ github.event.inputs.extended_tests }} id: test - uses: actions/upload-artifact@v4 @@ -91,6 +106,8 @@ jobs: path: "*.hprof" run_tests_windows: + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.extended_tests == 'true' }} + needs: prepare_test_matrix strategy: @@ -98,7 +115,7 @@ jobs: matrix: test: ${{ fromJson(needs.prepare_test_matrix.outputs.matrix) }} - runs-on: windows-2022 + runs-on: windows-2025 steps: - uses: actions/checkout@v4 @@ -130,8 +147,8 @@ jobs: strategy: fail-fast: false matrix: - java: [ 17, 21 ] - os: [ windows-2022, ubuntu-24.04, macos-14 ] + java: [ 21 ] + os: [ windows-2025, ubuntu-24.04, macos-15 ] runs-on: ${{ matrix.os }} steps: diff --git a/build.gradle b/build.gradle index 570cb354..aaafa52f 100644 --- a/build.gradle +++ b/build.gradle @@ -18,12 +18,12 @@ tasks.withType(JavaCompile).configureEach { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions { - jvmTarget = "17" + jvmTarget = "21" } } group = "dev.architectury" -def baseVersion = '1.10' +def baseVersion = '1.11' def ENV = System.getenv() def runNumber = ENV.GITHUB_RUN_NUMBER ?: "9999" @@ -144,6 +144,9 @@ dependencies { // source code remapping implementation libs.fabric.mercury + implementation libs.fabric.unpick + implementation libs.fabric.unpick.utils + // Kotlin implementation(libs.kotlin.metadata) { transitive = false @@ -176,6 +179,9 @@ dependencies { } testImplementation testLibs.mockito testImplementation testLibs.java.debug + testImplementation testLibs.bcprov + testImplementation testLibs.bcutil + testImplementation testLibs.bcpkix compileOnly runtimeLibs.jetbrains.annotations testCompileOnly runtimeLibs.jetbrains.annotations @@ -201,13 +207,13 @@ base { } tasks.withType(JavaCompile).configureEach { - it.options.release = 17 + it.options.release = 21 } java { withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } spotless { @@ -362,6 +368,7 @@ publishing { tasks.register('writeActionsTestMatrix') { doLast { def testMatrix = [] + def extendedTests = Boolean.parseBoolean(System.getenv('EXTENDED_TESTS') ?: 'false') file('src/test/groovy/net/fabricmc/loom/test/integration').traverse { if (it.name.endsWith("Test.groovy")) { if (it.name.endsWith("ReproducibleBuildTest.groovy")) { @@ -369,7 +376,7 @@ tasks.register('writeActionsTestMatrix') { return } - if (it.name.endsWith("DebugLineNumbersTest.groovy")) { + if (it.name.endsWith("DebugLineNumbersTest.groovy") && !extendedTests) { // Known flakey test return } @@ -389,9 +396,6 @@ tasks.register('writeActionsTestMatrix') { // Run all the unit tests together testMatrix.add("net.fabricmc.loom.test.unit.*") - // Kotlin tests - testMatrix.add("net.fabricmc.loom.test.kotlin.*") - def json = groovy.json.JsonOutput.toJson(testMatrix) def output = file("build/test_matrix.json") output.parentFile.mkdir() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 235d08e9..cf5519f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin = "2.0.21" -asm = "9.7.1" +asm = "9.8" commons-io = "2.15.1" gson = "2.10.1" guava = "33.0.0-jre" @@ -12,6 +12,7 @@ mapping-io = "0.7.1" lorenz-tiny = "4.0.2" mercury = "0.1.4.17" loom-native = "0.2.0" +unpick = "3.0.0-beta.9" # Plugins spotless = "6.25.0" @@ -48,6 +49,8 @@ fabric-mapping-io = { module = "net.fabricmc:mapping-io", version.ref = "mapping fabric-lorenz-tiny = { module = "net.fabricmc:lorenz-tiny", version.ref = "lorenz-tiny" } fabric-mercury = { module = "dev.architectury:mercury", version.ref = "mercury" } fabric-loom-nativelib = { module = "net.fabricmc:fabric-loom-native", version.ref = "loom-native" } +fabric-unpick = { module = "net.fabricmc.unpick:unpick", version.ref = "unpick" } +fabric-unpick-utils = { module = "net.fabricmc.unpick:unpick-format-utils", version.ref = "unpick" } # Misc kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } diff --git a/gradle/runtime.libs.versions.toml b/gradle/runtime.libs.versions.toml index bc445d57..614c1bf1 100644 --- a/gradle/runtime.libs.versions.toml +++ b/gradle/runtime.libs.versions.toml @@ -8,9 +8,12 @@ vineflower = "1.11.1" mixin-compile-extensions = "0.6.0" dev-launch-injector = "0.2.1+build.8" terminal-console-appender = "1.3.0" -jetbrains-annotations = "25.0.0" +jetbrains-annotations = "26.0.2" native-support = "1.0.1" -fabric-installer = "1.0.1" +fabric-installer = "1.0.3" + +# Debug tools +renderdoc = "1.37" # Forge Runtime depedencies javax-annotations = "3.0.2" @@ -36,6 +39,9 @@ jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "j native-support = { module = "net.fabricmc:fabric-loom-native-support", version.ref = "native-support" } fabric-installer = { module = "net.fabricmc:fabric-installer", version.ref = "fabric-installer" } +# Debug tools +renderdoc = { module = "org.renderdoc:renderdoc", version.ref = "renderdoc" } # Not a maven dependency + # Forge Runtime depedencies javax-annotations = { module = "com.google.code.findbugs:jsr305", version.ref = "javax-annotations" } mixin-remapper-service = { module = "dev.architectury:architectury-mixin-remapper-service", version.ref = "forge-runtime" } diff --git a/gradle/test.libs.versions.toml b/gradle/test.libs.versions.toml index 6a854144..43205a57 100644 --- a/gradle/test.libs.versions.toml +++ b/gradle/test.libs.versions.toml @@ -1,13 +1,15 @@ [versions] spock = "2.3-groovy-3.0" -junit = "5.11.3" -javalin = "6.3.0" -mockito = "5.14.2" -java-debug = "0.52.0" +junit = "5.12.2" +javalin = "6.6.0" +mockito = "5.17.0" +java-debug = "0.53.1" mixin = "0.15.3+mixin.0.8.7" +bouncycastle = "1.80" -gradle-nightly = "8.14-20250208001853+0000" -fabric-loader = "0.16.9" +gradle-latest = "9.0.0-rc-1" +gradle-nightly = "9.1.0-20250620001442+0000" +fabric-loader = "0.16.14" [libraries] spock = { module = "org.spockframework:spock-core", version.ref = "spock" } @@ -17,5 +19,9 @@ javalin = { module = "io.javalin:javalin", version.ref = "javalin" } mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } java-debug = { module = "com.microsoft.java:com.microsoft.java.debug.core", version.ref = "java-debug" } mixin = { module = "net.fabricmc:sponge-mixin", version.ref = "mixin" } +gradle-latest = { module = "org.gradle:dummy", version.ref = "gradle-latest" } gradle-nightly = { module = "org.gradle:dummy", version.ref = "gradle-nightly" } -fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } \ No newline at end of file +fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } +bcprov = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncycastle" } +bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bouncycastle" } +bcutil = { module = "org.bouncycastle:bcutil-jdk18on", version.ref = "bouncycastle" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b95..1b33c55b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e0fd0202..6514f919 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f3b75f3b..23d15a93 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -205,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a218..db3a6ac2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java index 9854547c..6bfd378b 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/LoomGradleExtension.java @@ -155,6 +155,11 @@ public interface LoomGradleExtension extends LoomGradleExtensionAPI { boolean isProjectIsolationActive(); + /** + * @return true when '--write-verification-metadata` is set + */ + boolean isCollectingDependencyVerificationMetadata(); + // =================== // Architectury Loom // =================== diff --git a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java index 8c82aadf..d3eebd7d 100644 --- a/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java +++ b/src/main/java/net/fabricmc/loom/LoomGradlePlugin.java @@ -27,10 +27,10 @@ package net.fabricmc.loom; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; -import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.gradle.api.Plugin; @@ -98,8 +98,8 @@ public class LoomGradlePlugin implements Plugin { LibraryLocationLogger.logLibraryVersions(); // Apply default plugins - project.apply(ImmutableMap.of("plugin", "java-library")); - project.apply(ImmutableMap.of("plugin", "eclipse")); + project.apply(Map.of("plugin", "java-library")); + project.apply(Map.of("plugin", "eclipse")); // Setup extensions project.getExtensions().create(LoomGradleExtensionAPI.class, "loom", LoomGradleExtensionImpl.class, project, LoomFiles.create(project)); diff --git a/src/main/java/net/fabricmc/loom/api/RemapConfigurationSettings.java b/src/main/java/net/fabricmc/loom/api/RemapConfigurationSettings.java index a4fe759a..8ebc3a6d 100644 --- a/src/main/java/net/fabricmc/loom/api/RemapConfigurationSettings.java +++ b/src/main/java/net/fabricmc/loom/api/RemapConfigurationSettings.java @@ -40,6 +40,8 @@ import org.gradle.api.tasks.SourceSet; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; + /** * A {@link Named} object for configuring "proxy" configurations that remap artifacts. */ @@ -140,7 +142,7 @@ public abstract class RemapConfigurationSettings implements Named { } private Provider defaultDependencyTransforms() { - return getSourceSet().map(sourceSet -> sourceSet.getName().equals(SourceSet.MAIN_SOURCE_SET_NAME) || sourceSet.getName().equals("client")); + return getSourceSet().map(sourceSet -> sourceSet.getName().equals(SourceSet.MAIN_SOURCE_SET_NAME) || sourceSet.getName().equals(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME)); } @Override diff --git a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingContext.java b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingContext.java index ebb1c394..5dd9a47c 100644 --- a/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingContext.java +++ b/src/main/java/net/fabricmc/loom/api/mappings/layered/MappingContext.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2021 FabricMC + * Copyright (c) 2016-2025 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -46,6 +46,8 @@ public interface MappingContext { Supplier intermediaryTree(); + boolean isUsingIntermediateMappings(); + MinecraftProvider minecraftProvider(); default String minecraftVersion() { @@ -62,4 +64,6 @@ public interface MappingContext { DownloadBuilder download(String url); boolean refreshDeps(); + + boolean hasProperty(String property); } diff --git a/src/main/java/net/fabricmc/loom/api/processor/SpecContext.java b/src/main/java/net/fabricmc/loom/api/processor/SpecContext.java index e0480168..9cbe8c67 100644 --- a/src/main/java/net/fabricmc/loom/api/processor/SpecContext.java +++ b/src/main/java/net/fabricmc/loom/api/processor/SpecContext.java @@ -34,9 +34,16 @@ public interface SpecContext { List localMods(); - // Returns mods that are both on the compile and runtime classpath + /** + * Return a set of mods that should be used for transforms, that target EITHER the common or client. + */ List modDependenciesCompileRuntime(); + /** + * Return a set of mods that should be used for transforms, that target ONLY the client. + */ + List modDependenciesCompileRuntimeClient(); + default List allMods() { return Stream.concat(modDependencies().stream(), localMods().stream()).toList(); } diff --git a/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java index 742bab84..451ce90e 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/GroovyApInvoker.java @@ -25,10 +25,10 @@ package net.fabricmc.loom.build.mixin; import java.io.File; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import com.google.common.collect.ImmutableList; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; @@ -41,7 +41,7 @@ public class GroovyApInvoker extends AnnotationProcessorInvoker { public GroovyApInvoker(Project project) { super( project, - ImmutableList.of(), + List.of(), getInvokerTasks(project), AnnotationProcessorInvoker.GROOVY); } diff --git a/src/main/java/net/fabricmc/loom/build/mixin/ScalaApInvoker.java b/src/main/java/net/fabricmc/loom/build/mixin/ScalaApInvoker.java index b6fa1465..99ab5f95 100644 --- a/src/main/java/net/fabricmc/loom/build/mixin/ScalaApInvoker.java +++ b/src/main/java/net/fabricmc/loom/build/mixin/ScalaApInvoker.java @@ -25,10 +25,10 @@ package net.fabricmc.loom.build.mixin; import java.io.File; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import com.google.common.collect.ImmutableList; import org.gradle.api.Project; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; @@ -42,7 +42,7 @@ public class ScalaApInvoker extends AnnotationProcessorInvoker { super( project, // Scala just uses the java AP configuration afaik. This of course assumes the java AP also gets configured. - ImmutableList.of(), + List.of(), getInvokerTasks(project), AnnotationProcessorInvoker.SCALA); } diff --git a/src/main/java/net/fabricmc/loom/build/nesting/NestableJarGenerationTask.java b/src/main/java/net/fabricmc/loom/build/nesting/NestableJarGenerationTask.java index 86a11e8d..35f6fd5c 100644 --- a/src/main/java/net/fabricmc/loom/build/nesting/NestableJarGenerationTask.java +++ b/src/main/java/net/fabricmc/loom/build/nesting/NestableJarGenerationTask.java @@ -39,7 +39,6 @@ import java.util.regex.Pattern; import javax.inject.Inject; -import com.google.common.hash.Hashing; import com.google.gson.JsonObject; import org.apache.commons.io.FileUtils; import org.gradle.api.artifacts.ArtifactView; @@ -63,6 +62,7 @@ import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.task.AbstractLoomTask; +import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.ModPlatform; import net.fabricmc.loom.util.ZipReprocessorUtil; import net.fabricmc.loom.util.ZipUtils; @@ -194,9 +194,7 @@ public abstract class NestableJarGenerationTask extends AbstractLoomTask { // Fabric Loader can't handle modIds longer than 64 characters if (modId.length() > 64) { - String hash = Hashing.sha256() - .hashString(modId, StandardCharsets.UTF_8) - .toString(); + String hash = Checksum.of(modId).sha256().hex(); modId = modId.substring(0, 50) + hash.substring(0, 14); } diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index 830c5985..a024c2bd 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -367,7 +367,7 @@ public abstract class CompileConfiguration implements Runnable { private LockFile getLockFile() { final LoomGradleExtension extension = LoomGradleExtension.get(getProject()); final Path cacheDirectory = extension.getFiles().getUserCache().toPath(); - final String pathHash = Checksum.projectHash(getProject()); + final String pathHash = Checksum.of(getProject()).sha1().hex(); return new LockFile( cacheDirectory.resolve("." + pathHash + ".lock"), "Lock for cache='%s', project='%s'".formatted( diff --git a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java index 2c5755c1..060aa3c7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java +++ b/src/main/java/net/fabricmc/loom/configuration/LoomConfigurations.java @@ -138,7 +138,6 @@ public abstract class LoomConfigurations implements Runnable { register(Constants.Configurations.MAPPINGS, Role.RESOLVABLE); register(Constants.Configurations.MAPPINGS_FINAL, Role.RESOLVABLE); register(Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES, Role.RESOLVABLE); - register(Constants.Configurations.UNPICK_CLASSPATH, Role.RESOLVABLE); register(Constants.Configurations.LOCAL_RUNTIME, Role.RESOLVABLE); extendsFrom(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME, Constants.Configurations.LOCAL_RUNTIME); diff --git a/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java b/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java index 18991bba..ffa95da8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java +++ b/src/main/java/net/fabricmc/loom/configuration/MavenPublication.java @@ -35,7 +35,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; -import com.google.common.collect.ImmutableMap; import groovy.util.Node; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; @@ -54,7 +53,7 @@ import net.fabricmc.loom.util.gradle.GradleUtils; public abstract class MavenPublication implements Runnable { // ImmutableMap is needed since it guarantees ordering // (compile must go before runtime, or otherwise dependencies might get the "weaker" runtime scope). - private static final Map CONFIGURATION_TO_SCOPE = ImmutableMap.of( + private static final Map CONFIGURATION_TO_SCOPE = Map.of( JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME, "compile", JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME, "runtime" ); diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/LocalAccessWidenerEntry.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/LocalAccessWidenerEntry.java index 3d51e3e7..c2aed2d6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/LocalAccessWidenerEntry.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/LocalAccessWidenerEntry.java @@ -25,7 +25,6 @@ package net.fabricmc.loom.configuration.accesswidener; import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; @@ -40,11 +39,7 @@ import net.fabricmc.tinyremapper.TinyRemapper; public record LocalAccessWidenerEntry(Path path, String hash) implements AccessWidenerEntry { public static LocalAccessWidenerEntry create(Path path) { - try { - return new LocalAccessWidenerEntry(path, Checksum.sha1Hex(path)); - } catch (IOException e) { - throw new UncheckedIOException("Failed to create LocalAccessWidenerEntry", e); - } + return new LocalAccessWidenerEntry(path, Checksum.of(path).sha1().hex()); } @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java index dd3a1e98..4556a04e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/DecompileConfiguration.java @@ -24,18 +24,12 @@ package net.fabricmc.loom.configuration.decompile; -import java.io.File; - import org.gradle.api.Project; -import org.gradle.api.artifacts.ConfigurationContainer; import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftJar; import net.fabricmc.loom.configuration.providers.minecraft.mapped.MappedMinecraftProvider; -import net.fabricmc.loom.task.GenerateSourcesTask; -import net.fabricmc.loom.util.Constants; public abstract class DecompileConfiguration { static final String DEFAULT_DECOMPILER = "Vineflower"; @@ -55,15 +49,4 @@ public abstract class DecompileConfiguration public abstract String getTaskName(MinecraftJar.Type type); public abstract void afterEvaluation(); - - protected final void configureUnpick(GenerateSourcesTask task, File unpickOutputJar) { - final ConfigurationContainer configurations = task.getProject().getConfigurations(); - - task.getUnpickDefinitions().set(mappingConfiguration.getUnpickDefinitionsFile()); - task.getUnpickOutputJar().set(unpickOutputJar); - task.getUnpickConstantJar().setFrom(configurations.getByName(Constants.Configurations.MAPPING_CONSTANTS)); - task.getUnpickClasspath().setFrom(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES)); - task.getUnpickClasspath().from(configurations.getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED)); - extension.getMinecraftJars(MappingsNamespace.NAMED).forEach(task.getUnpickClasspath()::from); - } } diff --git a/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java index f93e119f..f79ab3bf 100644 --- a/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/decompile/SingleJarDecompileConfiguration.java @@ -24,7 +24,6 @@ package net.fabricmc.loom.configuration.decompile; -import java.io.File; import java.util.List; import org.gradle.api.Project; @@ -66,11 +65,6 @@ public class SingleJarDecompileConfiguration extends DecompileConfiguration commonDecompileTask = createDecompileTasks("Common", task -> { task.getInputJarName().set(commonJar.getName()); task.getSourcesOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", commonJar.getPath())); - - if (mappingConfiguration.hasUnpickDefinitions()) { - File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-common-unpicked.jar"); - configureUnpick(task, unpickJar); - } }); final TaskProvider clientOnlyDecompileTask = createDecompileTasks("ClientOnly", task -> { task.getInputJarName().set(clientOnlyJar.getName()); task.getSourcesOutputJar().fileValue(GenerateSourcesTask.getJarFileWithSuffix("-sources.jar", clientOnlyJar.getPath())); - if (mappingConfiguration.hasUnpickDefinitions()) { - File unpickJar = new File(extension.getMappingConfiguration().mappingsWorkingDir().toFile(), "minecraft-clientonly-unpicked.jar"); - configureUnpick(task, unpickJar); - } - // Don't allow them to run at the same time. task.mustRunAfter(commonDecompileTask); }); diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java index fe833683..f6b0b175 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfig.java @@ -41,9 +41,9 @@ import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; -import com.google.common.collect.ImmutableMap; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import groovy.xml.XmlUtil; import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.artifacts.ModuleVersionIdentifier; @@ -51,9 +51,6 @@ import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedModuleVersion; import org.gradle.api.tasks.SourceSet; import org.gradle.plugins.ide.eclipse.model.EclipseModel; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.InstallerData; @@ -78,42 +75,7 @@ public class RunConfig { public transient SourceSet sourceSet; public Map environmentVariables; public String projectName; - - public Element genRuns(Element doc) { - Element root = this.addXml(doc, "component", ImmutableMap.of("name", "ProjectRunConfigurationManager")); - root = addXml(root, "configuration", ImmutableMap.of("default", "false", "name", configName, "type", "Application", "factoryName", "Application")); - - this.addXml(root, "module", ImmutableMap.of("name", ideaModuleName)); - this.addXml(root, "option", ImmutableMap.of("name", "MAIN_CLASS_NAME", "value", mainClass)); - this.addXml(root, "option", ImmutableMap.of("name", "WORKING_DIRECTORY", "value", runDirIdeaUrl)); - - if (!vmArgs.isEmpty()) { - this.addXml(root, "option", ImmutableMap.of("name", "VM_PARAMETERS", "value", joinArguments(vmArgs))); - } - - if (!programArgs.isEmpty()) { - this.addXml(root, "option", ImmutableMap.of("name", "PROGRAM_PARAMETERS", "value", joinArguments(programArgs))); - } - - return root; - } - - public Element addXml(Node parent, String name, Map values) { - Document doc = parent.getOwnerDocument(); - - if (doc == null) { - doc = (Document) parent; - } - - Element e = doc.createElement(name); - - for (Map.Entry entry : values.entrySet()) { - e.setAttribute(entry.getKey(), entry.getValue()); - } - - parent.appendChild(e); - return e; - } + public String folderName; // Turns camelCase/PascalCase into Capital Case // caseConversionExample -> Case Conversion Example @@ -194,6 +156,7 @@ public class RunConfig { runConfig.environmentVariables = new HashMap<>(); runConfig.environmentVariables.putAll(settings.getEnvironmentVariables()); runConfig.projectName = project.getName(); + runConfig.folderName = settings.getIdeConfigFolder().getOrNull(); for (Consumer consumer : extension.getSettingsPostEdit()) { consumer.accept(runConfig); @@ -228,6 +191,7 @@ public class RunConfig { dummyConfig = dummyConfig.replace("%VM_ARGS%", joinArguments(vmArgs).replaceAll("\"", """)); dummyConfig = dummyConfig.replace("%IDEA_ENV_VARS%", getEnvVars("")); dummyConfig = dummyConfig.replace("%ECLIPSE_ENV_VARS%", getEnvVars("")); + dummyConfig = dummyConfig.replace("%IDEA_FOLDER_NAME%", folderName == null ? "" : "folderName=\"" + XmlUtil.escapeXml(folderName) + "\""); return dummyConfig; } diff --git a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java index 60b7e2c7..407e5925 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java +++ b/src/main/java/net/fabricmc/loom/configuration/ide/RunConfigSettings.java @@ -54,7 +54,7 @@ import net.fabricmc.loom.util.ModPlatform; import net.fabricmc.loom.util.Platform; import net.fabricmc.loom.util.gradle.SourceSetHelper; -public class RunConfigSettings implements Named { +public abstract class RunConfigSettings implements Named { /** * Arguments for the JVM, such as system properties. */ @@ -465,6 +465,7 @@ public class RunConfigSettings implements Named { defaultMainClass = parent.defaultMainClass; source = parent.source; ideConfigGenerated = parent.ideConfigGenerated; + getIdeConfigFolder().set(parent.getIdeConfigFolder()); } public void makeRunDir() { @@ -483,6 +484,15 @@ public class RunConfigSettings implements Named { this.ideConfigGenerated = ideConfigGenerated; } + /** + * Group this run config under the given folder. + * + *

This is currently only supported on IntelliJ IDEA. + * + * @return The property used to set the config folder. + */ + public abstract Property getIdeConfigFolder(); + @ApiStatus.Internal @ApiStatus.Experimental public Property devLaunchMainClass() { diff --git a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java index 7ab263d6..6df14fd9 100644 --- a/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/ifaceinject/InterfaceInjectionProcessor.java @@ -100,10 +100,14 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess return null; } - return new Spec(injectedInterfaces); + Set clientOnlyModIds = context.modDependenciesCompileRuntimeClient().stream() + .map(FabricModJson::getId) + .collect(Collectors.toSet()); + + return new Spec(injectedInterfaces, clientOnlyModIds); } - public record Spec(List injectedInterfaces) implements MinecraftJarProcessor.Spec { + public record Spec(List injectedInterfaces, Set clientOnlyModIds) implements MinecraftJarProcessor.Spec { } @Override @@ -115,6 +119,10 @@ public abstract class InterfaceInjectionProcessor implements MinecraftJarProcess try (LazyCloseable tinyRemapper = context.createRemapper(MappingsNamespace.INTERMEDIARY, MappingsNamespace.NAMED)) { final List remappedInjectedInterfaces = spec.injectedInterfaces().stream() + .filter(injectedInterface -> { + return context.includesClient() // The client jar depends on the server, so always apply all to it + || !spec.clientOnlyModIds.contains(injectedInterface.modId()); // Or the mod is NOT only found on the client classpath, so we can apply it to the server jar + }) .map(injectedInterface -> remap( injectedInterface, s -> mappings.mapClassName(s, intermediaryIndex, namedIndex), diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactRef.java b/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactRef.java index b5c9ca8d..5a495cf3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactRef.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ArtifactRef.java @@ -69,7 +69,7 @@ public interface ArtifactRef { } public String version() { - return replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.truncatedSha256(artifact.getFile())); + return replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.of(artifact.getFile()).sha256().hex(10)); } public String classifier() { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java index 87fbe05d..ecbf6009 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModConfigurationRemapper.java @@ -38,7 +38,6 @@ import java.util.Map; import java.util.Set; import java.util.function.Supplier; -import com.google.common.collect.ImmutableMap; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.FileCollectionDependency; @@ -58,6 +57,8 @@ import org.gradle.api.tasks.SourceSet; import org.gradle.jvm.JvmLibrary; import org.gradle.language.base.artifact.SourcesArtifact; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; @@ -65,6 +66,7 @@ import net.fabricmc.loom.api.RemapConfigurationSettings; import net.fabricmc.loom.configuration.RemapConfigurations; import net.fabricmc.loom.configuration.mods.dependency.ModDependency; import net.fabricmc.loom.configuration.mods.dependency.ModDependencyFactory; +import net.fabricmc.loom.configuration.mods.dependency.ModDependencyOptions; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftSourceSets; import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; @@ -79,6 +81,8 @@ public class ModConfigurationRemapper { // This can happen when the dependency is a FileCollectionDependency or from a flatDir repository. public static final String MISSING_GROUP = "unspecified"; + private static final Logger LOGGER = LoggerFactory.getLogger(ModConfigurationRemapper.class); + public static void supplyModConfigurations(Project project, ServiceFactory serviceFactory, String mappingsSuffix, LoomGradleExtension extension, SourceRemapper sourceRemapper) { final DependencyHandler dependencies = project.getDependencies(); // The configurations where the source and remapped artifacts go. @@ -98,7 +102,7 @@ public class ModConfigurationRemapper { for (RemapConfigurationSettings entry : remapConfigurationSettings) { // key: true if runtime, false if compile - final Map envToEnabled = ImmutableMap.of( + final Map envToEnabled = Map.of( false, entry.getOnCompileClasspath().get(), true, entry.getOnRuntimeClasspath().get() ); @@ -135,6 +139,15 @@ public class ModConfigurationRemapper { } } + final ModDependencyOptions modDependencyOptions = ModDependencyOptions.create(project, ModDependencyOptions.class, options -> { + options.getMappings().set(mappingsSuffix); + options.getInlineRefmap().set(extension.getMixin().getInlineDependencyRefmaps()); + }); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Mod dependency options: {}", modDependencyOptions.getJson()); + } + // Round 1: Discovery // Go through all the configs to find artifacts to remap and // the installer data. The installer data has to be added before @@ -177,7 +190,7 @@ public class ModConfigurationRemapper { continue; } - final ModDependency modDependency = ModDependencyFactory.create(artifact, artifactMetadata, remappedConfig, clientRemappedConfig, mappingsSuffix, project); + final ModDependency modDependency = ModDependencyFactory.create(artifact, artifactMetadata, remappedConfig, clientRemappedConfig, modDependencyOptions, project); scheduleSourcesRemapping(project, sourceRemapper, modDependency); modDependencies.add(modDependency); } @@ -263,7 +276,7 @@ public class ModConfigurationRemapper { for (File artifact : files) { final String name = getNameWithoutExtension(artifact.toPath()); - final String version = replaceIfNullOrEmpty(dependency.getVersion(), () -> Checksum.truncatedSha256(artifact)); + final String version = replaceIfNullOrEmpty(dependency.getVersion(), () -> Checksum.of(artifact).sha256().hex(10)); artifacts.add(new ArtifactRef.FileArtifactRef(artifact.toPath(), group, name, version)); } } @@ -333,7 +346,7 @@ public class ModConfigurationRemapper { } if (dependency.isCacheInvalid(project, "sources")) { - final Path output = dependency.getWorkingFile("sources"); + final Path output = dependency.getWorkingFile(project, "sources"); sourceRemapper.scheduleRemapSources(sourcesInput.toFile(), output.toFile(), false, true, () -> { try { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index 62722d47..c0728c58 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.jar.Manifest; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -48,15 +49,19 @@ import dev.architectury.loom.util.MappingOption; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.attributes.Usage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.RemapConfigurationSettings; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.build.IntermediaryNamespaces; import net.fabricmc.loom.configuration.mods.dependency.ModDependency; +import net.fabricmc.loom.configuration.mods.extension.ModProcessorExtension; import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration; import net.fabricmc.loom.extension.RemapperExtensionHolder; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.IdentityBiMap; import net.fabricmc.loom.util.LoggerFilter; import net.fabricmc.loom.util.ModPlatform; import net.fabricmc.loom.util.Pair; @@ -73,11 +78,12 @@ import net.fabricmc.tinyremapper.InputTag; import net.fabricmc.tinyremapper.NonClassCopyMode; import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.TinyRemapper; -import net.fabricmc.tinyremapper.extension.mixin.MixinExtension; public class ModProcessor { private static final String toM = MappingsNamespace.NAMED.toString(); + private static final Logger LOGGER = LoggerFactory.getLogger(ModProcessor.class); + private static final Pattern COPY_CONFIGURATION_PATTERN = Pattern.compile("^(.+)Copy[0-9]*$"); private final Project project; @@ -92,7 +98,7 @@ public class ModProcessor { public void processMods(List remapList) throws IOException { try { - project.getLogger().lifecycle(":remapping {} mods from {}", remapList.size(), describeConfiguration(sourceConfiguration)); + LOGGER.info(":remapping {} mods from {}", remapList.size(), describeConfiguration(sourceConfiguration)); remapJars(remapList); } catch (Exception e) { throw new RuntimeException(String.format(Locale.ENGLISH, "Failed to remap %d mods", remapList.size()), e); @@ -192,13 +198,21 @@ public class ModProcessor { builder.extension(kotlinRemapperClassloader.getTinyRemapperExtension()); } - final Set remapMixins = new HashSet<>(); - final boolean requiresStaticMixinRemap = remapList.stream() - .anyMatch(modDependency -> modDependency.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.STATIC); + final IdentityBiMap inputTags = new IdentityBiMap<>(); + final List activeExtensions = ModProcessorExtension.EXTENSIONS.stream() + .filter(e -> remapList.stream().anyMatch(e::appliesTo)) + .toList(); + final ModProcessorExtension.Context context = new ModProcessorExtension.Context(fromM, toM, remapList); - if (requiresStaticMixinRemap) { - // Configure the mixin extension to remap mixins from mod jars that were remapped with the mixin extension. - builder.extension(new MixinExtension(remapMixins::contains)); + for (ModProcessorExtension modProcessorExtension : activeExtensions) { + LOGGER.info("Applying mod processor extension: {}", modProcessorExtension.getClass().getSimpleName()); + + final Predicate applyPredicate = inputTag -> { + ModDependency mod = inputTags.getByKey(inputTag); + return mod != null && modProcessorExtension.appliesTo(mod); + }; + + builder.extension(modProcessorExtension.createExtension(context, applyPredicate)); } for (RemapperExtensionHolder holder : extension.getRemapperExtensions().get()) { @@ -209,14 +223,13 @@ public class ModProcessor { remapper.readClassPath(extension.getMinecraftJars(IntermediaryNamespaces.runtimeIntermediaryNamespace(project)).toArray(Path[]::new)); - final Map tagMap = new HashMap<>(); final Map outputConsumerMap = new HashMap<>(); final Map> accessWidenerMap = new HashMap<>(); for (RemapConfigurationSettings entry : extension.getRemapConfigurations()) { for (File inputFile : entry.getSourceConfiguration().get().getFiles()) { if (remapList.stream().noneMatch(info -> info.getInputFile().toFile().equals(inputFile))) { - project.getLogger().debug("Adding " + inputFile + " onto the remap classpath"); + LOGGER.debug("Adding " + inputFile + " onto the remap classpath"); remapper.readClassPathAsync(inputFile.toPath()); } } @@ -225,23 +238,10 @@ public class ModProcessor { for (ModDependency info : remapList) { InputTag tag = remapper.createInputTag(); - project.getLogger().debug("Adding " + info.getInputFile() + " as a remap input"); - - // Note: this is done at a jar level, not at the level of an individual mixin config. - // If a mod has multiple mixin configs, it's assumed that either all or none of them have refmaps. - if (info.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.STATIC) { - if (!requiresStaticMixinRemap) { - // Should be impossible but stranger things have happened. - throw new IllegalStateException("Was not configured for static remap, but a mod required it?!"); - } - - project.getLogger().info("Remapping mixins in {} statically", info.getInputFile()); - remapMixins.add(tag); - } + LOGGER.debug("Adding " + info.getInputFile() + " as a remap input"); + inputTags.put(tag, info); remapper.readInputsAsync(tag, info.getInputFile()); - tagMap.put(info, tag); - Files.deleteIfExists(getRemappedOutput(info)); } @@ -258,12 +258,12 @@ public class ModProcessor { final AccessWidenerUtils.AccessWidenerData accessWidenerData = AccessWidenerUtils.readAccessWidenerData(dependency.getInputFile(), platform); if (accessWidenerData != null) { - project.getLogger().debug("Remapping access widener in {}", dependency.getInputFile()); + LOGGER.debug("Remapping access widener in {}", dependency.getInputFile()); byte[] remappedAw = AccessWidenerUtils.remapAccessWidener(accessWidenerData.content(), remapper.getEnvironment().getRemapper()); accessWidenerMap.put(dependency, new Pair<>(remappedAw, accessWidenerData.path())); } - remapper.apply(outputConsumer, tagMap.get(dependency)); + remapper.apply(outputConsumer, inputTags.getByValue(dependency)); } catch (Exception e) { throw new RuntimeException("Failed to remap: " + dependency, e); } @@ -288,6 +288,12 @@ public class ModProcessor { ZipUtils.replace(output, accessWidener.right(), accessWidener.left()); } + for (ModProcessorExtension modProcessorExtension : activeExtensions) { + if (modProcessorExtension.appliesTo(dependency)) { + modProcessorExtension.finalise(dependency, output); + } + } + stripNestedJars(output); remapJarManifestEntries(output); @@ -307,8 +313,8 @@ public class ModProcessor { } } - private static Path getRemappedOutput(ModDependency dependency) { - return dependency.getWorkingFile(null); + private Path getRemappedOutput(ModDependency dependency) { + return dependency.getWorkingFile(project, null); } private void remapJarManifestEntries(Path jar) throws IOException { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/LocalMavenHelper.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/LocalMavenHelper.java index c1a81b1a..cf08e968 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/LocalMavenHelper.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/LocalMavenHelper.java @@ -34,7 +34,11 @@ import java.nio.file.StandardCopyOption; import org.jetbrains.annotations.Nullable; -public record LocalMavenHelper(String group, String name, String version, @Nullable String baseClassifier, Path root) { +public record LocalMavenHelper(String group, String name, String version, @Nullable String baseClassifier, Path root, @Nullable String snapshotVersion) { + public LocalMavenHelper(String group, String name, String version, @Nullable String baseClassifier, Path root) { + this(group, name, version, baseClassifier, root, null); + } + public Path copyToMaven(Path artifact, @Nullable String classifier) throws IOException { if (!artifact.getFileName().toString().endsWith(".jar")) { throw new UnsupportedOperationException(); @@ -77,6 +81,13 @@ public record LocalMavenHelper(String group, String name, String version, @Nulla } private Path getDirectory() { + String version = this.version(); + + // When using a specific snapshot version the directory name should be the 1.0.0-SNAPSHOT version + if (this.snapshotVersion() != null) { + version = this.snapshotVersion(); + } + return root.resolve("%s/%s/%s".formatted(group.replace(".", "/"), name, version)); } diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependency.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependency.java index 166de041..e8de5b39 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependency.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependency.java @@ -28,6 +28,8 @@ import java.io.IOException; import java.nio.file.Path; import org.gradle.api.Project; +import org.gradle.api.artifacts.component.ComponentIdentifier; +import org.gradle.api.internal.artifacts.repositories.resolver.MavenUniqueSnapshotComponentIdentifier; import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; @@ -37,23 +39,21 @@ import net.fabricmc.loom.configuration.mods.ArtifactRef; public abstract sealed class ModDependency permits SplitModDependency, SimpleModDependency { private final ArtifactRef artifact; private final ArtifactMetadata metadata; - protected final String group; - protected final String name; - protected final String version; + private final String group; + private final String name; + private final String version; @Nullable - protected final String classifier; - protected final String mappingsSuffix; - protected final Project project; + private final String classifier; + private final ModDependencyOptions options; - public ModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Project project) { + public ModDependency(ArtifactRef artifact, ArtifactMetadata metadata, ModDependencyOptions options) { this.artifact = artifact; this.metadata = metadata; this.group = artifact.group(); this.name = artifact.name(); this.version = artifact.version(); this.classifier = artifact.classifier(); - this.mappingsSuffix = mappingsSuffix; - this.project = project; + this.options = options; } /** @@ -71,10 +71,27 @@ public abstract sealed class ModDependency permits SplitModDependency, SimpleMod */ public abstract void applyToProject(Project project); - protected LocalMavenHelper createMaven(String name) { + /** + * Create a maven helper for the local cache. + * @param type The jar type, e.g "common" or "client" for split dependencies. + */ + protected LocalMavenHelper createMavenHelper(Project project, @Nullable String type) { final LoomGradleExtension extension = LoomGradleExtension.get(project); final Path root = extension.getFiles().getRemappedModCache().toPath(); - return new LocalMavenHelper(getRemappedGroup(), name, this.version, this.classifier, root); + final String fullName = getName() + (type != null ? "-" + type : ""); + return new LocalMavenHelper(getGroup(), fullName, this.version, this.classifier, root, getSnapshotVersion()); + } + + private @Nullable String getSnapshotVersion() { + if (artifact instanceof ArtifactRef.ResolvedArtifactRef resolvedArtifactRef) { + ComponentIdentifier componentIdentifier = resolvedArtifactRef.artifact().getId().getComponentIdentifier(); + + if (componentIdentifier instanceof MavenUniqueSnapshotComponentIdentifier mavenUniqueId) { + return mavenUniqueId.getSnapshotVersion(); + } + } + + return null; } public ArtifactRef getInputArtifact() { @@ -85,26 +102,34 @@ public abstract sealed class ModDependency permits SplitModDependency, SimpleMod return metadata; } - protected String getRemappedGroup() { - return getMappingsPrefix() + "." + group; + protected String getName() { + return "%s-%s".formatted(name, options.getCacheKey()); } - private String getMappingsPrefix() { - return mappingsSuffix.replace(".", "_").replace("-", "_").replace("+", "_"); + protected String getGroup() { + return "remapped.%s".formatted(group); + } + + protected String getVersion() { + return version; } public Path getInputFile() { return artifact.path(); } - public Path getWorkingFile(@Nullable String classifier) { + public Path getWorkingFile(Project project, @Nullable String classifier) { final LoomGradleExtension extension = LoomGradleExtension.get(project); - final String fileName = classifier == null ? String.format("%s-%s-%s.jar", getRemappedGroup(), name, version) - : String.format("%s-%s-%s-%s.jar", getRemappedGroup(), name, version, classifier); + final String fileName = classifier == null ? String.format("%s-%s-%s.jar", getGroup(), getName(), version) + : String.format("%s-%s-%s-%s.jar", getGroup(), getName(), version, classifier); return extension.getFiles().getProjectBuildCache().toPath().resolve("remapped_working").resolve(fileName); } + public ModDependencyOptions getOptions() { + return options; + } + @Override public String toString() { return "ModDependency{" + "group='" + group + '\'' + ", name='" + name + '\'' + ", version='" + version + '\'' + ", classifier='" + classifier + '\'' + '}'; diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyFactory.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyFactory.java index c1909b57..04a03619 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyFactory.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyFactory.java @@ -41,7 +41,7 @@ import net.fabricmc.loom.util.AttributeHelper; public class ModDependencyFactory { private static final String TARGET_ATTRIBUTE_KEY = "loom-target"; - public static ModDependency create(ArtifactRef artifact, ArtifactMetadata metadata, Configuration targetConfig, @Nullable Configuration targetClientConfig, String mappingsSuffix, Project project) { + public static ModDependency create(ArtifactRef artifact, ArtifactMetadata metadata, Configuration targetConfig, @Nullable Configuration targetClientConfig, ModDependencyOptions options, Project project) { if (targetClientConfig != null && LoomGradleExtension.get(project).getSplitModDependencies().get()) { final Optional cachedTarget = readTarget(artifact); JarSplitter.Target target; @@ -54,11 +54,11 @@ public class ModDependencyFactory { } if (target != null) { - return new SplitModDependency(artifact, metadata, mappingsSuffix, targetConfig, targetClientConfig, target, project); + return new SplitModDependency(artifact, metadata, options, targetConfig, targetClientConfig, target, project); } } - return new SimpleModDependency(artifact, metadata, mappingsSuffix, targetConfig, project); + return new SimpleModDependency(artifact, metadata, options, targetConfig, project); } private static Optional readTarget(ArtifactRef artifact) { diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyOptions.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyOptions.java new file mode 100644 index 00000000..c9639716 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/ModDependencyOptions.java @@ -0,0 +1,38 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.mods.dependency; + +import org.gradle.api.provider.Property; + +import net.fabricmc.loom.util.CacheKey; + +/** + * Inputs used to process a mod dependency. The output jar is cached based on these properties. + */ +public abstract class ModDependencyOptions extends CacheKey { + public abstract Property getMappings(); + + public abstract Property getInlineRefmap(); +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SimpleModDependency.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SimpleModDependency.java index 34c82cf3..37078fcc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SimpleModDependency.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SimpleModDependency.java @@ -40,10 +40,10 @@ public final class SimpleModDependency extends ModDependency { private final Configuration targetConfig; private final LocalMavenHelper maven; - public SimpleModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Configuration targetConfig, Project project) { - super(artifact, metadata, mappingsSuffix, project); + public SimpleModDependency(ArtifactRef artifact, ArtifactMetadata metadata, ModDependencyOptions options, Configuration targetConfig, Project project) { + super(artifact, metadata, options); this.targetConfig = Objects.requireNonNull(targetConfig); - this.maven = createMaven(name); + this.maven = createMavenHelper(project, null); } @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SplitModDependency.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SplitModDependency.java index 7f088357..0d01c5d1 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SplitModDependency.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/SplitModDependency.java @@ -48,13 +48,13 @@ public final class SplitModDependency extends ModDependency { @Nullable private final LocalMavenHelper clientMaven; - public SplitModDependency(ArtifactRef artifact, ArtifactMetadata metadata, String mappingsSuffix, Configuration targetCommonConfig, Configuration targetClientConfig, JarSplitter.Target target, Project project) { - super(artifact, metadata, mappingsSuffix, project); + public SplitModDependency(ArtifactRef artifact, ArtifactMetadata metadata, ModDependencyOptions options, Configuration targetCommonConfig, Configuration targetClientConfig, JarSplitter.Target target, Project project) { + super(artifact, metadata, options); this.targetCommonConfig = Objects.requireNonNull(targetCommonConfig); this.targetClientConfig = Objects.requireNonNull(targetClientConfig); this.target = Objects.requireNonNull(target); - this.commonMaven = target.common() ? createMaven(name + "-common") : null; - this.clientMaven = target.client() ? createMaven(name + "-client") : null; + this.commonMaven = target.common() ? createMavenHelper(project, "common") : null; + this.clientMaven = target.client() ? createMavenHelper(project, "client") : null; } @Override @@ -86,8 +86,8 @@ public final class SplitModDependency extends ModDependency { // Split the jar into 2 case SPLIT -> { final String suffix = variant == null ? "" : "-" + variant; - final Path commonTempJar = getWorkingFile("common" + suffix); - final Path clientTempJar = getWorkingFile("client" + suffix); + final Path commonTempJar = getWorkingFile(project, "common" + suffix); + final Path clientTempJar = getWorkingFile(project, "client" + suffix); final JarSplitter splitter = new JarSplitter(path); splitter.split(commonTempJar, clientTempJar); @@ -114,15 +114,16 @@ public final class SplitModDependency extends ModDependency { if (target == JarSplitter.Target.SPLIT) { createModGroup( + project, getCommonMaven().getOutputFile(null), getClientMaven().getOutputFile(null) ); } } - private void createModGroup(Path commonJar, Path clientJar) { + private void createModGroup(Project project, Path commonJar, Path clientJar) { LoomGradleExtension extension = LoomGradleExtension.get(project); - final ModSettings modSettings = extension.getMods().maybeCreate(String.format("%s-%s-%s", getRemappedGroup(), name, version)); + final ModSettings modSettings = extension.getMods().maybeCreate(String.format("%s-%s-%s", getGroup(), getName(), getVersion())); modSettings.getModFiles().from( commonJar.toFile(), clientJar.toFile() diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinReferenceRemapper.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinReferenceRemapper.java new file mode 100644 index 00000000..8479e3a3 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinReferenceRemapper.java @@ -0,0 +1,29 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.mods.dependency.refmap; + +public interface MixinReferenceRemapper { + String remapReference(String mixinClassName, String reference); +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinReferenceRemapperImpl.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinReferenceRemapperImpl.java new file mode 100644 index 00000000..9f296bc4 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinReferenceRemapperImpl.java @@ -0,0 +1,69 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.mods.dependency.refmap; + +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.util.fmj.mixin.MixinRefmap; + +public record MixinReferenceRemapperImpl(Map data) implements MixinReferenceRemapper { + private static final Logger LOGGER = LoggerFactory.getLogger(MixinReferenceRemapperImpl.class); + + public static MixinReferenceRemapper createFromRefmaps(String from, String to, Stream refmaps) { + MixinRefmap.NamespacePair namespaces = new MixinRefmap.NamespacePair(from, to); + + Map data = refmaps + .map(refmap -> refmap.getData(namespaces)) + .filter(Objects::nonNull) + .map(MixinRefmap.MixinMappingData::data) + .flatMap(map -> map.entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, + (existing, replacement) -> { + // TODO we could merge this, but it should never happen in practice + LOGGER.warn("Duplicate mixin reference mapping for {} in refmaps, using the first one", existing); + return existing; + } + )); + + return new MixinReferenceRemapperImpl(data); + } + + @Override + public String remapReference(String mixinClassName, String reference) { + final MixinRefmap.ReferenceMappingData data = data().get(mixinClassName); + + if (data != null) { + return data.remap(reference); + } + + return reference; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinRefmapInliner.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinRefmapInliner.java new file mode 100644 index 00000000..d9d35fd7 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinRefmapInliner.java @@ -0,0 +1,72 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.mods.dependency.refmap; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.configuration.mods.ArtifactMetadata; +import net.fabricmc.loom.configuration.mods.dependency.ModDependency; +import net.fabricmc.loom.util.ExceptionUtil; +import net.fabricmc.loom.util.fmj.FabricModJson; +import net.fabricmc.loom.util.fmj.FabricModJsonFactory; +import net.fabricmc.loom.util.fmj.mixin.MixinConfiguration; + +public class MixinRefmapInliner { + private static final Logger LOGGER = LoggerFactory.getLogger(MixinRefmapInliner.class); + + public static MixinReferenceRemapper createRemapper(String from, String to, List mods) throws IOException { + List mixinConfigurations = new ArrayList<>(); + + for (ModDependency mod : mods) { + if (mod.getMetadata().mixinRemapType() != ArtifactMetadata.MixinRemapType.MIXIN) { + continue; + } + + FabricModJson fabricModJson = FabricModJsonFactory.createFromZipNullable(mod.getInputFile()); + + if (fabricModJson == null) { + LOGGER.warn("Failed to read fabric.mod.json from {}", mod.getInputFile()); + continue; + } + + try { + mixinConfigurations.addAll(MixinConfiguration.fromMod(fabricModJson)); + } catch (IOException e) { + throw ExceptionUtil.createDescriptiveWrapper(IOException::new, "Failed to read mixin configuration from " + mod.getInputFile(), e); + } + } + + return MixinReferenceRemapperImpl.createFromRefmaps(from, to, mixinConfigurations.stream().map(MixinConfiguration::refmap)); + } + + public static void removeRefmap(ModDependency modDependency, Path ouputPath) { + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinRefmapInlinerApplyVisitorProvider.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinRefmapInlinerApplyVisitorProvider.java new file mode 100644 index 00000000..4a680270 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinRefmapInlinerApplyVisitorProvider.java @@ -0,0 +1,60 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.mods.dependency.refmap; + +import java.util.function.Predicate; + +import org.objectweb.asm.ClassVisitor; + +import net.fabricmc.tinyremapper.InputTag; +import net.fabricmc.tinyremapper.TinyRemapper; +import net.fabricmc.tinyremapper.api.TrClass; + +public record MixinRefmapInlinerApplyVisitorProvider( + MixinReferenceRemapper remapper, + // A set of input tags that do NOT need their refmaps inlined + Predicate staticRemappedMixins) implements TinyRemapper.ApplyVisitorProvider, TinyRemapper.Extension { + @Override + public ClassVisitor insertApplyVisitor(TrClass cls, ClassVisitor next) { + return new MixinRefmapInlinerClassVisitor(remapper, next); + } + + @Override + public ClassVisitor insertApplyVisitor(TrClass cls, ClassVisitor next, InputTag[] inputTags) { + for (InputTag tag : inputTags) { + if (staticRemappedMixins.test(tag)) { + // No need to inline the refmaps for this tag, as we know this was originally a statically remapped mixin with no refmap + return next; + } + } + + return new MixinRefmapInlinerClassVisitor(remapper, next); + } + + @Override + public void attach(TinyRemapper.Builder builder) { + builder.extraPreApplyVisitor(this); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinRefmapInlinerClassVisitor.java b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinRefmapInlinerClassVisitor.java new file mode 100644 index 00000000..828773f7 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/dependency/refmap/MixinRefmapInlinerClassVisitor.java @@ -0,0 +1,99 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.mods.dependency.refmap; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +import net.fabricmc.loom.util.Constants; + +public class MixinRefmapInlinerClassVisitor extends ClassVisitor { + private final MixinReferenceRemapper remapper; + + private String className = null; + + public MixinRefmapInlinerClassVisitor(MixinReferenceRemapper remapper, ClassVisitor classVisitor) { + super(Constants.ASM_VERSION, classVisitor); + this.remapper = remapper; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.className = name; + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + AnnotationVisitor annotationVisitor = super.visitAnnotation(descriptor, visible); + return new RefmapInlinerAnnotationVisitor(annotationVisitor); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions); + return new RefmapInlinerMethodVisitor(methodVisitor); + } + + private class RefmapInlinerMethodVisitor extends MethodVisitor { + private RefmapInlinerMethodVisitor(MethodVisitor methodVisitor) { + super(MixinRefmapInlinerClassVisitor.super.api, methodVisitor); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + AnnotationVisitor annotationVisitor = super.visitAnnotation(descriptor, visible); + return new RefmapInlinerAnnotationVisitor(annotationVisitor); + } + } + + private class RefmapInlinerAnnotationVisitor extends AnnotationVisitor { + private RefmapInlinerAnnotationVisitor(AnnotationVisitor annotationVisitor) { + super(MixinRefmapInlinerClassVisitor.super.api, annotationVisitor); + } + + @Override + public void visit(String name, Object value) { + if (value instanceof String strValue) { + value = remapper.remapReference(className, strValue); + } + + super.visit(name, value); + } + + @Override + public AnnotationVisitor visitArray(String name) { + AnnotationVisitor annotationVisitor = super.visitArray(name); + return new RefmapInlinerAnnotationVisitor(annotationVisitor); + } + + @Override + public AnnotationVisitor visitAnnotation(String name, String descriptor) { + AnnotationVisitor annotationVisitor = super.visitAnnotation(name, descriptor); + return new RefmapInlinerAnnotationVisitor(annotationVisitor); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/extension/InlineRefmap.java b/src/main/java/net/fabricmc/loom/configuration/mods/extension/InlineRefmap.java new file mode 100644 index 00000000..5e19a877 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/extension/InlineRefmap.java @@ -0,0 +1,60 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.mods.extension; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Predicate; + +import net.fabricmc.loom.configuration.mods.ArtifactMetadata; +import net.fabricmc.loom.configuration.mods.dependency.ModDependency; +import net.fabricmc.loom.configuration.mods.dependency.refmap.MixinReferenceRemapper; +import net.fabricmc.loom.configuration.mods.dependency.refmap.MixinRefmapInliner; +import net.fabricmc.loom.configuration.mods.dependency.refmap.MixinRefmapInlinerApplyVisitorProvider; +import net.fabricmc.tinyremapper.InputTag; +import net.fabricmc.tinyremapper.TinyRemapper; + +final class InlineRefmap implements ModProcessorExtension { + static final InlineRefmap INSTANCE = new InlineRefmap(); + + private InlineRefmap() { + } + + @Override + public boolean appliesTo(ModDependency modDependency) { + return modDependency.getOptions().getInlineRefmap().get() + && modDependency.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.MIXIN; + } + + @Override + public TinyRemapper.Extension createExtension(Context ctx, Predicate applyPredicate) throws IOException { + MixinReferenceRemapper refmapRemapper = MixinRefmapInliner.createRemapper(ctx.from(), ctx.to(), ctx.mods()); + return new MixinRefmapInlinerApplyVisitorProvider(refmapRemapper, applyPredicate); + } + + @Override + public void finalise(ModDependency modDependency, Path path) throws IOException { + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/extension/MixinRemap.java b/src/main/java/net/fabricmc/loom/configuration/mods/extension/MixinRemap.java new file mode 100644 index 00000000..6284bca9 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/extension/MixinRemap.java @@ -0,0 +1,58 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.mods.extension; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Predicate; + +import net.fabricmc.loom.configuration.mods.ArtifactMetadata; +import net.fabricmc.loom.configuration.mods.dependency.ModDependency; +import net.fabricmc.loom.configuration.mods.dependency.refmap.MixinRefmapInliner; +import net.fabricmc.tinyremapper.InputTag; +import net.fabricmc.tinyremapper.TinyRemapper; +import net.fabricmc.tinyremapper.extension.mixin.MixinExtension; + +final class MixinRemap implements ModProcessorExtension { + static final MixinRemap INSTANCE = new MixinRemap(); + + private MixinRemap() { + } + + @Override + public boolean appliesTo(ModDependency modDependency) { + return modDependency.getMetadata().mixinRemapType() == ArtifactMetadata.MixinRemapType.STATIC; + } + + @Override + public TinyRemapper.Extension createExtension(Context ctx, Predicate applyPredicate) { + return new MixinExtension(applyPredicate); + } + + @Override + public void finalise(ModDependency modDependency, Path path) throws IOException { + MixinRefmapInliner.removeRefmap(modDependency, path); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/extension/ModProcessorExtension.java b/src/main/java/net/fabricmc/loom/configuration/mods/extension/ModProcessorExtension.java new file mode 100644 index 00000000..24da41cc --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/mods/extension/ModProcessorExtension.java @@ -0,0 +1,61 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.mods.extension; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Predicate; + +import net.fabricmc.loom.configuration.mods.dependency.ModDependency; +import net.fabricmc.tinyremapper.InputTag; +import net.fabricmc.tinyremapper.TinyRemapper; + +/** + * An interface to aid with applying mod-specific remapping extensions. + */ +public interface ModProcessorExtension { + List EXTENSIONS = List.of( + MixinRemap.INSTANCE, + InlineRefmap.INSTANCE + ); + + /** + * Return true if the extension applies to the given mod dependency. + */ + boolean appliesTo(ModDependency modDependency); + + /** + * Create a TinyRemapper extension that uses the predicate to only apply to mods that match appliesTo. + */ + TinyRemapper.Extension createExtension(Context ctx, Predicate applyPredicate) throws IOException; + + void finalise(ModDependency modDependency, Path path) throws IOException; + + record Context( + String from, + String to, + List mods) { } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftJarProcessorManager.java b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftJarProcessorManager.java index 5f8809df..ec3eac67 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftJarProcessorManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftJarProcessorManager.java @@ -25,7 +25,6 @@ package net.fabricmc.loom.configuration.processors; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -113,11 +112,11 @@ public final class MinecraftJarProcessorManager { public String getJarHash() { //fabric-loom:mod-javadoc:-1289977000 - return Checksum.sha1Hex(getCacheValue().getBytes(StandardCharsets.UTF_8)).substring(0, 10); + return Checksum.of(getCacheValue()).sha1().hex(10); } public String getSourceMappingsHash() { - return Checksum.sha1Hex(getCacheValue().getBytes(StandardCharsets.UTF_8)); + return Checksum.of(getCacheValue()).sha1().hex(); } public boolean requiresProcessingJar(Path jar) { diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/ModJavadocProcessor.java b/src/main/java/net/fabricmc/loom/configuration/processors/ModJavadocProcessor.java index 781ca37f..2b74dfb2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/ModJavadocProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/ModJavadocProcessor.java @@ -124,7 +124,7 @@ public abstract class ModJavadocProcessor implements MinecraftJarProcessor modDependencies, List localMods, List compileRuntimeMods) implements SpecContext { +public record SpecContextImpl( + List modDependencies, + List localMods, + List compileRuntimeMods) implements SpecContext { public static SpecContextImpl create(Project project) { final Map> fmjCache = new HashMap<>(); - return new SpecContextImpl(getDependentMods(project, fmjCache), FabricModJsonHelpers.getModsInProject(project), getCompileRuntimeMods(project, fmjCache)); + return new SpecContextImpl( + getDependentMods(project, fmjCache), + FabricModJsonHelpers.getModsInProject(project), + getCompileRuntimeMods(project, fmjCache) + ); } // Reruns a list of mods found on both the compile and/or runtime classpaths @@ -108,39 +116,68 @@ public record SpecContextImpl(List modDependencies, List getCompileRuntimeMods(Project project, Map> fmjCache) { - var mods = new ArrayList<>(getCompileRuntimeModsFromRemapConfigs(project, fmjCache).toList()); + private static List getCompileRuntimeMods(Project project, Map> fmjCache) { + var mods = new ArrayList<>(getCompileRuntimeModsFromRemapConfigs(project, fmjCache)); for (Project dependentProject : getCompileRuntimeProjectDependencies(project).toList()) { - mods.addAll(fmjCache.computeIfAbsent(dependentProject.getPath(), $ -> { + List projectMods = fmjCache.computeIfAbsent(dependentProject.getPath(), $ -> { return FabricModJsonHelpers.getModsInProject(dependentProject); - })); + }); + + for (FabricModJson mod : projectMods) { + mods.add(new ModHolder(mod)); + } } return Collections.unmodifiableList(mods); } // Returns a list of jar mods that are found on the compile and runtime remapping configurations - private static Stream getCompileRuntimeModsFromRemapConfigs(Project project, Map> fmjCache) { + private static List getCompileRuntimeModsFromRemapConfigs(Project project, Map> fmjCache) { final LoomGradleExtension extension = LoomGradleExtension.get(project); - final Set runtimeModIds = extension.getRuntimeRemapConfigurations().stream() - .filter(settings -> settings.getApplyDependencyTransforms().get()) - .flatMap(resolveArtifacts(project, true)) - .map(modFromZip(fmjCache)) - .filter(Objects::nonNull) - .map(FabricModJson::getId) - .collect(Collectors.toSet()); - return extension.getCompileRemapConfigurations().stream() - .filter(settings -> settings.getApplyDependencyTransforms().get()) - .flatMap(resolveArtifacts(project, false))// Use the intersection of the two configurations. - .map(modFromZip(fmjCache)) - .filter(Objects::nonNull) + // A set of mod ids from all remap configurations that are considered for dependency transforms. + final Set runtimeModIds = getModIds( + project, + fmjCache, + extension.getRuntimeRemapConfigurations().stream() + .filter(settings -> settings.getApplyDependencyTransforms().get()) + ); + + // A set of mod ids that are found on one or more remap configurations that target the common source set. + // Null when split source sets are not enabled, meaning all mods are common. + final Set commonModIds = extension.areEnvironmentSourceSetsSplit() ? getModIds( + project, + fmjCache, + extension.getRuntimeRemapConfigurations().stream() + .filter(settings -> settings.getSourceSet().map(sourceSet -> !sourceSet.getName().equals(MinecraftSourceSets.Split.CLIENT_ONLY_SOURCE_SET_NAME)).get()) + .filter(settings -> settings.getApplyDependencyTransforms().get())) + : null; + + return getMods( + project, + fmjCache, + extension.getCompileRemapConfigurations().stream() + .filter(settings -> settings.getApplyDependencyTransforms().get())) // Only check based on the modid, as there may be differing versions used between the compile and runtime classpath. // We assume that the version used at runtime will be binary compatible with the version used to compile against. // It's not perfect but better than silently not supplying the mod, and this could happen with regular API that you compile against anyway. .filter(fabricModJson -> runtimeModIds.contains(fabricModJson.getId())) - .sorted(Comparator.comparing(FabricModJson::getId)); + .sorted(Comparator.comparing(FabricModJson::getId)) + .map(fabricModJson -> new ModHolder(fabricModJson, commonModIds == null || commonModIds.contains(fabricModJson.getId()))) + .toList(); + } + + private static Stream getMods(Project project, Map> fmjCache, Stream stream) { + return stream.flatMap(resolveArtifacts(project, true)) + .map(modFromZip(fmjCache)) + .filter(Objects::nonNull); + } + + private static Set getModIds(Project project, Map> fmjCache, Stream stream) { + return getMods(project, fmjCache, stream) + .map(FabricModJson::getId) + .collect(Collectors.toSet()); } private static Function modFromZip(Map> fmjCache) { @@ -190,6 +227,22 @@ public record SpecContextImpl(List modDependencies, List modDependenciesCompileRuntime() { - return compileRuntimeMods; + return compileRuntimeMods.stream() + .map(ModHolder::mod) + .toList(); + } + + @Override + public List modDependenciesCompileRuntimeClient() { + return compileRuntimeMods.stream() + .filter(modHolder -> !modHolder.common()) + .map(ModHolder::mod) + .toList(); + } + + private record ModHolder(FabricModJson mod, boolean common) { + ModHolder(FabricModJson mod) { + this(mod, true); + } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java index 3db4043c..2e5595a2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/GradleMappingContext.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2018-2021 FabricMC + * Copyright (c) 2018-2025 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -41,6 +41,7 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingContext; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.util.download.DownloadBuilder; +import net.fabricmc.loom.util.gradle.GradleUtils; import net.fabricmc.loom.util.service.ScopedServiceFactory; import net.fabricmc.mappingio.tree.MemoryMappingTree; @@ -85,6 +86,11 @@ public class GradleMappingContext implements MappingContext { }; } + @Override + public boolean isUsingIntermediateMappings() { + return !(extension.getIntermediateMappingsProvider() instanceof NoOpIntermediateMappingsProvider); + } + @Override public MinecraftProvider minecraftProvider() { return extension.getMinecraftProvider(); @@ -110,6 +116,11 @@ public class GradleMappingContext implements MappingContext { return extension.refreshDeps(); } + @Override + public boolean hasProperty(String property) { + return GradleUtils.getBooleanProperty(project, property); + } + public Project getProject() { return project; } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryMappingsProvider.java index 9d9ce0e0..f72914e4 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/IntermediaryMappingsProvider.java @@ -25,7 +25,6 @@ package net.fabricmc.loom.configuration.providers.mappings; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -115,7 +114,7 @@ public abstract class IntermediaryMappingsProvider extends IntermediateMappingsP if (!LoomGradleExtensionApiImpl.DEFAULT_INTERMEDIARY_URL.equals(urlRaw)) { final String url = getIntermediaryUrl().get().formatted(encodedMcVersion); - return NAME + "-" + Checksum.sha1Hex(url.getBytes(StandardCharsets.UTF_8)); + return NAME + "-" + Checksum.of(url).sha1().hex(); } return NAME; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsFactory.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsFactory.java index d34c4666..15a59738 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsFactory.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/LayeredMappingsFactory.java @@ -47,6 +47,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.ConfigContext; import net.fabricmc.loom.configuration.mods.dependency.LocalMavenHelper; import net.fabricmc.loom.configuration.providers.mappings.extras.unpick.UnpickLayer; +import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata; import net.fabricmc.loom.configuration.providers.mappings.utils.AddConstructorMappingVisitor; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.mappingio.adapter.MappingDstNsReorder; @@ -148,7 +149,7 @@ public record LayeredMappingsFactory(LayeredMappingSpec spec) { return; } - ZipUtils.add(mappingsFile, "extras/definitions.unpick", unpickData.definitions()); - ZipUtils.add(mappingsFile, "extras/unpick.json", unpickData.metadata().asJson()); + ZipUtils.add(mappingsFile, UnpickMetadata.UNPICK_DEFINITIONS_PATH, unpickData.definitions()); + ZipUtils.add(mappingsFile, UnpickMetadata.UNPICK_METADATA_PATH, unpickData.rawMetadata()); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingConfiguration.java index e19090b3..c0fa8c28 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingConfiguration.java @@ -45,14 +45,12 @@ import java.util.Objects; import com.google.common.base.Stopwatch; import com.google.common.base.Supplier; -import com.google.gson.JsonObject; import dev.architectury.loom.util.MappingOption; import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.provider.Provider; import org.jetbrains.annotations.Nullable; -import org.objectweb.asm.Opcodes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,6 +62,7 @@ import net.fabricmc.loom.configuration.providers.forge.ForgeMigratedMappingConfi import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.mappings.tiny.MappingsMerger; import net.fabricmc.loom.configuration.providers.mappings.tiny.TinyJarInfo; +import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DeletingFileVisitor; @@ -102,7 +101,7 @@ public class MappingConfiguration { private final Map> mappingOptions; private final Path unpickDefinitions; - private boolean hasUnpickDefinitions; + @Nullable private UnpickMetadata unpickMetadata; private Map signatureFixes; @@ -250,15 +249,19 @@ public class MappingConfiguration { } public void applyToProject(Project project, DependencyInfo dependency) throws IOException { - if (hasUnpickDefinitions()) { - String notation = String.format("%s:%s:%s:constants", - dependency.getDependency().getGroup(), - dependency.getDependency().getName(), - dependency.getDependency().getVersion() - ); + if (unpickMetadata != null) { + if (unpickMetadata.hasConstants()) { + String notation = switch (unpickMetadata) { + case UnpickMetadata.V1 v1 -> String.format("%s:%s:%s:constants", + dependency.getDependency().getGroup(), + dependency.getDependency().getName(), + dependency.getDependency().getVersion() + ); + case UnpickMetadata.V2 v2 -> Objects.requireNonNull(v2.constants()); + }; - project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation); - populateUnpickClasspath(project); + project.getDependencies().add(Constants.Configurations.MAPPING_CONSTANTS, notation); + } } LoomGradleExtension extension = LoomGradleExtension.get(project); @@ -441,8 +444,8 @@ public class MappingConfiguration { } private void extractUnpickDefinitions(FileSystem jar) throws IOException { - Path unpickPath = jar.getPath("extras/definitions.unpick"); - Path unpickMetadataPath = jar.getPath("extras/unpick.json"); + Path unpickPath = jar.getPath(UnpickMetadata.UNPICK_DEFINITIONS_PATH); + Path unpickMetadataPath = jar.getPath(UnpickMetadata.UNPICK_METADATA_PATH); if (!Files.exists(unpickPath) || !Files.exists(unpickMetadataPath)) { return; @@ -450,8 +453,7 @@ public class MappingConfiguration { Files.copy(unpickPath, unpickDefinitions, StandardCopyOption.REPLACE_EXISTING); - unpickMetadata = parseUnpickMetadata(unpickMetadataPath); - hasUnpickDefinitions = true; + unpickMetadata = UnpickMetadata.parse(unpickMetadataPath); } private void extractSignatureFixes(FileSystem jar) throws IOException { @@ -467,40 +469,6 @@ public class MappingConfiguration { } } - private UnpickMetadata parseUnpickMetadata(Path input) throws IOException { - JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(Files.readString(input, StandardCharsets.UTF_8), JsonObject.class); - - if (!jsonObject.has("version") || jsonObject.get("version").getAsInt() != 1) { - throw new UnsupportedOperationException("Unsupported unpick version"); - } - - return new UnpickMetadata( - jsonObject.get("unpickGroup").getAsString(), - jsonObject.get("unpickVersion").getAsString() - ); - } - - private void populateUnpickClasspath(Project project) { - String unpickCliName = "unpick-cli"; - project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH, - String.format("%s:%s:%s", unpickMetadata.unpickGroup, unpickCliName, unpickMetadata.unpickVersion) - ); - - // Unpick ships with a slightly older version of asm, ensure it runs with at least the same version as loom. - String[] asmDeps = new String[] { - "org.ow2.asm:asm:%s", - "org.ow2.asm:asm-tree:%s", - "org.ow2.asm:asm-commons:%s", - "org.ow2.asm:asm-util:%s" - }; - - for (String asm : asmDeps) { - project.getDependencies().add(Constants.Configurations.UNPICK_CLASSPATH, - asm.formatted(Opcodes.class.getPackage().getImplementationVersion()) - ); - } - } - private void suggestFieldNames(Path inputJar, Path oldMappings, Path newMappings) { Command command = new CommandProposeFieldNames(); runCommand(command, inputJar.toFile().getAbsolutePath(), @@ -554,7 +522,11 @@ public class MappingConfiguration { } public boolean hasUnpickDefinitions() { - return hasUnpickDefinitions; + return unpickMetadata != null; + } + + public UnpickMetadata getUnpickMetadata() { + return Objects.requireNonNull(unpickMetadata, "Unpick metadata is not available"); } @Nullable @@ -562,10 +534,6 @@ public class MappingConfiguration { return signatureFixes; } - public String getBuildServiceName(String name, String from, String to) { - return "%s:%s:%s>%S".formatted(name, mappingsIdentifier(), from, to); - } - public Path getReplacedTarget(LoomGradleExtension loom, String namespace) { if (namespace.equals("intermediary")) return getPlatformMappingFile(loom); @@ -602,7 +570,4 @@ public class MappingConfiguration { return tinyMappings; } } - - public record UnpickMetadata(String unpickGroup, String unpickVersion) { - } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/extras/unpick/UnpickLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/extras/unpick/UnpickLayer.java index 8b3879ab..1a827d15 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/extras/unpick/UnpickLayer.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/extras/unpick/UnpickLayer.java @@ -25,37 +25,24 @@ package net.fabricmc.loom.configuration.providers.mappings.extras.unpick; import java.io.IOException; -import java.io.Reader; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata; @ApiStatus.Experimental public interface UnpickLayer { @Nullable UnpickData getUnpickData() throws IOException; - record UnpickData(Metadata metadata, byte[] definitions) { + record UnpickData(UnpickMetadata metadata, byte[] rawMetadata, byte[] definitions) { public static UnpickData read(Path metadataPath, Path definitionPath) throws IOException { final byte[] definitions = Files.readAllBytes(definitionPath); - final Metadata metadata; - - try (Reader reader = Files.newBufferedReader(metadataPath, StandardCharsets.UTF_8)) { - metadata = LoomGradlePlugin.GSON.fromJson(reader, Metadata.class); - } - - return new UnpickData(metadata, definitions); - } - - public record Metadata(int version, String unpickGroup, String unpickVersion) { - public String asJson() { - return LoomGradlePlugin.GSON.toJson(this); - } + final byte[] metadata = Files.readAllBytes(metadataPath); + return new UnpickData(UnpickMetadata.parse(metadataPath), metadata, definitions); } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsLayer.java index 2d6dd61a..df8144f7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsLayer.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/file/FileMappingsLayer.java @@ -36,6 +36,7 @@ import net.fabricmc.loom.api.mappings.layered.MappingLayer; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.providers.mappings.extras.unpick.UnpickLayer; import net.fabricmc.loom.configuration.providers.mappings.intermediary.IntermediaryMappingLayer; +import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata; import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.ZipUtils; import net.fabricmc.mappingio.MappingReader; @@ -52,9 +53,6 @@ public record FileMappingsLayer( boolean unpick, String mergeNamespace ) implements MappingLayer, UnpickLayer { - private static final String UNPICK_METADATA_PATH = "extras/unpick.json"; - private static final String UNPICK_DEFINITIONS_PATH = "extras/definitions.unpick"; - @Override public void visit(MappingVisitor mappingVisitor) throws IOException { // Bare file @@ -102,8 +100,8 @@ public record FileMappingsLayer( } try (FileSystemUtil.Delegate fileSystem = FileSystemUtil.getJarFileSystem(path)) { - final Path unpickMetadata = fileSystem.get().getPath(UNPICK_METADATA_PATH); - final Path unpickDefinitions = fileSystem.get().getPath(UNPICK_DEFINITIONS_PATH); + final Path unpickMetadata = fileSystem.get().getPath(UnpickMetadata.UNPICK_METADATA_PATH); + final Path unpickDefinitions = fileSystem.get().getPath(UnpickMetadata.UNPICK_DEFINITIONS_PATH); if (!Files.exists(unpickMetadata)) { // No unpick in this zip diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java index d6ce4893..b090a88c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingLayer.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2021 FabricMC + * Copyright (c) 2021-2025 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -30,9 +30,11 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.function.Supplier; import java.util.regex.Pattern; import org.gradle.api.logging.Logger; +import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.api.mappings.layered.MappingLayer; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; @@ -41,11 +43,14 @@ import net.fabricmc.loom.configuration.providers.mappings.utils.DstNameFilterMap import net.fabricmc.mappingio.MappingVisitor; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; import net.fabricmc.mappingio.format.proguard.ProGuardFileReader; +import net.fabricmc.mappingio.tree.MemoryMappingTree; public record MojangMappingLayer(String minecraftVersion, Path clientMappings, Path serverMappings, boolean nameSyntheticMembers, + boolean dropNoneIntermediaryRoots, + @Nullable Supplier intermediarySupplier, Logger logger, MojangMappingsSpec.SilenceLicenseOption silenceLicense) implements MappingLayer { private static final Pattern SYNTHETIC_NAME_PATTERN = Pattern.compile("^(access|this|val\\$this|lambda\\$.*)\\$[0-9]+$"); @@ -55,12 +60,41 @@ public record MojangMappingLayer(String minecraftVersion, printMappingsLicense(clientMappings); } + if (!dropNoneIntermediaryRoots) { + logger().debug("Not attempting to drop none intermediary roots"); + + readMappings(mappingVisitor); + return; + } + + logger().info("Attempting to drop none intermediary roots"); + + if (intermediarySupplier == null) { + // Using no-op intermediary mappings + readMappings(mappingVisitor); + return; + } + + // Create a mapping tree with src: official dst: named, intermediary + MemoryMappingTree mappingTree = new MemoryMappingTree(); + intermediarySupplier.get().accept(mappingTree); + readMappings(mappingTree); + + // The following code first switches the src namespace to intermediary dropping any entries that don't have an intermediary name + // This removes any none root methods before switching it back to official + var officialSwitch = new MappingSourceNsSwitch(mappingVisitor, getSourceNamespace().toString(), false); + var intermediarySwitch = new MappingSourceNsSwitch(officialSwitch, MappingsNamespace.INTERMEDIARY.toString(), true); + mappingTree.accept(intermediarySwitch); + } + + private void readMappings(MappingVisitor mappingVisitor) throws IOException { // Filter out field names matching the pattern - DstNameFilterMappingVisitor nameFilter = new DstNameFilterMappingVisitor(mappingVisitor, SYNTHETIC_NAME_PATTERN); + var nameFilter = new DstNameFilterMappingVisitor(mappingVisitor, SYNTHETIC_NAME_PATTERN); // Make official the source namespace - MappingSourceNsSwitch nsSwitch = new MappingSourceNsSwitch(nameSyntheticMembers() ? mappingVisitor : nameFilter, MappingsNamespace.OFFICIAL.toString()); + var nsSwitch = new MappingSourceNsSwitch(nameSyntheticMembers() ? mappingVisitor : nameFilter, MappingsNamespace.OFFICIAL.toString()); + // Read both server and client mappings try (BufferedReader clientBufferedReader = Files.newBufferedReader(clientMappings, StandardCharsets.UTF_8); BufferedReader serverBufferedReader = Files.newBufferedReader(serverMappings, StandardCharsets.UTF_8)) { ProGuardFileReader.read(clientBufferedReader, MappingsNamespace.NAMED.toString(), MappingsNamespace.OFFICIAL.toString(), nsSwitch); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java index 37eae9da..47aede94 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/mojmap/MojangMappingsSpec.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2021 FabricMC + * Copyright (c) 2016-2025 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -30,6 +30,7 @@ import java.nio.file.Path; import net.fabricmc.loom.api.mappings.layered.MappingContext; import net.fabricmc.loom.api.mappings.layered.spec.MappingsSpec; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftVersionMeta; +import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.download.DownloadException; public record MojangMappingsSpec(SilenceLicenseOption silenceLicense, boolean nameSyntheticMembers) implements MappingsSpec { @@ -103,6 +104,8 @@ public record MojangMappingsSpec(SilenceLicenseOption silenceLicense, boolean na clientMappings, serverMappings, nameSyntheticMembers(), + context.hasProperty(Constants.Properties.DROP_NON_INTERMEDIATE_ROOT_METHODS), + context.isUsingIntermediateMappings() ? context.intermediaryTree() : null, context.getLogger(), silenceLicense() ); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/unpick/UnpickMetadata.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/unpick/UnpickMetadata.java new file mode 100644 index 00000000..0f632fd7 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/unpick/UnpickMetadata.java @@ -0,0 +1,105 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.providers.mappings.unpick; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.google.gson.JsonObject; +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.LoomGradlePlugin; + +public sealed interface UnpickMetadata permits UnpickMetadata.V1, UnpickMetadata.V2 { + String UNPICK_METADATA_PATH = "extras/unpick.json"; + String UNPICK_DEFINITIONS_PATH = "extras/definitions.unpick"; + + boolean hasConstants(); + + /** + * @param unpickGroup Deprecated, always uses the version of unpick loom depends on. + * @param unpickVersion Deprecated, always uses the version of unpick loom depends on. + */ + record V1(@Deprecated String unpickGroup, @Deprecated String unpickVersion) implements UnpickMetadata { + @Override + public boolean hasConstants() { + return true; + } + } + + /** + * Unpick metadata v2. + * + * @param namespace the mapping namespace of the unpick definitions + * @param constants An optional maven notation of the constants jar. + */ + record V2(String namespace, @Nullable String constants) implements UnpickMetadata { + @Override + public boolean hasConstants() { + return constants != null; + } + } + + static UnpickMetadata parse(Path path) throws IOException { + JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(Files.readString(path, StandardCharsets.UTF_8), JsonObject.class); + + if (!jsonObject.has("version")) { + throw new UnsupportedOperationException("Missing unpick metadata version"); + } + + int version = jsonObject.get("version").getAsInt(); + + switch (version) { + case 1 -> { + return new V1( + getString(jsonObject, "unpickGroup"), + getString(jsonObject, "unpickVersion") + ); + } + case 2 -> { + return new V2( + getString(jsonObject, "namespace"), + getOptionalString(jsonObject, "constants") + ); + } + default -> throw new UnsupportedOperationException("Unsupported unpick metadata version: %s. Please update loom.".formatted(version)); + } + } + + private static String getString(JsonObject jsonObject, String key) { + if (!jsonObject.has(key)) { + throw new UnsupportedOperationException("Missing unpick metadata %s".formatted(key)); + } + + return jsonObject.get(key).getAsString(); + } + + @Nullable + private static String getOptionalString(JsonObject jsonObject, String key) { + return jsonObject.has(key) ? jsonObject.get(key).getAsString() : null; + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/utils/LocalFileSpec.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/utils/LocalFileSpec.java index d870d691..fc7ae177 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/utils/LocalFileSpec.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/utils/LocalFileSpec.java @@ -29,8 +29,8 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.Objects; -import net.fabricmc.loom.api.mappings.layered.spec.FileSpec; import net.fabricmc.loom.api.mappings.layered.MappingContext; +import net.fabricmc.loom.api.mappings.layered.spec.FileSpec; import net.fabricmc.loom.util.Checksum; public class LocalFileSpec implements FileSpec { @@ -48,7 +48,7 @@ public class LocalFileSpec implements FileSpec { } // Use the file hash as part of the spec, this means if the input file changes the mappings will be re-generated. - return Objects.hash(Arrays.hashCode(Checksum.sha256(file)), file.getAbsolutePath()); + return Objects.hash(Arrays.hashCode(Checksum.of(file).sha256().digest()), file.getAbsolutePath()); } @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java index bb67fe6e..5171c81c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MergedMinecraftProvider.java @@ -92,7 +92,6 @@ public class MergedMinecraftProvider extends MinecraftProvider { File minecraftServerJar = getMinecraftServerJar(); if (getServerBundleMetadata() != null) { - extractBundledServerJar(); minecraftServerJar = getMinecraftExtractedServerJar(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java index f0dcb0d2..36eb70a4 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftJarSplitter.java @@ -37,8 +37,6 @@ import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.stream.Stream; -import com.google.common.collect.Sets; - import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.FileSystemUtil; @@ -79,7 +77,7 @@ public class MinecraftJarSplitter implements AutoCloseable { } public static Set getJarEntries(Path input) throws IOException { - Set entries = Sets.newHashSet(); + Set entries = new HashSet<>(); try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(input); Stream walk = Files.walk(fs.get().getPath("/"))) { @@ -154,17 +152,17 @@ public class MinecraftJarSplitter implements AutoCloseable { this.clientEntries = clientEntries; this.serverEntries = serverEntries; - this.commonEntries = Sets.newHashSet(clientEntries); + this.commonEntries = new HashSet<>(clientEntries); this.commonEntries.retainAll(serverEntries); this.commonEntries.addAll(sharedEntries); this.commonEntries.removeAll(forcedClientEntries); - this.clientOnlyEntries = Sets.newHashSet(clientEntries); + this.clientOnlyEntries = new HashSet<>(clientEntries); this.clientOnlyEntries.removeAll(serverEntries); this.clientOnlyEntries.addAll(sharedEntries); this.clientOnlyEntries.addAll(forcedClientEntries); - this.serverOnlyEntries = Sets.newHashSet(serverEntries); + this.serverOnlyEntries = new HashSet<>(serverEntries); this.serverOnlyEntries.removeAll(clientEntries); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java index 1f6c8404..69eeea53 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftLibraryProvider.java @@ -31,6 +31,7 @@ import java.util.List; import org.gradle.api.JavaVersion; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ExternalModuleDependency; import org.gradle.api.artifacts.ModuleDependency; @@ -94,6 +95,10 @@ public class MinecraftLibraryProvider { if (provideServer) { provideServerLibraries(); } + + if (extension.isCollectingDependencyVerificationMetadata()) { + resolveAllLibraries(); + } } private void provideClientLibraries() { @@ -114,6 +119,22 @@ public class MinecraftLibraryProvider { processLibraries.forEach(this::applyServerLibrary); } + /** + * When Gradle is writing dependency verification metadata, we need to resolve all libraries across all platforms, + * to ensure that they are captured. + */ + private void resolveAllLibraries() { + project.getLogger().info("Resolving all libraries for dependency verification metadata generation"); + + final List libraries = MinecraftLibraryHelper.getAllLibraries(minecraftProvider.getVersionInfo()); + Configuration detachedConfiguration = project.getConfigurations().detachedConfiguration( + libraries.stream() + .map(library -> project.getDependencies().create(library.mavenNotation())) + .toArray(Dependency[]::new) + ); + detachedConfiguration.getFiles(); + } + private List processLibraries(List libraries) { final LibraryContext libraryContext = new LibraryContext(minecraftProvider.getVersionInfo(), getTargetRuntimeJavaVersion()); return processorManager.processLibraries(libraries, libraryContext); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java index 6b0f8415..16ad992f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftProvider.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import com.google.common.base.Preconditions; import org.gradle.api.JavaVersion; @@ -41,9 +42,12 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.configuration.ConfigContext; import net.fabricmc.loom.configuration.providers.BundleMetadata; +import net.fabricmc.loom.configuration.providers.minecraft.verify.MinecraftJarVerification; +import net.fabricmc.loom.configuration.providers.minecraft.verify.SignatureVerificationFailure; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.download.DownloadExecutor; import net.fabricmc.loom.util.download.GradleDownloadProgressListener; +import net.fabricmc.loom.util.gradle.GradleUtils; import net.fabricmc.loom.util.gradle.ProgressGroup; public abstract class MinecraftProvider { @@ -93,10 +97,18 @@ public abstract class MinecraftProvider { } } - downloadJars(); + boolean didDownload = downloadJars(); if (provideServer()) { serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath()); + + if (serverBundleMetadata != null) { + extractBundledServerJar(); + } + } + + if (didDownload) { + verifyJars(); } final MinecraftLibraryProvider libraryProvider = new MinecraftLibraryProvider(this, configContext.project()); @@ -114,7 +126,35 @@ public abstract class MinecraftProvider { } } - private void downloadJars() throws IOException { + private void verifyJars() throws IOException, SignatureVerificationFailure { + if (GradleUtils.getBooleanProperty(getProject(), Constants.Properties.DISABLE_MINECRAFT_VERIFICATION)) { + LOGGER.info("Skipping Minecraft jar verification!"); + return; + } + + LOGGER.info("Verifying Minecraft jars"); + + MinecraftJarVerification verification = getProject().getObjects().newInstance(MinecraftJarVerification.class, minecraftVersion()); + + if (provideClient()) { + verification.verifyClientJar(minecraftClientJar.toPath()); + } + + if (provideServer()) { + if (serverBundleMetadata == null) { + verification.verifyServerJar(minecraftServerJar.toPath()); + } else { + verification.verifyServerJar(getMinecraftExtractedServerJar().toPath()); + } + } + + LOGGER.info("Jar verification complete"); + } + + // Returns true when a file was downloaded + private boolean downloadJars() throws IOException { + AtomicBoolean didDownload = new AtomicBoolean(false); + try (ProgressGroup progressGroup = new ProgressGroup(getProject(), "Download Minecraft jars"); DownloadExecutor executor = new DownloadExecutor(2)) { if (provideClient()) { @@ -122,7 +162,12 @@ public abstract class MinecraftProvider { getExtension().download(client.url()) .sha1(client.sha1()) .progress(new GradleDownloadProgressListener("Minecraft client", progressGroup::createProgressLogger)) - .downloadPathAsync(minecraftClientJar.toPath(), executor); + .downloadPathAsync(minecraftClientJar.toPath(), executor) + .thenAccept(downloadResult -> { + if (downloadResult.didDownload()) { + didDownload.set(true); + } + }); } if (provideServer()) { @@ -130,9 +175,22 @@ public abstract class MinecraftProvider { getExtension().download(server.url()) .sha1(server.sha1()) .progress(new GradleDownloadProgressListener("Minecraft server", progressGroup::createProgressLogger)) - .downloadPathAsync(minecraftServerJar.toPath(), executor); + .downloadPathAsync(minecraftServerJar.toPath(), executor) + .thenAccept(downloadResult -> { + if (downloadResult.didDownload()) { + didDownload.set(true); + } + }); } } + + if (didDownload.get()) { + LOGGER.info("Downloaded new Minecraft jars"); + return true; + } + + LOGGER.info("Using cached Minecraft jars"); + return false; } public final void extractBundledServerJar() throws IOException { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java index f58e6e7d..a713ed02 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SingleJarMinecraftProvider.java @@ -143,14 +143,13 @@ public abstract class SingleJarMinecraftProvider extends MinecraftProvider { } @Override - public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception { + public Path getInputJar(SingleJarMinecraftProvider provider) { BundleMetadata serverBundleMetadata = provider.getServerBundleMetadata(); if (serverBundleMetadata == null) { return provider.getMinecraftServerJar().toPath(); } - provider.extractBundledServerJar(); return provider.getMinecraftExtractedServerJar().toPath(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java index d2190142..8b56df43 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/SplitMinecraftProvider.java @@ -74,8 +74,6 @@ public final class SplitMinecraftProvider extends MinecraftProvider { throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar can be split, please use a merged jar setup for this version of minecraft"); } - extractBundledServerJar(); - final Path clientJar = getMinecraftClientJar().toPath(); final Path serverJar = getMinecraftExtractedServerJar().toPath(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/library/MinecraftLibraryHelper.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/library/MinecraftLibraryHelper.java index d17e213d..cb9d44f6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/library/MinecraftLibraryHelper.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/library/MinecraftLibraryHelper.java @@ -27,6 +27,7 @@ package net.fabricmc.loom.configuration.providers.minecraft.library; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -72,6 +73,35 @@ public class MinecraftLibraryHelper { return Collections.unmodifiableList(libraries); } + public static List getAllLibraries(MinecraftVersionMeta versionMeta) { + var libraries = new ArrayList(); + + for (MinecraftVersionMeta.Library library : versionMeta.libraries()) { + if (library.artifact() != null) { + Library mavenLib = Library.fromMaven(library.name(), Library.Target.COMPILE); + + // Versions that have the natives on the classpath, attempt to target them as natives. + if (mavenLib.classifier() != null && mavenLib.classifier().startsWith("natives-")) { + mavenLib = mavenLib.withTarget(Library.Target.NATIVES); + } + + libraries.add(mavenLib); + } + + Map classifiers = library.downloads().classifiers(); + + if (classifiers == null) { + continue; + } + + for (MinecraftVersionMeta.Download download : classifiers.values()) { + libraries.add(downloadToLibrary(download)); + } + } + + return Collections.unmodifiableList(libraries); + } + private static Library downloadToLibrary(MinecraftVersionMeta.Download download) { final String path = download.path(); final Matcher matcher = NATIVES_PATTERN.matcher(path); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/CertificateChain.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/CertificateChain.java new file mode 100644 index 00000000..e0c16f45 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/CertificateChain.java @@ -0,0 +1,194 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.providers.minecraft.verify; + +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.Nullable; + +/** + * A node in the certificate chain. + */ +public interface CertificateChain { + /** + * The certificate itself. + */ + X509Certificate certificate(); + + /** + * The issuer of this certificate, or null if this is a root certificate. + */ + @Nullable CertificateChain issuer(); + + /** + * The children of this certificate, or an empty list if this is a leaf certificate. + */ + List children(); + + /** + * Verify that this certificate chain matches exactly with another one. + * @param other the other certificate chain + */ + void verifyChainMatches(CertificateChain other) throws SignatureVerificationFailure; + + /** + * Recursively visit all certificates in the chain, including this one. + */ + static void visitAll(CertificateChain chain, CertificateConsumer consumer) throws SignatureVerificationFailure { + consumer.accept(chain.certificate()); + + for (CertificateChain child : chain.children()) { + visitAll(child, consumer); + } + } + + /** + * Load certificate chain from the classpath, returning the root certificate. + */ + static CertificateChain getRoot(String name) throws IOException { + try (InputStream is = JarVerifier.class.getClassLoader().getResourceAsStream("certs/" + name + ".cer")) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Collection certificates = cf.generateCertificates(is).stream() + .map(c -> (X509Certificate) c) + .toList(); + return getRoot(certificates); + } catch (CertificateException e) { + throw new RuntimeException("Failed to load certificate: " + name, e); + } + } + + /** + * Takes an unordered collection of certificates and builds a tree structure. + */ + static CertificateChain getRoot(Collection certificates) { + Map certificateNodes = new HashMap<>(); + + for (X509Certificate certificate : certificates) { + Impl node = new Impl(); + node.certificate = certificate; + certificateNodes.put(certificate.getSubjectX500Principal().getName(), node); + } + + for (X509Certificate certificate : certificates) { + String subject = certificate.getSubjectX500Principal().getName(); + String issuer = certificate.getIssuerX500Principal().getName(); + + if (subject.equals(issuer)) { + continue; // self-signed + } + + Impl parent = certificateNodes.get(issuer); + Impl self = certificateNodes.get(subject); + + if (parent == self) { + throw new IllegalStateException("Certificate " + subject + " is its own issuer"); + } + + if (parent == null) { + throw new IllegalStateException("Certificate " + subject + " defines issuer " + issuer + " which is not in the chain"); + } + + parent.children.add(self); + self.issuer = parent; + } + + List roots = certificateNodes.values() + .stream() + .filter(node -> node.issuer == null) + .toList(); + + if (roots.size() != 1) { + throw new IllegalStateException("Expected exactly one root certificate, but found " + roots.size()); + } + + return roots.get(0); + } + + @FunctionalInterface + interface CertificateConsumer { + void accept(X509Certificate certificate) throws SignatureVerificationFailure; + } + + class Impl implements CertificateChain { + X509Certificate certificate; + @Nullable CertificateChain.Impl issuer; + List children = new ArrayList<>(); + + private Impl() { + } + + @Override + public X509Certificate certificate() { + return certificate; + } + + @Override + public @Nullable CertificateChain issuer() { + return issuer; + } + + @Override + public List children() { + return children; + } + + @Override + public void verifyChainMatches(CertificateChain other) throws SignatureVerificationFailure { + if (!this.certificate().equals(other.certificate())) { + throw new SignatureVerificationFailure("Certificate mismatch: " + this + " != " + other); + } + + if (this.children().size() != other.children().size()) { + throw new SignatureVerificationFailure("Certificate mismatch: " + this + " has " + this.children().size() + " children, but " + other + " has " + other.children().size()); + } + + if (this.children.isEmpty()) { + // Fine, leaf certificate + return; + } + + if (this.children.size() != 1) { + // TODO support this, not needed currently + throw new UnsupportedOperationException("Validating Certificate chain with multiple children is not supported"); + } + + this.children.get(0).verifyChainMatches(other.children().get(0)); + } + + @Override + public String toString() { + return certificate.getSubjectX500Principal().getName(); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/CertificateRevocationList.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/CertificateRevocationList.java new file mode 100644 index 00000000..aae616b5 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/CertificateRevocationList.java @@ -0,0 +1,122 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.providers.minecraft.verify; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.cert.CRLException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.gradle.api.Project; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.util.download.DownloadException; + +public record CertificateRevocationList(Collection crls, boolean downloadFailure) { + /** + * Hardcoded CRLs for Mojang's certificate, we don't want to add a large dependency just to parse this each time. + */ + public static final List CSC3_2010 = List.of( + "http://crl.verisign.com/pca3-g5.crl", + "http://crl.verisign.com/pca3.crl", + "http://csc3-2010-crl.verisign.com/CSC3-2010.crl" + ); + + private static final Logger LOGGER = LoggerFactory.getLogger(CertificateRevocationList.class); + + /** + * Attempt to download the CRL from the given URL, if we fail to get it its not the end of the world. + */ + public static CertificateRevocationList create(Project project, List urls) throws IOException { + List crls = new ArrayList<>(); + + boolean downloadFailure = false; + + for (String url : urls) { + try { + crls.add(download(project, url)); + } catch (DownloadException e) { + LOGGER.info("Failed to download CRL from {}: {}", url, e.getMessage()); + LOGGER.info("Loom will not be able to verify the integrity of the minecraft jar signature"); + downloadFailure = true; + } + } + + return new CertificateRevocationList(crls, downloadFailure); + } + + static X509CRL download(Project project, String url) throws IOException { + final LoomGradleExtension extension = LoomGradleExtension.get(project); + final String name = url.substring(url.lastIndexOf('/') + 1); + final Path path = extension.getFiles().getUserCache().toPath() + .resolve("crl") + .resolve(name); + + LOGGER.info("Downloading CRL from {} to {}", url, path); + + extension.download(url) + .allowInsecureProtocol() + .maxAge(Duration.ofDays(7)) // Cache the CRL for a week + .downloadPath(path); + + return parse(path); + } + + static X509CRL parse(Path path) throws IOException { + try (InputStream inStream = Files.newInputStream(path)) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + return (X509CRL) cf.generateCRL(inStream); + } catch (CRLException | CertificateException e) { + throw new RuntimeException(e); + } + } + + /** + * Verify that none of the certs in the chain are revoked. + * @throws SignatureVerificationFailure if the certificate is revoked + */ + public void verify(CertificateChain certificateChain) throws SignatureVerificationFailure { + CertificateChain.visitAll(certificateChain, this::verify); + } + + private void verify(X509Certificate certificate) throws SignatureVerificationFailure { + for (X509CRL crl : crls) { + if (crl.isRevoked(certificate)) { + throw new SignatureVerificationFailure("Certificate " + certificate.getSubjectX500Principal().getName() + " is revoked"); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/JarVerifier.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/JarVerifier.java new file mode 100644 index 00000000..19779031 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/JarVerifier.java @@ -0,0 +1,92 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.providers.minecraft.verify; + +import java.io.IOException; +import java.nio.file.Path; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.util.ZipReprocessorUtil; + +public final class JarVerifier { + private static final Logger LOGGER = LoggerFactory.getLogger(JarVerifier.class); + + private JarVerifier() { + } + + public static void verify(Path jarPath, CertificateChain certificateChain) throws IOException, SignatureVerificationFailure { + Objects.requireNonNull(jarPath, "jarPath"); + Objects.requireNonNull(certificateChain, "certificateChain"); + + if (certificateChain.issuer() != null) { + throw new IllegalStateException("Can only verify jars from a root certificate"); + } + + Set jarCertificates = new HashSet<>(); + + try (JarFile jarFile = new JarFile(jarPath.toFile(), true)) { + for (JarEntry jarEntry : Collections.list(jarFile.entries())) { + if (ZipReprocessorUtil.isSpecialFile(jarEntry.getName()) + || jarEntry.getName().equals("META-INF/MANIFEST.MF") + || jarEntry.isDirectory()) { + continue; + } + + try { + // Must read the entire entry to trigger the signature verification + byte[] bytes = jarFile.getInputStream(jarEntry).readAllBytes(); + } catch (SecurityException e) { + throw new SignatureVerificationFailure("Jar entry " + jarEntry.getName() + " failed signature verification", e); + } + + Certificate[] entryCertificates = jarEntry.getCertificates(); + + if (entryCertificates == null) { + throw new SignatureVerificationFailure("Jar entry " + jarEntry.getName() + " does not have a signature"); + } + + Arrays.stream(entryCertificates) + .map(c -> (X509Certificate) c) + .forEach(jarCertificates::add); + } + } + + CertificateChain jarCertificateChain = CertificateChain.getRoot(jarCertificates); + + jarCertificateChain.verifyChainMatches(certificateChain); + LOGGER.debug("Jar {} is signed by the expected certificate", jarPath); + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/KnownVersions.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/KnownVersions.java new file mode 100644 index 00000000..f585a956 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/KnownVersions.java @@ -0,0 +1,57 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.providers.minecraft.verify; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.util.Map; +import java.util.Objects; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; + +import net.fabricmc.loom.LoomGradlePlugin; + +/** + * The know versions keep track of the versions that are signed using SHA1 or not signature at all. + * The maps are the Minecraft version to sha256 hash of the jar file. + */ +public record KnownVersions( + Map client, + Map server) { + public static final Supplier INSTANCE = Suppliers.memoize(KnownVersions::load); + + private static KnownVersions load() { + try (InputStream is = KnownVersions.class.getClassLoader().getResourceAsStream("certs/known_versions.json"); + Reader reader = new InputStreamReader(Objects.requireNonNull(is))) { + return LoomGradlePlugin.GSON.fromJson(reader, KnownVersions.class); + } catch (IOException e) { + throw new UncheckedIOException("Failed to load known versions", e); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/MinecraftJarVerification.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/MinecraftJarVerification.java new file mode 100644 index 00000000..0a7253ca --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/MinecraftJarVerification.java @@ -0,0 +1,112 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.providers.minecraft.verify; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Function; + +import javax.inject.Inject; + +import org.gradle.api.Project; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.util.Checksum; + +public abstract class MinecraftJarVerification { + private static final Logger LOGGER = LoggerFactory.getLogger(MinecraftJarVerification.class); + + private final String minecraftVersion; + + @Inject + protected abstract Project getProject(); + + @Inject + public MinecraftJarVerification(String minecraftVersion) { + this.minecraftVersion = minecraftVersion; + } + + public void verifyClientJar(Path path) throws IOException, SignatureVerificationFailure { + verifyJarSignature(path, KnownJarType.CLIENT); + } + + public void verifyServerJar(Path path) throws IOException, SignatureVerificationFailure { + verifyJarSignature(path, KnownJarType.SERVER); + } + + private void verifyJarSignature(Path path, KnownJarType type) throws IOException, SignatureVerificationFailure { + CertificateChain chain = CertificateChain.getRoot("mojangcs"); + CertificateRevocationList revocationList = CertificateRevocationList.create(getProject(), CertificateRevocationList.CSC3_2010); + + try { + revocationList.verify(chain); + JarVerifier.verify(path, chain); + } catch (SignatureVerificationFailure e) { + if (isValidKnownVersion(path, minecraftVersion, type)) { + LOGGER.info("Minecraft {} signature verification failed, but is a known version", path.getFileName()); + return; + } + + LOGGER.error("Verification of Minecraft {} signature failed: {}", path.getFileName(), e.getMessage()); + throw e; + } + } + + private boolean isValidKnownVersion(Path path, String version, KnownJarType type) throws IOException, SignatureVerificationFailure { + Map knownVersions = type.getKnownVersions(); + String expectedHash = knownVersions.get(version); + + if (expectedHash == null) { + return false; + } + + LOGGER.info("Found executed hash ({}) for known version: {}", expectedHash, version); + Checksum.Result hash = Checksum.of(path).sha256(); + + if (hash.matchesStr(expectedHash)) { + LOGGER.info("Minecraft {} hash matches known version", path.getFileName()); + return true; + } + + throw new SignatureVerificationFailure("Hash mismatch for known Minecraft version " + version + ": expected " + expectedHash + ", got " + hash); + } + + private enum KnownJarType { + CLIENT(KnownVersions::client), + SERVER(KnownVersions::server),; + + private final Function> knownVersions; + + KnownJarType(Function> knownVersions) { + this.knownVersions = knownVersions; + } + + private Map getKnownVersions() { + return knownVersions.apply(KnownVersions.INSTANCE.get()); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/SignatureVerificationFailure.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/SignatureVerificationFailure.java new file mode 100644 index 00000000..03b368c0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/verify/SignatureVerificationFailure.java @@ -0,0 +1,35 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.configuration.providers.minecraft.verify; + +public final class SignatureVerificationFailure extends Exception { + public SignatureVerificationFailure(String message) { + super(message); + } + + public SignatureVerificationFailure(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java b/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java index 4fe53faa..7c8e3b9f 100644 --- a/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java +++ b/src/main/java/net/fabricmc/loom/decompilers/cache/ClassEntry.java @@ -105,13 +105,13 @@ public record ClassEntry(String name, List innerClasses, List su public String hash(Path root) throws IOException { StringJoiner joiner = new StringJoiner(","); - joiner.add(Checksum.sha256Hex(Files.readAllBytes(root.resolve(name)))); + joiner.add(Checksum.of(root.resolve(name)).sha256().hex()); for (String innerClass : innerClasses) { - joiner.add(Checksum.sha256Hex(Files.readAllBytes(root.resolve(innerClass)))); + joiner.add(Checksum.of(root.resolve(innerClass)).sha256().hex()); } - return Checksum.sha256Hex(joiner.toString().getBytes()); + return Checksum.of(joiner.toString()).sha256().hex(); } /** @@ -138,7 +138,7 @@ public record ClassEntry(String name, List innerClasses, List su } } - return Checksum.sha256Hex(joiner.toString().getBytes()); + return Checksum.of(joiner.toString()).sha256().hex(); } public String sourcesFileName() { diff --git a/src/main/java/net/fabricmc/loom/extension/LoomFiles.java b/src/main/java/net/fabricmc/loom/extension/LoomFiles.java index cd4fab5b..d75a6474 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomFiles.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomFiles.java @@ -46,7 +46,6 @@ public interface LoomFiles { File getNativesDirectory(Project project); File getDefaultLog4jConfigFile(); File getDevLauncherConfig(); - File getUnpickLoggingConfigFile(); File getRemapClasspathFile(); File getGlobalMinecraftRepo(); File getLocalMinecraftRepo(); diff --git a/src/main/java/net/fabricmc/loom/extension/LoomFilesBaseImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomFilesBaseImpl.java index b1d7fbcc..c3847278 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomFilesBaseImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomFilesBaseImpl.java @@ -84,11 +84,6 @@ public abstract class LoomFilesBaseImpl implements LoomFiles { return new File(getProjectPersistentCache(), "launch.cfg"); } - @Override - public File getUnpickLoggingConfigFile() { - return new File(getProjectPersistentCache(), "unpick-logging.properties"); - } - @Override public File getRemapClasspathFile() { return new File(getProjectPersistentCache(), "remapClasspath.txt"); diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java index ed76464e..3974279a 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionImpl.java @@ -88,6 +88,7 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl private final ListProperty libraryProcessorFactories; private final boolean configurationCacheActive; private final boolean isolatedProjectsActive; + private final boolean isCollectingDependencyVerificationMetadata; // +-------------------+ // | Architectury Loom | @@ -127,6 +128,7 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl configurationCacheActive = getBuildFeatures().getConfigurationCache().getActive().get(); isolatedProjectsActive = getBuildFeatures().getIsolatedProjects().getActive().get(); + isCollectingDependencyVerificationMetadata = !project.getGradle().getStartParameter().getWriteDependencyVerifications().isEmpty(); if (refreshDeps) { project.getLogger().lifecycle("Refresh dependencies is in use, loom will be significantly slower."); @@ -348,6 +350,11 @@ public abstract class LoomGradleExtensionImpl extends LoomGradleExtensionApiImpl return isolatedProjectsActive; } + @Override + public boolean isCollectingDependencyVerificationMetadata() { + return isCollectingDependencyVerificationMetadata; + } + @Override public ForgeExtensionAPI getForge() { ModPlatform.assertPlatform(this, ModPlatform.FORGE); diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtension.java b/src/main/java/net/fabricmc/loom/extension/MixinExtension.java index e7c428fe..fc9a2747 100644 --- a/src/main/java/net/fabricmc/loom/extension/MixinExtension.java +++ b/src/main/java/net/fabricmc/loom/extension/MixinExtension.java @@ -33,6 +33,7 @@ import org.gradle.api.InvalidUserDataException; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.plugins.ExtraPropertiesExtension; +import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.SourceSet; @@ -89,4 +90,6 @@ public interface MixinExtension extends MixinExtensionAPI { Collection getMixinSourceSets(); void init(); + + Property getInlineDependencyRefmaps(); } diff --git a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java index 5a2b0601..54222d22 100644 --- a/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/MixinExtensionImpl.java @@ -46,11 +46,13 @@ import org.gradle.api.tasks.Input; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.util.PatternSet; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinExtension { private boolean isDefault; private final Property defaultRefmapName; + private final Property inlineDependencyRefmaps; @Inject public MixinExtensionImpl(Project project) { @@ -59,6 +61,9 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx this.defaultRefmapName = project.getObjects().property(String.class) .convention(project.provider(this::getDefaultMixinRefmapName)); this.defaultRefmapName.finalizeValueOnRead(); + this.inlineDependencyRefmaps = project.getObjects().property(Boolean.class) + .convention(false); + this.inlineDependencyRefmaps.finalizeValueOnRead(); } @Override @@ -146,4 +151,10 @@ public class MixinExtensionImpl extends MixinExtensionApiImpl implements MixinEx } }); } + + @ApiStatus.Experimental + @Override + public Property getInlineDependencyRefmaps() { + return inlineDependencyRefmaps; + } } diff --git a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java index 6276a059..2ff68033 100644 --- a/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java +++ b/src/main/java/net/fabricmc/loom/task/AbstractRemapJarTask.java @@ -95,6 +95,14 @@ public abstract class AbstractRemapJarTask extends Jar { @Optional public abstract Property getClientOnlySourceSetName(); + /** + * Optionally supply a single mapping file or jar file containing mappings to be used for remapping. + */ + @ApiStatus.Experimental + @InputFiles + @Optional + public abstract ConfigurableFileCollection getCustomMappings(); + @Input @Optional @ApiStatus.Internal diff --git a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java index 036b8e77..14fe352c 100644 --- a/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java +++ b/src/main/java/net/fabricmc/loom/task/GenerateSourcesTask.java @@ -28,7 +28,6 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.UncheckedIOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -37,9 +36,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.time.Duration; -import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -59,7 +56,6 @@ import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.Property; import org.gradle.api.services.ServiceReference; import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; @@ -70,7 +66,6 @@ import org.gradle.api.tasks.UntrackedTask; import org.gradle.api.tasks.options.Option; import org.gradle.internal.logging.progress.ProgressLoggerFactory; import org.gradle.process.ExecOperations; -import org.gradle.process.ExecResult; import org.gradle.workers.WorkAction; import org.gradle.workers.WorkParameters; import org.gradle.workers.WorkQueue; @@ -91,6 +86,7 @@ import net.fabricmc.loom.decompilers.cache.CachedData; import net.fabricmc.loom.decompilers.cache.CachedFileStoreImpl; import net.fabricmc.loom.decompilers.cache.CachedJarProcessor; import net.fabricmc.loom.task.service.SourceMappingsService; +import net.fabricmc.loom.task.service.UnpickService; import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.ExceptionUtil; @@ -106,6 +102,7 @@ import net.fabricmc.loom.util.gradle.daemon.DaemonUtils; import net.fabricmc.loom.util.ipc.IPCClient; import net.fabricmc.loom.util.ipc.IPCServer; import net.fabricmc.loom.util.service.ScopedServiceFactory; +import net.fabricmc.loom.util.service.ServiceFactory; import net.fabricmc.mappingio.tree.MemoryMappingTree; @UntrackedTask(because = "Manually invoked, has internal caching") @@ -135,31 +132,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @OutputFile protected abstract ConfigurableFileCollection getClassesOutputJar(); // Single jar - // Unpick - @InputFile - @Optional - public abstract RegularFileProperty getUnpickDefinitions(); - - @InputFiles - @Optional - public abstract ConfigurableFileCollection getUnpickConstantJar(); - - @InputFiles - @Optional - public abstract ConfigurableFileCollection getUnpickClasspath(); - - @InputFiles - @Optional - @ApiStatus.Internal - public abstract ConfigurableFileCollection getUnpickRuntimeClasspath(); - - @OutputFile - @Optional - public abstract RegularFileProperty getUnpickOutputJar(); - - @OutputFile - protected abstract RegularFileProperty getUnpickLogConfig(); - @Input @Option(option = "use-cache", description = "Use the decompile cache") @ApiStatus.Experimental @@ -204,6 +176,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { @Nested protected abstract Property getDaemonUtilsContext(); + @Nested + @Optional + protected abstract Property getUnpickOptions(); + // Prevent Gradle from running two gen sources tasks in parallel @ServiceReference(SyncTaskBuildService.NAME) abstract Property getSyncTask(); @@ -246,8 +222,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { getMinecraftCompileLibraries().from(getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES)); getDecompileCacheFile().set(getExtension().getFiles().getDecompileCache(CACHE_VERSION)); - getUnpickRuntimeClasspath().from(getProject().getConfigurations().getByName(Constants.Configurations.UNPICK_CLASSPATH)); - getUnpickLogConfig().set(getExtension().getFiles().getUnpickLoggingConfigFile()); getUseCache().convention(true); getResetCache().convention(getExtension().refreshDeps()); @@ -259,6 +233,8 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { getDaemonUtilsContext().set(getProject().getObjects().newInstance(DaemonUtils.Context.class, getProject())); + getUnpickOptions().set(UnpickService.createOptions(this)); + mustRunAfter(getProject().getTasks().withType(AbstractRemapJarTask.class)); } @@ -270,57 +246,59 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { throw new UnsupportedOperationException("GenSources task requires a 64bit JVM to run due to the memory requirements."); } - if (!getUseCache().get()) { - getLogger().info("Not using decompile cache."); + try (ScopedServiceFactory serviceFactory = new ScopedServiceFactory()) { + if (!getUseCache().get()) { + getLogger().info("Not using decompile cache."); - try (var timer = new Timer("Decompiled sources")) { - runWithoutCache(); + try (var timer = new Timer("Decompiled sources")) { + runWithoutCache(serviceFactory); + } catch (Exception e) { + ExceptionUtil.processException(e, getDaemonUtilsContext().get()); + throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e); + } + + return; + } + + getLogger().info("Using decompile cache."); + + try (var timer = new Timer("Decompiled sources with cache")) { + final Path cacheFile = getDecompileCacheFile().getAsFile().get().toPath(); + + if (getResetCache().get()) { + getLogger().warn("Resetting decompile cache"); + Files.deleteIfExists(cacheFile); + } + + // TODO ensure we have a lock on this file to prevent multiple tasks from running at the same time + Files.createDirectories(cacheFile.getParent()); + + if (Files.exists(cacheFile)) { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) { + // Success, cache exists and can be read + } catch (IOException e) { + getLogger().warn("Discarding invalid decompile cache file: {}", cacheFile, e); + Files.delete(cacheFile); + } + } + + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) { + runWithCache(serviceFactory, fs.getRoot()); + } } catch (Exception e) { ExceptionUtil.processException(e, getDaemonUtilsContext().get()); throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e); } - - return; - } - - getLogger().info("Using decompile cache."); - - try (var timer = new Timer("Decompiled sources with cache")) { - final Path cacheFile = getDecompileCacheFile().getAsFile().get().toPath(); - - if (getResetCache().get()) { - getLogger().warn("Resetting decompile cache"); - Files.deleteIfExists(cacheFile); - } - - // TODO ensure we have a lock on this file to prevent multiple tasks from running at the same time - Files.createDirectories(cacheFile.getParent()); - - if (Files.exists(cacheFile)) { - try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) { - // Success, cache exists and can be read - } catch (IOException e) { - getLogger().warn("Discarding invalid decompile cache file: {}", cacheFile, e); - Files.delete(cacheFile); - } - } - - try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(cacheFile, true)) { - runWithCache(fs.getRoot()); - } - } catch (Exception e) { - ExceptionUtil.processException(e, getDaemonUtilsContext().get()); - throw ExceptionUtil.createDescriptiveWrapper(RuntimeException::new, "Failed to decompile", e); } } - private void runWithCache(Path cacheRoot) throws IOException { + private void runWithCache(ServiceFactory serviceFactory, Path cacheRoot) throws IOException { final Path classesInputJar = getClassesInputJar().getSingleFile().toPath(); final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath(); final Path classesOutputJar = getClassesOutputJar().getSingleFile().toPath(); final var cacheRules = new CachedFileStoreImpl.CacheRules(getMaxCachedFiles().get(), Duration.ofDays(getMaxCacheFileAge().get())); final var decompileCache = new CachedFileStoreImpl<>(cacheRoot, CachedData.SERIALIZER, cacheRules); - final String cacheKey = getCacheKey(); + final String cacheKey = getCacheKey(serviceFactory); final CachedJarProcessor cachedJarProcessor = new CachedJarProcessor(decompileCache, cacheKey); final CachedJarProcessor.WorkRequest workRequest; @@ -342,9 +320,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { Path workInputJar = workToDoJob.incomplete(); @Nullable Path existingClasses = (job instanceof CachedJarProcessor.PartialWorkJob partialWorkJob) ? partialWorkJob.existingClasses() : null; - if (getUnpickDefinitions().isPresent()) { + if (usingUnpick()) { try (var timer = new Timer("Unpick")) { - workInputJar = unpickJar(workInputJar, existingClasses); + UnpickService unpick = serviceFactory.get(getUnpickOptions()); + workInputJar = unpick.unpickJar(workInputJar, existingClasses); } } @@ -381,16 +360,17 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } } - private void runWithoutCache() throws IOException { + private void runWithoutCache(ServiceFactory serviceFactory) throws IOException { final Path classesInputJar = getClassesInputJar().getSingleFile().toPath(); final Path sourcesOutputJar = getSourcesOutputJar().get().getAsFile().toPath(); final Path classesOutputJar = getClassesOutputJar().getSingleFile().toPath(); Path workClassesJar = classesInputJar; - if (getUnpickDefinitions().isPresent()) { + if (usingUnpick()) { try (var timer = new Timer("Unpick")) { - workClassesJar = unpickJar(workClassesJar, null); + UnpickService unpick = serviceFactory.get(getUnpickOptions()); + workClassesJar = unpick.unpickJar(workClassesJar, null); } } @@ -427,24 +407,24 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { Files.move(tempJar, classesOutputJar, StandardCopyOption.REPLACE_EXISTING); } - private String getCacheKey() { + private String getCacheKey(ServiceFactory serviceFactory) { var sj = new StringJoiner(","); sj.add(getDecompilerCheckKey()); - sj.add(getUnpickCacheKey()); + + if (usingUnpick()) { + UnpickService unpick = serviceFactory.get(getUnpickOptions()); + sj.add(unpick.getUnpickCacheKey()); + } getLogger().info("Decompile cache data: {}", sj); - try { - return Checksum.sha256Hex(sj.toString().getBytes(StandardCharsets.UTF_8)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + return Checksum.of(sj.toString()).sha256().hex(); } private String getDecompilerCheckKey() { var sj = new StringJoiner(","); sj.add(decompilerOptions.getDecompilerClassName().get()); - sj.add(fileCollectionHash(decompilerOptions.getClasspath())); + sj.add(Checksum.of(decompilerOptions.getClasspath()).sha256().hex()); for (Map.Entry entry : decompilerOptions.getOptions().get().entrySet()) { sj.add(entry.getKey() + "=" + entry.getValue()); @@ -453,19 +433,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { return sj.toString(); } - private String getUnpickCacheKey() { - if (!getUnpickDefinitions().isPresent()) { - return ""; - } - - var sj = new StringJoiner(","); - sj.add(fileHash(getUnpickDefinitions().getAsFile().get())); - sj.add(fileCollectionHash(getUnpickConstantJar())); - sj.add(fileCollectionHash(getUnpickRuntimeClasspath())); - - return sj.toString(); - } - @Nullable private ClassLineNumbers runDecompileJob(Path inputJar, Path outputJar, @Nullable Path existingJar) throws IOException { final Platform platform = Platform.CURRENT; @@ -565,55 +532,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } } - private Path unpickJar(Path inputJar, @Nullable Path existingClasses) { - final Path outputJar = getUnpickOutputJar().get().getAsFile().toPath(); - final List args = getUnpickArgs(inputJar, outputJar, existingClasses); - - ExecResult result = getExecOperations().javaexec(spec -> { - spec.getMainClass().set("daomephsta.unpick.cli.Main"); - spec.classpath(getUnpickRuntimeClasspath()); - spec.args(args); - spec.systemProperty("java.util.logging.config.file", writeUnpickLogConfig().getAbsolutePath()); - }); - - result.rethrowFailure(); - - return outputJar; - } - - private List getUnpickArgs(Path inputJar, Path outputJar, @Nullable Path existingClasses) { - var fileArgs = new ArrayList(); - - fileArgs.add(inputJar.toFile()); - fileArgs.add(outputJar.toFile()); - fileArgs.add(getUnpickDefinitions().get().getAsFile()); - fileArgs.add(getUnpickConstantJar().getSingleFile()); - - for (File file : getUnpickClasspath()) { - fileArgs.add(file); - } - - if (existingClasses != null) { - fileArgs.add(existingClasses.toFile()); - } - - return fileArgs.stream() - .map(File::getAbsolutePath) - .toList(); - } - - private File writeUnpickLogConfig() { - final File unpickLoggingConfigFile = getUnpickLogConfig().getAsFile().get(); - - try (InputStream is = GenerateSourcesTask.class.getClassLoader().getResourceAsStream("unpick-logging.properties")) { - Files.copy(Objects.requireNonNull(is), unpickLoggingConfigFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new UncheckedIOException("Failed to copy unpick logging config", e); - } - - return unpickLoggingConfigFile; - } - private void remapLineNumbers(ClassLineNumbers lineNumbers, Path inputJar, Path outputJar) throws IOException { Objects.requireNonNull(lineNumbers, "lineNumbers"); final var remapper = new LineNumberRemapper(lineNumbers); @@ -689,6 +607,10 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { return !Boolean.getBoolean("fabric.loom.genSources.debug"); } + private boolean usingUnpick() { + return getUnpickOptions().isPresent(); + } + public interface DecompileParams extends WorkParameters { Property getDecompilerOptions(); @@ -816,26 +738,6 @@ public abstract class GenerateSourcesTask extends AbstractLoomTask { } } - private static String fileHash(File file) { - try { - return Checksum.sha256Hex(Files.readAllBytes(file.toPath())); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static String fileCollectionHash(FileCollection files) { - var sj = new StringJoiner(","); - - files.getFiles() - .stream() - .sorted(Comparator.comparing(File::getAbsolutePath)) - .map(GenerateSourcesTask::fileHash) - .forEach(sj::add); - - return sj.toString(); - } - public interface MappingsProcessor { boolean transform(MemoryMappingTree mappings); } diff --git a/src/main/java/net/fabricmc/loom/task/LoomTasks.java b/src/main/java/net/fabricmc/loom/task/LoomTasks.java index 97d4df71..8fd96545 100644 --- a/src/main/java/net/fabricmc/loom/task/LoomTasks.java +++ b/src/main/java/net/fabricmc/loom/task/LoomTasks.java @@ -24,13 +24,18 @@ package net.fabricmc.loom.task; +import java.io.File; + import javax.inject.Inject; import com.google.common.base.Preconditions; import org.gradle.api.Project; import org.gradle.api.Task; +import org.gradle.api.file.FileCollection; import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Sync; import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.TaskOutputs; import org.gradle.api.tasks.TaskProvider; import net.fabricmc.loom.LoomGradleExtension; @@ -41,6 +46,8 @@ import net.fabricmc.loom.task.launch.GenerateDLIConfigTask; import net.fabricmc.loom.task.launch.GenerateLog4jConfigTask; import net.fabricmc.loom.task.launch.GenerateRemapClasspathTask; import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.LoomVersions; +import net.fabricmc.loom.util.Platform; import net.fabricmc.loom.util.gradle.GradleUtils; public abstract class LoomTasks implements Runnable { @@ -132,17 +139,28 @@ public abstract class LoomTasks implements Runnable { private void registerRunTasks() { LoomGradleExtension extension = LoomGradleExtension.get(getProject()); + final boolean renderDocSupported = RenderDocRunTask.isSupported(Platform.CURRENT); Preconditions.checkArgument(extension.getRunConfigs().size() == 0, "Run configurations must not be registered before loom"); extension.getRunConfigs().whenObjectAdded(config -> { - getTasks().register(getRunConfigTaskName(config), RunGameTask.class, config).configure(t -> { + var runTask = getTasks().register(getRunConfigTaskName(config), RunGameTask.class, config); + + runTask.configure(t -> { t.setDescription("Starts the '" + config.getConfigName() + "' run configuration"); t.dependsOn(config.getEnvironment().equals("client") ? "configureClientLaunch" : "configureLaunch"); }); + + if (config.getName().equals("client") && renderDocSupported) { + getTasks().register("runClientRenderDoc", RenderDocRunTask.class, config); + } }); + if (renderDocSupported) { + configureRenderDocTasks(); + } + extension.getRunConfigs().whenObjectRemoved(runConfigSettings -> { getTasks().named(getRunConfigTaskName(runConfigSettings), task -> { // Disable the task so it can't be run @@ -170,7 +188,58 @@ public abstract class LoomTasks implements Runnable { return; } - extension.getRunConfigs().removeIf(settings -> settings.getName().equals(taskName)); + extension.getRunConfigs().removeIf(settings -> settings.getName().equals(taskName) + || settings.getName().equals(taskName + "RenderDoc")); + }); + } + + private void configureRenderDocTasks() { + final Platform.OperatingSystem operatingSystem = Platform.CURRENT.getOperatingSystem(); + final String renderDocVersion = LoomVersions.RENDERDOC.version(); + final String renderDocBaseName = operatingSystem.isWindows() + ? "RenderDoc_%s_64".formatted(renderDocVersion) + : "renderdoc_%s".formatted(renderDocVersion); + final String renderDocFilename = operatingSystem.isWindows() + ? "%s.zip".formatted(renderDocBaseName) + : "%s.tar.gz".formatted(renderDocBaseName); + final String renderDocUrl = "https://maven.fabricmc.net/org/renderdoc/%s".formatted(renderDocFilename); + final String executableExt = operatingSystem.isWindows() ? ".exe" : ""; + + var downloadRenderDoc = getTasks().register("downloadRenderDoc", DownloadTask.class, task -> { + task.setGroup(Constants.TaskGroup.FABRIC); + + task.getUrl().set(renderDocUrl); + task.getOutput().set(getProject().getLayout().getBuildDirectory().file(renderDocFilename)); + }); + + var extractRenderDoc = getTasks().register("extractRenderDoc", Sync.class, task -> { + task.setGroup(Constants.TaskGroup.FABRIC); + + if (operatingSystem.isWindows()) { + task.from(getProject().zipTree(downloadRenderDoc.map(DownloadTask::getOutput))); + } else { + task.from(getProject().tarTree(downloadRenderDoc.map(DownloadTask::getOutput))); + } + + task.into(getProject().getLayout().getBuildDirectory().dir("renderdoc")); + }); + + Provider renderDocDir = extractRenderDoc.map(Sync::getOutputs) + .map(TaskOutputs::getFiles) + .map(FileCollection::getSingleFile) + .map(dir -> new File(dir, renderDocBaseName)); + + if (operatingSystem.isLinux()) { + renderDocDir = renderDocDir.map(dir -> new File(dir, "bin")); + } + + Provider renderDocCMD = renderDocDir.map(dir -> new File(dir, "renderdoccmd" + executableExt)); + Provider renderDocUI = renderDocDir.map(dir -> new File(dir, "qrenderdoc" + executableExt)); + + getTasks().register("startRenderDocUI", RenderDocRunUITask.class, task -> task.getRenderDocExecutable().fileProvider(renderDocUI)); + + getTasks().withType(RenderDocRunTask.class).configureEach(task -> { + task.getRenderDocExecutable().fileProvider(renderDocCMD); }); } diff --git a/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java b/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java new file mode 100644 index 00000000..c6e851bb --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/RenderDocRunTask.java @@ -0,0 +1,93 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.task; + +import java.io.File; + +import javax.inject.Inject; + +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.process.CommandLineArgumentProvider; +import org.gradle.process.ExecOperations; +import org.gradle.process.ExecResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.Platform; + +public abstract class RenderDocRunTask extends RunGameTask { + private static final Logger LOGGER = LoggerFactory.getLogger(RenderDocRunTask.class); + + @InputFile + public abstract RegularFileProperty getRenderDocExecutable(); + + @Input + public abstract ListProperty getRenderDocArgs(); + + @Inject + protected abstract ExecOperations getExecOperations(); + + @Inject + public RenderDocRunTask(RunConfigSettings settings) { + super(settings); + setGroup(Constants.TaskGroup.FABRIC); + dependsOn("configureClientLaunch"); + getRenderDocArgs().addAll("capture", "--wait-for-exit"); + } + + @Override + public void exec() { + ExecResult result = getExecOperations().exec(exec -> { + exec.workingDir(new File(getProjectDir().get(), getInternalRunDir().get())); + exec.environment(getInternalEnvironmentVars().get()); + + exec.commandLine(getRenderDocExecutable().get().getAsFile()); + exec.args(getRenderDocArgs().get()); + exec.args("--working-dir", new File(getProjectDir().get(), getInternalRunDir().get())); + exec.args(getJavaLauncher().get().getExecutablePath()); + exec.args(getJvmArgs()); + exec.args(getMainClass().get()); + + for (CommandLineArgumentProvider provider : getArgumentProviders()) { + exec.args(provider.asArguments()); + } + + LOGGER.info("Running command: {}", exec.getCommandLine()); + }); + result.assertNormalExitValue(); + } + + public static boolean isSupported(Platform platform) { + final Platform.OperatingSystem os = platform.getOperatingSystem(); + final Platform.Architecture arch = platform.getArchitecture(); + // RenderDoc does support 32-bit Windows, but I cannot be bothered to test/maintain it + return (os.isLinux() || os.isWindows()) && arch.isX64(); + } +} diff --git a/src/main/java/net/fabricmc/loom/task/RenderDocRunUITask.java b/src/main/java/net/fabricmc/loom/task/RenderDocRunUITask.java new file mode 100644 index 00000000..7716d5c0 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/RenderDocRunUITask.java @@ -0,0 +1,51 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.task; + +import java.io.IOException; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.TaskAction; + +import net.fabricmc.loom.util.Constants; + +public abstract class RenderDocRunUITask extends DefaultTask { + @InputFile + public abstract RegularFileProperty getRenderDocExecutable(); + + public RenderDocRunUITask() { + setGroup(Constants.TaskGroup.FABRIC); + } + + @TaskAction + public void run() throws IOException { + ProcessBuilder builder = new ProcessBuilder() + .command(getRenderDocExecutable().getAsFile().get().getAbsolutePath()); + builder.start(); + // Allow to run in the background. + } +} diff --git a/src/main/java/net/fabricmc/loom/task/service/MappingsService.java b/src/main/java/net/fabricmc/loom/task/service/MappingsService.java index 44a7ced8..78055b93 100644 --- a/src/main/java/net/fabricmc/loom/task/service/MappingsService.java +++ b/src/main/java/net/fabricmc/loom/task/service/MappingsService.java @@ -25,6 +25,7 @@ package net.fabricmc.loom.task.service; import java.io.Closeable; +import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Path; @@ -35,9 +36,12 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration; +import net.fabricmc.loom.task.AbstractRemapJarTask; import net.fabricmc.loom.util.TinyRemapperHelper; import net.fabricmc.loom.util.service.Service; import net.fabricmc.loom.util.service.ServiceFactory; @@ -52,6 +56,8 @@ import net.fabricmc.tinyremapper.IMappingProvider; public final class MappingsService extends Service implements Closeable { public static ServiceType TYPE = new ServiceType<>(Options.class, MappingsService.class); + private static final Logger LOGGER = LoggerFactory.getLogger(TinyRemapperService.class); + // TODO use a nested TinyMappingsService instead of duplicating it public interface Options extends Service.Options { @InputFile @@ -91,6 +97,36 @@ public final class MappingsService extends Service impl return createOptions(project, LoomGradleExtension.get(project).getPlatformMappingFile(), from, to, false); } + public static Provider createForRemapTask(AbstractRemapJarTask remapJarTask) { + final Project project = remapJarTask.getProject(); + + return project.provider(() -> { + if (remapJarTask.getCustomMappings().isEmpty()) { + LOGGER.debug("Using default project mappings for remapping"); + return MappingsService.createOptionsWithProjectMappings( + project, + remapJarTask.getSourceNamespace(), + remapJarTask.getTargetNamespace() + ).get(); + } + + // Custom mappings: + File mappingsFile = remapJarTask.getCustomMappings().getSingleFile(); + + if (mappingsFile.getName().endsWith(".zip") || mappingsFile.getName().endsWith(".jar")) { + mappingsFile = project.zipTree(mappingsFile).matching(patternFilterable -> patternFilterable.include("mappings/mappings.tiny")).getSingleFile(); + } + + LOGGER.info("Using custom mappings for remap task: {}", mappingsFile); + return MappingsService.createOptions( + project, + mappingsFile.toPath(), + remapJarTask.getSourceNamespace(), remapJarTask.getTargetNamespace(), + false) + .get(); + }); + } + public MappingsService(Options options, ServiceFactory serviceFactory) { super(options, serviceFactory); } diff --git a/src/main/java/net/fabricmc/loom/task/service/MigrateMappingsService.java b/src/main/java/net/fabricmc/loom/task/service/MigrateMappingsService.java index 5fd32186..2f42a968 100644 --- a/src/main/java/net/fabricmc/loom/task/service/MigrateMappingsService.java +++ b/src/main/java/net/fabricmc/loom/task/service/MigrateMappingsService.java @@ -29,8 +29,8 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; -import com.google.common.collect.ImmutableMap; import org.cadixdev.lorenz.MappingSet; import org.cadixdev.mercury.Mercury; import org.cadixdev.mercury.remapper.MercuryRemapper; @@ -181,7 +181,7 @@ public class MigrateMappingsService extends Service createOptions(RemapSourcesJarTask task) { return TYPE.create(task.getProject(), o -> { - o.getMappings().set(MappingsService.createOptionsWithProjectMappings( - task.getProject(), - task.getSourceNamespace(), - task.getTargetNamespace() - )); + o.getMappings().set(MappingsService.createForRemapTask(task)); o.getJavaCompileRelease().set(getJavaCompileRelease(task.getProject())); o.getClasspath().from(task.getClasspath()); }); diff --git a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java index ad56653d..57e569e2 100644 --- a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperService.java @@ -41,6 +41,7 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; +import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; @@ -51,6 +52,7 @@ import org.gradle.api.tasks.Optional; import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; import net.fabricmc.loom.build.IntermediaryNamespaces; import net.fabricmc.loom.extension.RemapperExtensionHolder; import net.fabricmc.loom.task.AbstractRemapJarTask; @@ -66,7 +68,7 @@ import net.fabricmc.tinyremapper.InputTag; import net.fabricmc.tinyremapper.TinyRemapper; import net.fabricmc.tinyremapper.extension.mixin.MixinExtension; -public class TinyRemapperService extends Service implements Closeable { +public class TinyRemapperService extends Service implements TinyRemapperServiceInterface, Closeable { public static final ServiceType TYPE = new ServiceType<>(Options.class, TinyRemapperService.class); public interface Options extends Service.Options { @@ -103,7 +105,7 @@ public class TinyRemapperService extends Service im options.getFrom().set(remapJarTask.getSourceNamespace()); options.getTo().set(remapJarTask.getTargetNamespace()); - options.getMappings().add(MappingsService.createOptionsWithProjectMappings(project, options.getFrom(), options.getTo())); + options.getMappings().add(MappingsService.createForRemapTask(remapJarTask)); if (legacyMixin) { options.getMixinApMappings().set(MixinAPMappingService.createOptions(project, options.getFrom(), options.getTo().map(to -> IntermediaryNamespaces.replaceMixinIntermediaryNamespace(project, to)))); @@ -117,6 +119,56 @@ public class TinyRemapperService extends Service im }); } + public static Provider createSimple(Project project, Provider from, Provider to, ClasspathLibraries classpathLibraries) { + return TYPE.create(project, options -> { + final LoomGradleExtension extension = LoomGradleExtension.get(project); + final FileCollection classpath = getRemapClasspath(project, from, classpathLibraries); + + options.getFrom().set(from); + options.getTo().set(to); + options.getMappings().add(MappingsService.createOptionsWithProjectMappings(project, options.getFrom(), options.getTo())); + options.getUselegacyMixinAP().set(true); + options.getClasspath().from(classpath); + options.getKnownIndyBsms().set(extension.getKnownIndyBsms().get().stream().sorted().toList()); + options.getRemapperExtensions().set(extension.getRemapperExtensions()); + }); + } + + private static FileCollection getRemapClasspath(Project project, Provider from, ClasspathLibraries classpathLibraries) { + final LoomGradleExtension extension = LoomGradleExtension.get(project); + final ConfigurationContainer configurations = project.getConfigurations(); + + if (from.get().equals(MappingsNamespace.INTERMEDIARY.toString())) { + ConfigurableFileCollection files = project.files(extension.getMinecraftJars(MappingsNamespace.INTERMEDIARY)); + + if (classpathLibraries == ClasspathLibraries.INCLUDE) { + files = files.from(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES)); + } + + return files; + } + + if (classpathLibraries == ClasspathLibraries.INCLUDE) { + return configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME); + } + + return configurations.getByName(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME) + .minus(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES)) + .minus(configurations.getByName(Constants.Configurations.MINECRAFT_RUNTIME_LIBRARIES)); + } + + public enum ClasspathLibraries { + /** + * Default, in most cases the Minecraft libraries are not required as they are not obfuscated and do not need to be queried. + */ + EXCLUDE, + + /** + * Uses more memory, but provides a complete index of all the classes within the libraries. + */ + INCLUDE + } + private TinyRemapper tinyRemapper; @Nullable private KotlinRemapperClassloader kotlinRemapperClassloader; @@ -179,11 +231,13 @@ public class TinyRemapperService extends Service im return tag; } + @Override public TinyRemapper getTinyRemapperForRemapping() { isRemapping = true; return Objects.requireNonNull(tinyRemapper, "Tiny remapper has not been setup"); } + @Override public TinyRemapper getTinyRemapperForInputs() { if (isRemapping) { throw new IllegalStateException("Cannot read inputs as remapping has already started"); diff --git a/src/main/java/net/fabricmc/loom/task/service/TinyRemapperServiceInterface.java b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperServiceInterface.java new file mode 100644 index 00000000..52feaedf --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/service/TinyRemapperServiceInterface.java @@ -0,0 +1,35 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.task.service; + +import org.jetbrains.annotations.VisibleForTesting; + +import net.fabricmc.tinyremapper.TinyRemapper; + +@VisibleForTesting +public interface TinyRemapperServiceInterface { + TinyRemapper getTinyRemapperForRemapping(); + TinyRemapper getTinyRemapperForInputs(); +} diff --git a/src/main/java/net/fabricmc/loom/task/service/UnpickRemapperService.java b/src/main/java/net/fabricmc/loom/task/service/UnpickRemapperService.java new file mode 100644 index 00000000..3838c8de --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/service/UnpickRemapperService.java @@ -0,0 +1,171 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.task.service; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import daomephsta.unpick.constantmappers.datadriven.parser.v3.UnpickV3Reader; +import daomephsta.unpick.constantmappers.datadriven.parser.v3.UnpickV3Remapper; +import daomephsta.unpick.constantmappers.datadriven.parser.v3.UnpickV3Writer; +import daomephsta.unpick.constantmappers.datadriven.tree.UnpickV3Visitor; +import org.gradle.api.Project; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Nested; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.Remapper; + +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata; +import net.fabricmc.loom.util.JarPackageIndex; +import net.fabricmc.loom.util.service.Service; +import net.fabricmc.loom.util.service.ServiceFactory; +import net.fabricmc.loom.util.service.ServiceType; +import net.fabricmc.tinyremapper.TinyRemapper; +import net.fabricmc.tinyremapper.api.TrClass; +import net.fabricmc.tinyremapper.api.TrField; + +public class UnpickRemapperService extends Service { + public static final ServiceType TYPE = new ServiceType<>(Options.class, UnpickRemapperService.class); + + public interface Options extends Service.Options { + @Nested + Property getTinyRemapper(); + } + + public static Provider createOptions(Project project, UnpickMetadata.V2 metadata) { + return TYPE.create(project, options -> { + options.getTinyRemapper().set(TinyRemapperService.createSimple(project, + project.provider(metadata::namespace), + project.provider(MappingsNamespace.NAMED::toString), + TinyRemapperService.ClasspathLibraries.INCLUDE // Must include the full set of libraries on classpath so fields can be looked up. This does use a lot of memory however... + )); + }); + } + + public UnpickRemapperService(Options options, ServiceFactory serviceFactory) { + super(options, serviceFactory); + } + + /** + * Return the remapped definitions. + */ + public String remap(File input) throws IOException { + TinyRemapperServiceInterface tinyRemapperService = getServiceFactory().get(getOptions().getTinyRemapper()); + TinyRemapper tinyRemapper = tinyRemapperService.getTinyRemapperForRemapping(); + + List classpath = getOptions().getTinyRemapper().get().getClasspath().getFiles().stream().map(File::toPath).toList(); + JarPackageIndex packageIndex = JarPackageIndex.create(classpath); + + return doRemap(input, tinyRemapper, packageIndex); + } + + private String doRemap(File input, TinyRemapper remapper, JarPackageIndex packageIndex) throws IOException { + try (Reader fileReader = new BufferedReader(new FileReader(input)); + var reader = new UnpickV3Reader(fileReader)) { + var writer = new UnpickV3Writer(); + reader.accept(new UnpickRemapper(writer, remapper, packageIndex)); + return writer.getOutput().replace(System.lineSeparator(), "\n"); + } + } + + private static final class UnpickRemapper extends UnpickV3Remapper { + private final TinyRemapper tinyRemapper; + private final Remapper remapper; + private final JarPackageIndex jarPackageIndex; + + private UnpickRemapper(UnpickV3Visitor downstream, TinyRemapper tinyRemapper, JarPackageIndex jarPackageIndex) { + super(downstream); + this.tinyRemapper = tinyRemapper; + this.remapper = tinyRemapper.getEnvironment().getRemapper(); + this.jarPackageIndex = jarPackageIndex; + } + + @Override + protected String mapClassName(String className) { + return remapper.map(className.replace('.', '/')).replace('/', '.'); + } + + @Override + protected String mapFieldName(String className, String fieldName, String fieldDesc) { + return remapper.mapFieldName(className.replace('.', '/'), fieldName, fieldDesc); + } + + @Override + protected String mapMethodName(String className, String methodName, String methodDesc) { + return remapper.mapMethodName(className.replace('.', '/'), methodName, methodDesc); + } + + // Return all classes in the given package, not recursively. + @Override + protected List getClassesInPackage(String pkg) { + return jarPackageIndex.packages().getOrDefault(pkg, Collections.emptyList()) + .stream() + .map(className -> pkg + "." + className) + .toList(); + } + + @Override + protected String getFieldDesc(String className, String fieldName) { + TrClass trClass = tinyRemapper.getEnvironment().getClass(className.replace('.', '/')); + + if (trClass != null) { + for (TrField trField : trClass.getFields()) { + if (trField.getName().equals(fieldName)) { + return trField.getDesc(); + } + } + } + + String fieldDesc = getFieldDescFromReflection(className, fieldName); + + if (fieldDesc == null) { + throw new IllegalStateException("Could not find field " + fieldName + " in class " + className); + } + + return fieldDesc; + } + + private static String getFieldDescFromReflection(String className, String fieldName) { + try { + // Use the bootstrap class loader, which should only resolve classes from the JDK. + // Don't run the static initializer. + Class clazz = Class.forName(className, false, null); + Field field = clazz.getDeclaredField(fieldName); + return Type.getDescriptor(field.getType()); + } catch (ClassNotFoundException | NoSuchFieldException e) { + return null; + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/task/service/UnpickService.java b/src/main/java/net/fabricmc/loom/task/service/UnpickService.java new file mode 100644 index 00000000..8168a25a --- /dev/null +++ b/src/main/java/net/fabricmc/loom/task/service/UnpickService.java @@ -0,0 +1,270 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.task.service; + +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Stream; + +import daomephsta.unpick.api.ConstantUninliner; +import daomephsta.unpick.api.classresolvers.ClassResolvers; +import daomephsta.unpick.api.classresolvers.IClassResolver; +import daomephsta.unpick.api.constantgroupers.ConstantGroupers; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputFile; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.providers.mappings.MappingConfiguration; +import net.fabricmc.loom.configuration.providers.mappings.unpick.UnpickMetadata; +import net.fabricmc.loom.task.GenerateSourcesTask; +import net.fabricmc.loom.util.AsyncZipProcessor; +import net.fabricmc.loom.util.Checksum; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.SLF4JAdapterHandler; +import net.fabricmc.loom.util.service.Service; +import net.fabricmc.loom.util.service.ServiceFactory; +import net.fabricmc.loom.util.service.ServiceType; + +public class UnpickService extends Service { + private static final Logger LOGGER = LoggerFactory.getLogger(UnpickService.class); + private static final java.util.logging.Logger JAVA_LOGGER = java.util.logging.Logger.getLogger("loom-unpick-service"); + + static { + JAVA_LOGGER.setUseParentHandlers(false); + JAVA_LOGGER.addHandler(new SLF4JAdapterHandler(LOGGER, true)); + } + + public static final ServiceType TYPE = new ServiceType<>(Options.class, UnpickService.class); + + public interface Options extends Service.Options { + @InputFile + RegularFileProperty getUnpickDefinitions(); + + @Optional + @Nested + Property getUnpickRemapperService(); + + @InputFiles + ConfigurableFileCollection getUnpickConstantJar(); + + @InputFiles + ConfigurableFileCollection getUnpickClasspath(); + + @OutputFile + RegularFileProperty getUnpickOutputJar(); + + @Input + Property getLenient(); + } + + public static Provider createOptions(GenerateSourcesTask task) { + final Project project = task.getProject(); + return TYPE.maybeCreate(project, options -> { + LoomGradleExtension extension = LoomGradleExtension.get(project); + MappingConfiguration mappingConfiguration = extension.getMappingConfiguration(); + + if (!mappingConfiguration.hasUnpickDefinitions()) { + return false; + } + + UnpickMetadata unpickMetadata = mappingConfiguration.getUnpickMetadata(); + + if (unpickMetadata instanceof UnpickMetadata.V2 v2) { + if (!Objects.equals(v2.namespace(), MappingsNamespace.NAMED.toString())) { + options.getUnpickRemapperService().set(UnpickRemapperService.createOptions(project, v2)); + } + } + + ConfigurationContainer configurations = project.getConfigurations(); + File mappingsWorkingDir = mappingConfiguration.mappingsWorkingDir().toFile(); + + options.getUnpickDefinitions().set(mappingConfiguration.getUnpickDefinitionsFile()); + options.getUnpickOutputJar().set(task.getInputJarName().map(s -> project.getLayout() + .dir(project.provider(() -> mappingsWorkingDir)).get().file(s + "-unpicked.jar"))); + options.getUnpickConstantJar().setFrom(configurations.getByName(Constants.Configurations.MAPPING_CONSTANTS)); + options.getUnpickClasspath().setFrom(configurations.getByName(Constants.Configurations.MINECRAFT_COMPILE_LIBRARIES)); + options.getUnpickClasspath().from(configurations.getByName(Constants.Configurations.MOD_COMPILE_CLASSPATH_MAPPED)); + options.getLenient().set(unpickMetadata instanceof UnpickMetadata.V1); + extension.getMinecraftJars(MappingsNamespace.NAMED).forEach(options.getUnpickClasspath()::from); + return true; + }); + } + + public UnpickService(Options options, ServiceFactory serviceFactory) { + super(options, serviceFactory); + } + + public Path unpickJar(Path inputJar, @Nullable Path existingClasses) throws IOException { + final List classpath = Stream.of( + getOptions().getUnpickClasspath().getFiles().stream().map(File::toPath), + getOptions().getUnpickConstantJar().getFiles().stream().map(File::toPath), + Stream.of(inputJar), + Stream.ofNullable(existingClasses) + ).flatMap(Function.identity()).toList(); + final Path outputJar = getOptions().getUnpickOutputJar().get().getAsFile().toPath(); + Files.deleteIfExists(outputJar); + + try (ZipFsClasspath zipFsClasspath = ZipFsClasspath.create(classpath); + InputStream unpickDefinitions = getUnpickDefinitionsInputStream()) { + IClassResolver classResolver = zipFsClasspath.createClassResolver().chain(ClassResolvers.classpath()); + ConstantUninliner uninliner = ConstantUninliner.builder() + .logger(JAVA_LOGGER) + .classResolver(classResolver) + .grouper(ConstantGroupers.dataDriven() + .logger(JAVA_LOGGER) + .lenient(getOptions().getLenient().get()) + .classResolver(classResolver) + .mappingSource(unpickDefinitions) + .build()) + .build(); + + AsyncZipProcessor.processEntries(inputJar, outputJar, new UnpickZipProcessor(uninliner)); + } + + return outputJar; + } + + private InputStream getUnpickDefinitionsInputStream() throws IOException { + final Path unpickDefinitionsPath = getOptions().getUnpickDefinitions().getAsFile().get().toPath(); + + if (getOptions().getUnpickRemapperService().isPresent()) { + LOGGER.info("Remapping unpick definitions: {}", unpickDefinitionsPath); + + UnpickRemapperService unpickRemapperService = getServiceFactory().get(getOptions().getUnpickRemapperService()); + String remapped = unpickRemapperService.remap(unpickDefinitionsPath.toFile()); + + return new ByteArrayInputStream(remapped.getBytes(StandardCharsets.UTF_8)); + } + + LOGGER.debug("Using unpick definitions: {}", unpickDefinitionsPath); + + return Files.newInputStream(unpickDefinitionsPath); + } + + public String getUnpickCacheKey() { + return Checksum.of(List.of( + Checksum.of(getOptions().getUnpickDefinitions().getAsFile().get()), + Checksum.of(getOptions().getUnpickConstantJar()), + Checksum.of(getOptions().getUnpickRemapperService() + .flatMap(options -> options.getTinyRemapper() + .flatMap(TinyRemapperService.Options::getFrom)) + .getOrElse("named")) + )).sha256().hex(); + } + + private record UnpickZipProcessor(ConstantUninliner uninliner) implements AsyncZipProcessor { + @Override + public void processEntryAsync(Path input, Path output) throws IOException { + Files.createDirectories(output.getParent()); + + String fileName = input.toAbsolutePath().toString(); + + if (!fileName.endsWith(".class")) { + // Copy non-class files + Files.copy(input, output); + return; + } + + ClassNode classNode = new ClassNode(); + + try (InputStream is = Files.newInputStream(input)) { + ClassReader reader = new ClassReader(is); + reader.accept(classNode, 0); + } + + LOGGER.debug("Unpick class: {}", classNode.name); + uninliner.transform(classNode); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + + Files.write(output, writer.toByteArray()); + } + } + + private record ZipFsClasspath(List fileSystems) implements Closeable { + private ZipFsClasspath { + if (fileSystems.isEmpty()) { + throw new IllegalArgumentException("No resolvers provided"); + } + } + + public static ZipFsClasspath create(List classpath) throws IOException { + var fileSystems = new ArrayList(); + + for (Path path : classpath) { + FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(path, false); + fileSystems.add(fs); + } + + return new ZipFsClasspath(fileSystems); + } + + public IClassResolver createClassResolver() { + IClassResolver resolver = ClassResolvers.fromDirectory(fileSystems.getFirst().getRoot()); + + for (int i = 1; i < fileSystems.size(); i++) { + resolver = resolver.chain(ClassResolvers.fromDirectory(fileSystems.get(i).getRoot())); + } + + return resolver; + } + + @Override + public void close() throws IOException { + for (FileSystemUtil.Delegate fileSystem : fileSystems) { + fileSystem.close(); + } + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/AsyncZipProcessor.java b/src/main/java/net/fabricmc/loom/util/AsyncZipProcessor.java index 4d3af845..b25aaa8f 100644 --- a/src/main/java/net/fabricmc/loom/util/AsyncZipProcessor.java +++ b/src/main/java/net/fabricmc/loom/util/AsyncZipProcessor.java @@ -40,17 +40,17 @@ import java.util.concurrent.Executors; public interface AsyncZipProcessor { static void processEntries(Path inputZip, Path outputZip, AsyncZipProcessor processor) throws IOException { try (FileSystemUtil.Delegate inFs = FileSystemUtil.getJarFileSystem(inputZip, false); - FileSystemUtil.Delegate outFs = FileSystemUtil.getJarFileSystem(outputZip, true)) { + FileSystemUtil.Delegate outFs = FileSystemUtil.getJarFileSystem(outputZip, true); + ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())) { final Path inRoot = inFs.get().getPath("/"); final Path outRoot = outFs.get().getPath("/"); List> futures = new ArrayList<>(); - final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); Files.walkFileTree(inRoot, new SimpleFileVisitor<>() { @Override - public FileVisitResult visitFile(Path inputFile, BasicFileAttributes attrs) throws IOException { - final CompletableFuture future = CompletableFuture.supplyAsync(() -> { + public FileVisitResult visitFile(Path inputFile, BasicFileAttributes attrs) { + final CompletableFuture future = CompletableFuture.runAsync(() -> { try { final String rel = inRoot.relativize(inputFile).toString(); final Path outputFile = outRoot.resolve(rel); @@ -58,8 +58,6 @@ public interface AsyncZipProcessor { } catch (IOException e) { throw new CompletionException(e); } - - return null; }, executor); futures.add(future); @@ -67,7 +65,7 @@ public interface AsyncZipProcessor { } }); - // Wait for all futures to complete + // Wait for all futures to complete, throwing the first exception if any for (CompletableFuture future : futures) { try { future.join(); @@ -79,8 +77,6 @@ public interface AsyncZipProcessor { throw new RuntimeException("Failed to process zip", e.getCause()); } } - - executor.shutdown(); } } diff --git a/src/main/java/net/fabricmc/loom/util/CacheKey.java b/src/main/java/net/fabricmc/loom/util/CacheKey.java new file mode 100644 index 00000000..d6fe8ebf --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/CacheKey.java @@ -0,0 +1,59 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.util; + +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.tasks.Internal; + +import net.fabricmc.loom.util.gradle.GradleTypeAdapter; + +/** + * A simple base class for creating cache keys. Extend this class and create abstract properties to be included in the cache key. + */ +public abstract class CacheKey { + private static final int CHECKSUM_LENGTH = 8; + private final transient Supplier jsonSupplier = Suppliers.memoize(() -> GradleTypeAdapter.GSON.toJson(this)); + private final transient Supplier cacheKeySupplier = Suppliers.memoize(() -> Checksum.of(jsonSupplier.get()).sha1().hex(CHECKSUM_LENGTH)); + + public static T create(Project project, Class clazz, Action action) { + T instance = project.getObjects().newInstance(clazz); + action.execute(instance); + return instance; + } + + @Internal + public final String getJson() { + return jsonSupplier.get(); + } + + @Internal + public final String getCacheKey() { + return cacheKeySupplier.get(); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/Checksum.java b/src/main/java/net/fabricmc/loom/util/Checksum.java index b9192d41..27a5258d 100644 --- a/src/main/java/net/fabricmc/loom/util/Checksum.java +++ b/src/main/java/net/fabricmc/loom/util/Checksum.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2016-2020 FabricMC + * Copyright (c) 2025 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,88 +26,138 @@ package net.fabricmc.loom.util; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; +import java.util.List; -import com.google.common.hash.HashCode; -import com.google.common.hash.Hashing; -import com.google.common.io.BaseEncoding; -import com.google.common.io.ByteSource; -import com.google.common.io.Files; import org.gradle.api.Project; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; +import org.gradle.api.file.FileCollection; +import org.jetbrains.annotations.NotNull; -public class Checksum { - private static final Logger log = Logging.getLogger(Checksum.class); +public final class Checksum { + public static Checksum of(byte[] data) { + return new Checksum(digest -> digest.write(data)); + } - public static boolean equals(File file, String checksum) { - if (file == null || !file.exists()) { - return false; - } + public static Checksum of(String str) { + return new Checksum(digest -> digest.write(str)); + } + + public static Checksum of(File file) { + return of(file.toPath()); + } + + public static Checksum of(Path file) { + return new Checksum(digest -> { + try (InputStream is = Files.newInputStream(file)) { + is.transferTo(digest); + } + }); + } + + public static Checksum of(Project project) { + return of(project.getProjectDir().getAbsolutePath() + ":" + project.getPath()); + } + + public static Checksum of(FileCollection files) { + return new Checksum(os -> { + for (File file : files) { + try (InputStream is = Files.newInputStream(file.toPath())) { + is.transferTo(os); + } + } + }); + } + + public static Checksum of(List others) { + return new Checksum(os -> { + for (Checksum other : others) { + other.consumer.accept(os); + } + }); + } + + private final DataConsumer consumer; + + private Checksum(DataConsumer consumer) { + this.consumer = consumer; + } + + public Result sha1() { + return computeResult("SHA-1"); + } + + public Result sha256() { + return computeResult("SHA-256"); + } + + public Result md5() { + return computeResult("MD5"); + } + + private Result computeResult(String algorithm) { + MessageDigest digest; try { - HashCode hash = Files.asByteSource(file).hash(Hashing.sha1()); - String hashString = hash.toString(); - log.debug("Checksum check: '" + hashString + "' == '" + checksum + "'?"); - return hashString.equals(checksum); - } catch (IOException e) { - e.printStackTrace(); + digest = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); } - return false; + try (MessageDigestOutputStream os = new MessageDigestOutputStream(digest)) { + consumer.accept(os); + } catch (IOException e) { + throw new UncheckedIOException("Failed to compute checksum", e); + } + + return new Result(digest.digest()); } - public static byte[] sha256(File file) { - try { - HashCode hash = Files.asByteSource(file).hash(Hashing.sha256()); - return hash.asBytes(); - } catch (IOException e) { - throw new UncheckedIOException("Failed to get file hash", e); + public record Result(byte[] digest) { + public String hex() { + return HexFormat.of().formatHex(digest()); + } + + public String hex(int length) { + return hex().substring(0, length); + } + + public boolean matchesStr(String other) { + return hex().equalsIgnoreCase(other); } } - public static String sha256Hex(byte[] input) throws IOException { - HashCode hash = ByteSource.wrap(input).hash(Hashing.sha256()); - return Checksum.toHex(hash.asBytes()); + @FunctionalInterface + private interface DataConsumer { + void accept(MessageDigestOutputStream os) throws IOException; } - public static String sha1Hex(Path path) throws IOException { - HashCode hash = Files.asByteSource(path.toFile()).hash(Hashing.sha1()); - return toHex(hash.asBytes()); - } + private static class MessageDigestOutputStream extends OutputStream { + private final MessageDigest digest; - public static String sha1Hex(byte[] input) { - try { - HashCode hash = ByteSource.wrap(input).hash(Hashing.sha1()); - return toHex(hash.asBytes()); - } catch (IOException e) { - throw new UncheckedIOException("Failed to hash", e); + private MessageDigestOutputStream(MessageDigest digest) { + this.digest = digest; } - } - public static String truncatedSha256(File file) { - try { - HashCode hash = Files.asByteSource(file).hash(Hashing.sha256()); - return hash.toString().substring(0, 12); - } catch (IOException e) { - throw new UncheckedIOException("Failed to get file hash of " + file, e); + @Override + public void write(int b) { + digest.update((byte) b); } - } - public static byte[] sha256(String string) { - HashCode hash = Hashing.sha256().hashString(string, StandardCharsets.UTF_8); - return hash.asBytes(); - } + @Override + public void write(byte @NotNull[] b, int off, int len) { + digest.update(b, off, len); + } - public static String toHex(byte[] bytes) { - return BaseEncoding.base16().lowerCase().encode(bytes); - } - - public static String projectHash(Project project) { - String str = project.getProjectDir().getAbsolutePath() + ":" + project.getPath(); - String hex = sha1Hex(str.getBytes(StandardCharsets.UTF_8)); - return hex.substring(hex.length() - 16); + public void write(String string) throws IOException { + write(string.getBytes(StandardCharsets.UTF_8)); + } } } diff --git a/src/main/java/net/fabricmc/loom/util/Constants.java b/src/main/java/net/fabricmc/loom/util/Constants.java index dd7628c8..1d035ce9 100644 --- a/src/main/java/net/fabricmc/loom/util/Constants.java +++ b/src/main/java/net/fabricmc/loom/util/Constants.java @@ -101,7 +101,6 @@ public class Constants { */ public static final String FORGE_RUNTIME_LIBRARY = "forgeRuntimeLibrary"; public static final String MAPPING_CONSTANTS = "mappingsConstants"; - public static final String UNPICK_CLASSPATH = "unpick"; /** * A configuration that behaves like {@code runtimeOnly} but is not * exposed in {@code runtimeElements} to dependents. A bit like @@ -176,6 +175,14 @@ public class Constants { public static final String RUNTIME_JAVA_COMPATIBILITY_VERSION = "fabric.loom.runtimeJavaCompatibilityVersion"; public static final String DECOMPILE_CACHE_MAX_FILES = "fabric.loom.decompileCacheMaxFiles"; public static final String DECOMPILE_CACHE_MAX_AGE = "fabric.loom.decompileCacheMaxAge"; + /** + * Skip the signature verification of the Minecraft jar after downloading it. + */ + public static final String DISABLE_MINECRAFT_VERIFICATION = "fabric.loom.disableMinecraftVerification"; + /** + * When using the MojangMappingLayer this will remove names for non root methods by using the intermediary mappings. + */ + public static final String DROP_NON_INTERMEDIATE_ROOT_METHODS = "fabric.loom.dropNonIntermediateRootMethods"; public static final String ALLOW_MISMATCHED_PLATFORM_VERSION = "loom.allowMismatchedPlatformVersion"; public static final String IGNORE_DEPENDENCY_LOOM_VERSION_VALIDATION = "loom.ignoreDependencyLoomVersionValidation"; } diff --git a/src/main/java/net/fabricmc/loom/util/IdentityBiMap.java b/src/main/java/net/fabricmc/loom/util/IdentityBiMap.java new file mode 100644 index 00000000..b59e0b24 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/IdentityBiMap.java @@ -0,0 +1,49 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.util; + +import java.util.IdentityHashMap; +import java.util.Map; + +public final class IdentityBiMap { + private final Map keyToValue = new IdentityHashMap<>(); + private final Map valueToKey = new IdentityHashMap<>(); + + public IdentityBiMap() { + } + + public void put(K key, V value) { + keyToValue.put(key, value); + valueToKey.put(value, key); + } + + public V getByKey(K key) { + return keyToValue.get(key); + } + + public K getByValue(V value) { + return valueToKey.get(value); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/JarPackageIndex.java b/src/main/java/net/fabricmc/loom/util/JarPackageIndex.java new file mode 100644 index 00000000..cb892532 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/JarPackageIndex.java @@ -0,0 +1,100 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * An index of all packages and the classes directly contained within. + */ +public record JarPackageIndex(Map> packages) { + public static JarPackageIndex create(List jars) { + Map> packages = jars.stream() + .map(jar -> CompletableFuture.supplyAsync(() -> { + try { + List classes = getClasses(jar); + return groupClassesByPackage(classes); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, Executors.newVirtualThreadPerTaskExecutor())) + .map(CompletableFuture::join) + .flatMap(map -> map.entrySet().stream()) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (existing, newValues) -> { + existing.addAll(newValues); + return existing; + } + )); + + return new JarPackageIndex(packages); + } + + private static List getClasses(Path jar) throws IOException { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(jar, false); + Stream walk = Files.walk(fs.getRoot())) { + return walk + .filter(Files::isRegularFile) + .map(Path::toString) + .filter(className -> className.endsWith(".class")) + .map(className -> className.startsWith("/") ? className.substring(1) : className) + .toList(); + } + } + + private static Map> groupClassesByPackage(List classes) { + return classes.stream() + .filter(className -> className.endsWith(".class")) // Ensure it's a class file + .collect(Collectors.groupingBy( + JarPackageIndex::extractPackageName, + Collectors.mapping( + JarPackageIndex::extractClassName, + Collectors.toList() + ) + )); + } + + // Returns the package name from a class name, e.g., "com/example/MyClass.class" -> "com.example" + private static String extractPackageName(String className) { + int lastSlashIndex = className.lastIndexOf('/'); + return lastSlashIndex == -1 ? "" : className.substring(0, lastSlashIndex).replace("/", "."); + } + + private static String extractClassName(String className) { + int lastSlashIndex = className.lastIndexOf('/'); + String simpleName = lastSlashIndex == -1 ? className : className.substring(lastSlashIndex + 1); + return simpleName.endsWith(".class") ? simpleName.substring(0, simpleName.length() - 6) : simpleName; + } +} diff --git a/src/main/java/net/fabricmc/loom/util/Platform.java b/src/main/java/net/fabricmc/loom/util/Platform.java index 9cb99f89..c7487549 100644 --- a/src/main/java/net/fabricmc/loom/util/Platform.java +++ b/src/main/java/net/fabricmc/loom/util/Platform.java @@ -53,6 +53,10 @@ public interface Platform { boolean isArm(); boolean isRiscV(); + + default boolean isX64() { + return is64Bit() && !isArm() && !isRiscV(); + } } Architecture getArchitecture(); diff --git a/src/main/java/net/fabricmc/loom/util/SLF4JAdapterHandler.java b/src/main/java/net/fabricmc/loom/util/SLF4JAdapterHandler.java new file mode 100644 index 00000000..d0dd63ae --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/SLF4JAdapterHandler.java @@ -0,0 +1,64 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.util; + +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import org.slf4j.Logger; + +public class SLF4JAdapterHandler extends Handler { + private final Logger logger; + private final boolean suppressWarnings; + + public SLF4JAdapterHandler(Logger logger, boolean suppressWarnings) { + this.logger = logger; + this.suppressWarnings = suppressWarnings; + } + + @Override + public void publish(LogRecord record) { + if (record.getLevel().intValue() >= Level.SEVERE.intValue()) { + logger.error(record.getMessage(), record.getThrown()); + } else if (record.getLevel().intValue() >= Level.WARNING.intValue() && !suppressWarnings) { + logger.warn(record.getMessage(), record.getThrown()); + } else if (record.getLevel().intValue() >= Level.INFO.intValue()) { + logger.info(record.getMessage(), record.getThrown()); + } else if (record.getLevel().intValue() >= Level.FINER.intValue()) { + logger.debug(record.getMessage(), record.getThrown()); + } else { + logger.trace(record.getMessage(), record.getThrown()); + } + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } +} diff --git a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java index 87b62afc..11787ef1 100644 --- a/src/main/java/net/fabricmc/loom/util/SourceRemapper.java +++ b/src/main/java/net/fabricmc/loom/util/SourceRemapper.java @@ -120,7 +120,7 @@ public class SourceRemapper { source = new File(destination.getAbsolutePath().substring(0, destination.getAbsolutePath().lastIndexOf('.')) + "-dev.jar"); try { - com.google.common.io.Files.move(destination, source); + Files.move(destination.toPath(), source.toPath()); } catch (IOException e) { throw new RuntimeException("Could not rename " + destination.getName() + "!", e); } diff --git a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java index 9829df3d..a15b24ba 100644 --- a/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java +++ b/src/main/java/net/fabricmc/loom/util/TinyRemapperHelper.java @@ -31,7 +31,6 @@ import java.util.Set; import java.util.function.Consumer; import java.util.regex.Pattern; -import com.google.common.collect.ImmutableMap; import dev.architectury.loom.util.MappingOption; import org.gradle.api.Project; @@ -50,11 +49,11 @@ import net.fabricmc.tinyremapper.TinyRemapper; * Contains shortcuts to create tiny remappers using the mappings accessibly to the project. */ public final class TinyRemapperHelper { - public static final Map JSR_TO_JETBRAINS = new ImmutableMap.Builder() - .put("javax/annotation/Nullable", "org/jetbrains/annotations/Nullable") - .put("javax/annotation/Nonnull", "org/jetbrains/annotations/NotNull") - .put("javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable") - .build(); + public static final Map JSR_TO_JETBRAINS = Map.of( + "javax/annotation/Nullable", "org/jetbrains/annotations/Nullable", + "javax/annotation/Nonnull", "org/jetbrains/annotations/NotNull", + "javax/annotation/concurrent/Immutable", "org/jetbrains/annotations/Unmodifiable" + ); /** * Matches the new local variable naming format introduced in 21w37a. diff --git a/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java b/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java index 39f0f282..e59b8396 100644 --- a/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java +++ b/src/main/java/net/fabricmc/loom/util/ZipReprocessorUtil.java @@ -46,7 +46,7 @@ public class ZipReprocessorUtil { private static final String META_INF = "META-INF/"; // See https://docs.oracle.com/en/java/javase/20/docs/specs/jar/jar.html#signed-jar-file - private static boolean isSpecialFile(String zipEntryName) { + public static boolean isSpecialFile(String zipEntryName) { if (!zipEntryName.startsWith(META_INF)) { return false; } diff --git a/src/main/java/net/fabricmc/loom/util/download/Download.java b/src/main/java/net/fabricmc/loom/util/download/Download.java index df903148..b8c360b9 100644 --- a/src/main/java/net/fabricmc/loom/util/download/Download.java +++ b/src/main/java/net/fabricmc/loom/util/download/Download.java @@ -24,8 +24,6 @@ package net.fabricmc.loom.util.download; -import static com.google.common.io.Files.createParentDirs; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -145,13 +143,13 @@ public final class Download { } } - void downloadPath(Path output) throws DownloadException { + DownloadResult downloadPath(Path output) throws DownloadException { boolean downloadRequired = requiresDownload(output); if (!downloadRequired) { // Does not require download, we are done here. progressListener.onEnd(); - return; + return new DownloadResultImpl(false); } try { @@ -162,6 +160,8 @@ public final class Download { } finally { progressListener.onEnd(); } + + return new DownloadResultImpl(true); } private void doDownload(Path output) throws DownloadException { @@ -172,7 +172,7 @@ public final class Download { } try { - createParentDirs(output.toFile()); + Files.createDirectories(output.getParent()); } catch (IOException e) { throw error(e, "Failed to create parent directories"); } @@ -222,7 +222,7 @@ public final class Download { String downloadedHash; try { - downloadedHash = Checksum.sha1Hex(output); + downloadedHash = Checksum.of(output).sha1().hex(); Files.deleteIfExists(output); } catch (IOException e) { downloadedHash = "unknown hash"; @@ -357,12 +357,12 @@ public final class Download { String hash = expectedHash.substring(i + 1); try { - String computedHash = switch (algorithm) { - case "sha1" -> Checksum.sha1Hex(path); + Checksum.Result computedHash = switch (algorithm) { + case "sha1" -> Checksum.of(path).sha1(); default -> throw error("Unsupported hash algorithm (%s)", algorithm); }; - return computedHash.equalsIgnoreCase(hash); + return computedHash.matchesStr(hash); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -483,4 +483,6 @@ public final class Download { private DownloadException error(Throwable throwable, String message, Object... args) { return new DownloadException(message.formatted(args), throwable); } + + private record DownloadResultImpl(boolean didDownload) implements DownloadResult { } } diff --git a/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java b/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java index ab787bfb..8da80ce0 100644 --- a/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java +++ b/src/main/java/net/fabricmc/loom/util/download/DownloadBuilder.java @@ -33,6 +33,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.Locale; +import java.util.concurrent.CompletableFuture; @SuppressWarnings("UnusedReturnValue") public class DownloadBuilder { @@ -115,15 +116,12 @@ public class DownloadBuilder { return new Download(this.url, this.expectedHash, this.useEtag, this.forceDownload, this.offline, maxAge, progressListener, httpVersion, downloadAttempt); } - public void downloadPathAsync(Path path, DownloadExecutor executor) { - executor.runAsync(() -> downloadPath(path)); + public CompletableFuture downloadPathAsync(Path path, DownloadExecutor executor) { + return executor.runAsync(() -> downloadPath(path)); } - public void downloadPath(Path path) throws DownloadException { - withRetries((download) -> { - download.downloadPath(path); - return null; - }); + public DownloadResult downloadPath(Path path) throws DownloadException { + return withRetries((download) -> download.downloadPath(path)); } public String downloadString() throws DownloadException { diff --git a/src/main/java/net/fabricmc/loom/util/download/DownloadExecutor.java b/src/main/java/net/fabricmc/loom/util/download/DownloadExecutor.java index f1a4606e..a4c9eba0 100644 --- a/src/main/java/net/fabricmc/loom/util/download/DownloadExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/download/DownloadExecutor.java @@ -24,10 +24,11 @@ package net.fabricmc.loom.util.download; -import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -40,20 +41,20 @@ public class DownloadExecutor implements AutoCloseable { executorService = Executors.newFixedThreadPool(threads); } - void runAsync(DownloadRunner downloadRunner) { + CompletableFuture runAsync(DownloadRunner downloadRunner) { if (!downloadExceptions.isEmpty()) { - return; + return CompletableFuture.failedFuture(new DownloadException("Download blocked due to previous errors")); } - executorService.execute(() -> { + return CompletableFuture.supplyAsync(() -> { try { - downloadRunner.run(); + return downloadRunner.run(); } catch (DownloadException e) { executorService.shutdownNow(); downloadExceptions.add(e); - throw new UncheckedIOException(e); + throw new CompletionException(e); } - }); + }, executorService); } @Override @@ -79,6 +80,6 @@ public class DownloadExecutor implements AutoCloseable { @FunctionalInterface public interface DownloadRunner { - void run() throws DownloadException; + DownloadResult run() throws DownloadException; } } diff --git a/src/main/java/net/fabricmc/loom/util/download/DownloadResult.java b/src/main/java/net/fabricmc/loom/util/download/DownloadResult.java new file mode 100644 index 00000000..4ac1deb1 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/download/DownloadResult.java @@ -0,0 +1,29 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.util.download; + +public interface DownloadResult { + boolean didDownload(); +} diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java index aa269bd0..c57a2dd3 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java @@ -61,7 +61,7 @@ public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJso public abstract Map getClassTweakers(); - public final FabricModJsonSource getSource() { + public FabricModJsonSource getSource() { return source; } diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java index 411e79cc..6b948366 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJsonFactory.java @@ -150,7 +150,15 @@ public final class FabricModJsonFactory { } try (Reader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { - return create(LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class), new FabricModJsonSource.SourceSetSource(project, sourceSets)); + final JsonObject modJson = LoomGradlePlugin.GSON.fromJson(reader, JsonObject.class); + + if (modJson == null) { + // fromJson returns null if the file is empty + LOGGER.warn("Failed to parse empty fabric.mod.json: {}", file.getAbsolutePath()); + return null; + } + + return create(modJson, new FabricModJsonSource.SourceSetSource(project, sourceSets)); } catch (JsonSyntaxException e) { LOGGER.warn("Failed to parse fabric.mod.json: {}", file.getAbsolutePath()); return null; diff --git a/src/main/java/net/fabricmc/loom/util/fmj/mixin/MixinConfiguration.java b/src/main/java/net/fabricmc/loom/util/fmj/mixin/MixinConfiguration.java new file mode 100644 index 00000000..5e74518b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/fmj/mixin/MixinConfiguration.java @@ -0,0 +1,65 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.util.fmj.mixin; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonObject; +import org.jetbrains.annotations.Nullable; + +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.util.fmj.FabricModJson; +import net.fabricmc.loom.util.fmj.FabricModJsonSource; + +public record MixinConfiguration( + @Nullable MixinRefmap refmap) { + private static final String REFMAP_KEY = "refmap"; + + public static List fromMod(FabricModJson fabricModJson) throws IOException { + var configs = new ArrayList(); + + for (String configPath : fabricModJson.getMixinConfigurations()) { + configs.add(fromMod(configPath, fabricModJson.getSource())); + } + + return configs; + } + + private static MixinConfiguration fromMod(String configPath, FabricModJsonSource modSource) throws IOException { + final String mixinConfigJson = new String(modSource.read(configPath)); + final JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(mixinConfigJson, JsonObject.class); + + MixinRefmap refmap = null; + + if (jsonObject.has(REFMAP_KEY)) { + String refmapPath = jsonObject.get(REFMAP_KEY).getAsString(); + refmap = MixinRefmap.fromMod(refmapPath, modSource); + } + + return new MixinConfiguration(refmap); + } +} diff --git a/src/main/java/net/fabricmc/loom/util/fmj/mixin/MixinRefmap.java b/src/main/java/net/fabricmc/loom/util/fmj/mixin/MixinRefmap.java new file mode 100644 index 00000000..1c159b4b --- /dev/null +++ b/src/main/java/net/fabricmc/loom/util/fmj/mixin/MixinRefmap.java @@ -0,0 +1,137 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2025 FabricMC + * + * 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 net.fabricmc.loom.util.fmj.mixin; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import net.fabricmc.loom.LoomGradlePlugin; +import net.fabricmc.loom.util.fmj.FabricModJsonSource; + +public record MixinRefmap( + String refmapPath, + MixinMappingData mappings, + Map data) { + private static final String MAPPINGS_KEY = "mappings"; + private static final String DATA_KEY = "data"; + + public static MixinRefmap fromMod(String refmapPath, FabricModJsonSource modSource) throws IOException { + String refmapJson = new String(modSource.read(refmapPath)); + JsonObject jsonObject = LoomGradlePlugin.GSON.fromJson(refmapJson, JsonObject.class); + + MixinMappingData mappings = MixinMappingData.EMPTY; + var data = new HashMap(); + + if (jsonObject.has(MAPPINGS_KEY)) { + mappings = MixinMappingData.parse(jsonObject.getAsJsonObject(MAPPINGS_KEY)); + } + + if (jsonObject.has(DATA_KEY)) { + JsonObject dataJson = jsonObject.getAsJsonObject(DATA_KEY); + + for (Map.Entry entry : dataJson.entrySet()) { + String namespaces = entry.getKey(); + JsonObject namespacedData = entry.getValue().getAsJsonObject(); + data.put(NamespacePair.parseString(namespaces), MixinMappingData.parse(namespacedData)); + } + } + + return new MixinRefmap(refmapPath, mappings, data); + } + + public MixinMappingData getData(NamespacePair namespaces) { + MixinMappingData data = this.data.get(namespaces); + + if (data != null) { + return data; + } + + // TODO: log warning? + return mappings; + } + + public record NamespacePair( + String from, + String to) { + private static NamespacePair parseString(String string) { + final String[] parts = string.split(":"); + + if (parts.length != 2) { + throw new IllegalArgumentException("Invalid namespace pair: " + string); + } + + return new NamespacePair(parts[0], parts[1]); + } + } + + // Mixin class name -> ReferenceMappingData + public record MixinMappingData( + Map data) { + private static final MixinMappingData EMPTY = new MixinMappingData(Map.of()); + + private static MixinMappingData parse(JsonObject jsonObject) { + var data = new HashMap(); + + for (Map.Entry entry : jsonObject.entrySet()) { + String mixinName = entry.getKey(); + JsonObject mappings = entry.getValue().getAsJsonObject(); + data.put(mixinName, ReferenceMappingData.parse(mappings)); + } + + return new MixinMappingData(data); + } + + public String remap(String mixinName, String reference) { + ReferenceMappingData referenceMappingData = data.get(mixinName); + + if (referenceMappingData == null) { + return reference; + } + + return referenceMappingData.remap(reference); + } + } + + public record ReferenceMappingData( + Map mappings) { + private static ReferenceMappingData parse(JsonObject jsonObject) { + var mappings = new HashMap(); + + for (Map.Entry entry : jsonObject.entrySet()) { + mappings.put(entry.getKey(), entry.getValue().getAsString()); + } + + return new ReferenceMappingData(mappings); + } + + public String remap(String reference) { + return mappings.getOrDefault(reference, reference); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/util/service/ScopedServiceFactory.java b/src/main/java/net/fabricmc/loom/util/service/ScopedServiceFactory.java index c7d8dbc2..ef4d770f 100644 --- a/src/main/java/net/fabricmc/loom/util/service/ScopedServiceFactory.java +++ b/src/main/java/net/fabricmc/loom/util/service/ScopedServiceFactory.java @@ -31,13 +31,15 @@ import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; +import org.jetbrains.annotations.VisibleForTesting; + import net.fabricmc.loom.util.gradle.GradleTypeAdapter; /** * An implementation of {@link ServiceFactory} that creates services scoped to the factory instance. * When the factory is closed, all services created by it are closed and discarded. */ -public final class ScopedServiceFactory implements ServiceFactory, Closeable { +public class ScopedServiceFactory implements ServiceFactory, Closeable { private final Map> servicesIdentityMap = new IdentityHashMap<>(); private final Map> servicesJsonMap = new HashMap<>(); @@ -60,7 +62,7 @@ public final class ScopedServiceFactory implements ServiceFactory, Closeable { return service; } - service = createService(options, this); + service = createService(options, getEffectiveServiceFactory()); servicesIdentityMap.put(options, service); servicesJsonMap.put(key, service); @@ -68,6 +70,11 @@ public final class ScopedServiceFactory implements ServiceFactory, Closeable { return service; } + @VisibleForTesting + protected ServiceFactory getEffectiveServiceFactory() { + return this; + } + private static > S createService(O options, ServiceFactory serviceFactory) { // We need to create the service from the provided options final Class serviceClass; diff --git a/src/main/java/net/fabricmc/loom/util/service/ServiceType.java b/src/main/java/net/fabricmc/loom/util/service/ServiceType.java index e8db626b..a6d17ace 100644 --- a/src/main/java/net/fabricmc/loom/util/service/ServiceType.java +++ b/src/main/java/net/fabricmc/loom/util/service/ServiceType.java @@ -1,7 +1,7 @@ /* * This file is part of fabric-loom, licensed under the MIT License (MIT). * - * Copyright (c) 2024 FabricMC + * Copyright (c) 2024-2025 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,6 +24,9 @@ package net.fabricmc.loom.util.service; +import java.lang.reflect.Method; +import java.util.function.Function; + import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.provider.Provider; @@ -38,15 +41,39 @@ public record ServiceType>(Class * Create an instance of the options class for the given service class. * @param project The {@link Project} to create the options for. * @param action An action to configure the options. - * @return The created options instance. + * @return The created options provider. */ public Provider create(Project project, Action action) { + return maybeCreate(project, o -> { + action.execute(o); + return true; + }); + } + + /** + * Maybe create an instance of the options class for the given service class. + * @param project The {@link Project} to create the options for. + * @param function A function to configure the options, returning true if the options should be created. + * @return The created options provider. + */ + public Provider maybeCreate(Project project, Function function) { return project.provider(() -> { O options = project.getObjects().newInstance(optionsClass); + + for (Method method : optionsClass.getDeclaredMethods()) { + // Gradle property values are lazily initialized, ensure that all of the values are not null + // Before we try to serialize the options as json + method.invoke(options); + } + options.getServiceClass().set(serviceClass.getName()); options.getServiceClass().finalizeValue(); - action.execute(options); - return options; + + if (function.apply(options)) { + return options; + } + + return null; }); } } diff --git a/src/main/resources/certs/known_versions.json b/src/main/resources/certs/known_versions.json new file mode 100644 index 00000000..f8653c49 --- /dev/null +++ b/src/main/resources/certs/known_versions.json @@ -0,0 +1,682 @@ +{ + "client": { + "14w18b": "8403c9fb03b1e9c60cb6fb2f97e25dd60041aba4f0a62596f4e63eed49cfa9c0", + "14w18a": "8b18e1c6bcc01d9a96e44171e8ed05bd8193a2bb6a3c9c9f5fecce7492b04cac", + "14w17a": "db07fed9bed91d5de8f48c3906b87b7c05aa2b7242a16000c24009eabcf3516e", + "1.7.9": "38f8d799a9b42fb539ca7250e317dd6546910c8ac7718a720c11aad79780e8d8", + "1.7.8": "09d06a078aaedc075682440a5d87d473ee5ebfb35270aa8f085575f133029a31", + "1.7.7": "3e41fd81092a1e9bb7d3d268ebd8c8700b5906065974dc6a962ba8417d57ba73", + "14w10c": "542a846d2ca4975069230c1b8e3da143731966190ff307cc202fd75117fa7c83", + "14w10b": "02367e9efcbd9feff8c41afb9e1e2801076190821fd259de61d0d49137c9c078", + "14w10a": "267dcc0da9deff6a5e8ad4a80b3ecf1da0f17c9184b7c236c3b11e2350f92d53", + "14w08a": "5fefa25e18becd6d4a522b21488f835100dde75d1e20612ff1864586453f8ab9", + "1.7.5": "5f83b944b59c48ea7fa8f92fefd491ecb6d1e8d6c9b412fc849f6457c8cee27b", + "14w07a": "298f9b762fdd56fab0aa250385629f8971cebc7054c37d48c8f3f65454f62116", + "14w06b": "f1e87881adf7e02af3dc69a2f57631c32aed31d709b7820037771af28faf55cc", + "14w06a": "047db6da1fb6a5d6d9f8500c4f37fc274d351dc3aeec5b4b5a5879565617265c", + "14w05b": "7c9d9fbdcdca7160bef96cdb1ada2e8cc4edad73d0f996e36305a79d3a1790bc", + "14w05a": "893f6b8129a1fdee79c0aec3f912a11a6f3af3240debdf75a685aad2a00b118b", + "14w04b": "0278bdc4c5ba4e7035555fc5e5b90b8d005478ad12be32613ecc6fe03aee4da7", + "14w04a": "a09bae6167d85fc4cabef338a5f9f4b710b029079354eb9c36097fa9dc137244", + "14w03b": "f027f1aaded0445f769aad31770cc93abdeb6bb7b66cf23b1b32d68feaf638b6", + "14w03a": "d640a5a69510251fe8417cb96acd9f2ee2e302e671adb33581abcbe6e27eb1cd", + "14w02c": "1189a21527efd3e2bc9936787d6697b865b84f917e271fa29edd8b1e6ddfb66d", + "14w02b": "02080a3cea941c7fdedebada32315c62a121a2d372310f80ba119c4ba2fd8d2d", + "14w02a": "4b08784409fbf168a8d27a74f8a4c992fe50ace58021434a1fd2d22d6df2bc01", + "1.7.4": "c51494c52a612648cbcb7ebcdaedbcb61f6cd287f6215f90113a43136cb62526", + "1.7.3": "8b1d90ae662b235072d05dbc27d38ae5fef8b322a21d10302703fd798feb05c5", + "13w49a": "63e598023cbdbf77f4ec08b7fae4c25ab276b6dd17f3f59ff1086d1334ebdb00", + "13w48b": "d606b7eb41bdcbedb678bb5aa87d05e9e0fb13b31e16dac845ea658b3e970d51", + "13w48a": "92edd6cfa4ec6dd46f361bfbc2a537803f92177c80a643def663d2c7be376385", + "13w47e": "0cf206856ebaf09f08fa27faa5cedce3230fe837b5486806ea8354b952aae7c9", + "13w47d": "19d5f72e2aa1779a02335a3a4692c1a1a2a04be8d0329c15bf675bcd74464fdf", + "13w47c": "0a69ec62aca3a4b5c6f2956df4cabe465d4a7f6f2fc80a7ac1444936f07a5dd3", + "13w47b": "11216038d4c85cbf1cb2d3bf61808b0d26ca287b064b5fd8d511e11323414bf2", + "13w47a": "e8b0204b3d35b2a6c381417d8a32d3163c9e98caa4c733b4e9914989fecc7282", + "1.7.2": "507fb3660e77ab1a3000424d9b08c61606ae1e5a9be41caa3728bddd9597365f", + "1.7.1": "0ae4bb959d5d916ce7a6a83882aa154bb4233ea8a3624d3fb0970a5fd3720fa8", + "1.7": "af3ce46c2b91d5ae61bc5c590fb8c72b00b538875c8de225e9f2f2dd465afb50", + "13w43a": "ae0fe25f37d971787e73116fd091c1e1edbce197552bf15be70862ac7b4e5c40", + "13w42b": "e5ec51369856028631bb973cffdb13e5b83ba3fdd466314469ddedaa5c1725ba", + "13w42a": "1c1d7ee75225aedba2c04f871f7f1de6261f42debbc1219024bb2ec4af0e47ae", + "13w41b": "3e9eaef7162673b5fb062017ee02e847cc7e53395bd9366ce1d7db95d808dd6e", + "13w41a": "ef428b96e3550b7c733d43544950553971503bf99dc68942971d35c5269d73a3", + "13w39b": "cf804e8ed8c0589df08baa76f2f77eb89046b1df51b5e7e7a6ec198012a06bb1", + "13w39a": "a4cbdea94098d07c30c2ed4941344ee0fa2019edfc6e483826cc7e075e9934e1", + "13w38c": "d28cb5d42a8bd4bd9e870f13ce6f6cb2cf5427b43efb631a71751cff56c32288", + "13w38b": "0ea32cb82f5f49db167987907e4ff14e5358a42cc202c794a8cd4525cc286009", + "13w38a": "941e72596782c260e2cc8687757c141060b3f3d1b77c1a75e7c4e4e2b04504e4", + "1.6.4": "f4513e51c766cdd1d32ca8acd0835d7e16e2851ebea3f2a3b4ce5ae696baf3ae", + "1.6.3": "e6dc707af0a0cd050d5f66da38476c81f2ad6437074e46c852bae410ee2a30d3", + "13w37b": "998240601ae985884f7899933410188075320e432ca2257748b56f17e8d09770", + "13w37a": "61252567fe990c23689a1bbd3514d82460442b3c6822dafc2fdd06a2e5746809", + "13w36b": "00e81ac87bfe31a47f2b7f5aeb532649e0b75b847a237b3bcd2f6b101a161667", + "13w36a": "796e2540899dac97bcdc873a84f08d74a41212ec5648e5f70ae1e87f75dcf692", + "1.6.2": "08406549a47412294923705bf023b1080ae14da22ec2770d16d920ce420c3092", + "1.6.1": "1d8a2945e196db1226930ca51f20f8dbe2d9578884949c569c5cc8dc5fbdcc53", + "1.6": "1362475725c495e34fba6cd8bd10af1e621d9425180c8428b9cd8efca168e611", + "13w26a": "71e49cfd67aade77449ee898bdbdd719149ff142be501d29312d702db107d530", + "13w25c": "26c2259621d151b29ffcda4b4f2f39d044b835a4d5152cc3f87138b81a6ab080", + "13w25b": "18d9e3ff2ae3184c5d2be0ea6ad5ebac496e2ef8454a8911b036b078087c8c35", + "13w25a": "8ebc73a02b8b3c952d678b9cadd3250bdf47218513baabe6b7529e8871e19b9e", + "13w24b": "bfa6fc3154afad3ce6f3e416f7bf8b7dcc93f21b8d67fc1cb8872346462e73c9", + "13w24a": "391edb763fa015d2893ddf9345ffa676322e939719b16ff77fd442fcac873282", + "13w23b": "53ba4cb9f0e2ad5ddddb6cebddca18cfd85db3968ae8b3ac4f36ba93169faeac", + "13w23a": "f99593e38e106713d022c43cca369ec08187a062b8112b34cfb6f484534d342a", + "13w22a": "cd3b4e88804f7e6ba6ca604f3e53c49770d8b5a967c9f069c78b637f9a79d407", + "13w21b": "6790b306bdc554d5485edbee9cbb434707f8f45c85d36041079d80588dd2d96b", + "13w21a": "7a267523ed4f42e654bd80817368dead64b3e6c3a4afb8f4b2070d87441a004a", + "13w19a": "0cc59566ee89deb80090c8752c35cc532667921332ed4b4d5b2b136e6131388c", + "13w18c": "45d3b2ff10dfbb55f5f4d719b26cdd3c5e232261d0a3d9d66c2536f7a1c9695a", + "13w18b": "bc54c2f86339d489a0f20376fbb23f19dcf8464645a675cf0eeb623b249c2934", + "13w18a": "1750c66d6c4aaeb75061ef1cb8f55821553ad469ea3a8dbdf1f27c70af2ac53d", + "13w17a": "d9b3725b86e69b04eb55f24aacd92cb339d77e1083f6c986acfd5ae8ae1511d3", + "1.5.2": "dc0fa48951f61c12eafede5e46e248aa86ab86d1e4c28cd880c1d9c348ec44d6", + "13w16b": "9a7108fbefc3e4a85b82100eaa57f725668540d98a5b0160a55ada148d5e80a7", + "13w16a": "54eeeb77ef7813415bf31551ea14005d612d64fee0d04e5b543bff4b5c2bc787", + "1.5.1": "e3164ae3a18954b0e09aefa20afd9c6713d9ca3b63d603fc8ae2502026149922", + "1.5": "4ad020a9c3fd95850370a45ea3511a8fd98728f7dffda68007ca2e99276b67c4", + "1.4.7": "c5f4972bd775e03b1d3a1255d962ffc00f8c1f6ad98a30a149d5f107153c0da6", + "1.4.5": "b7f06f2019ccfe5d8aaf7e4fb5f5e8e2bdc3ce6509c4ab4f559a5ce4ffe011a8", + "1.4.6": "c4bb5fd8f98ac7160b34fff7a65f809e0930ee39c5d94f463f0884a6477b25ca", + "1.4.4": "9e155ec17c574488c0bf361aedb1c485d1f08b31fa9d21ddcbcdd441a0d41f53", + "1.4.3": "06d60d55d1c16f60884f0def32fa249145bf7e0b8f5058ad885e3aeaecf798fb", + "1.4.2": "199f44e06dfbc20567f9b6db0a965776b38208359a62956b43906bdc51118cf2", + "1.4.1": "773e2bbe53db57ff8ba9dd530ad62db546f6d9f4c37b0dcb7631e337d26641e3", + "1.4": "3a70045649d016e8026e98ed0488b1578910b9bef0dd48a744690501b369f43c", + "1.3.2": "24ec25082cf2bafad90518b5e24d23fdfec55f1bff7f2ead391ab40501eb8354", + "1.3.1": "26a5586a1dd5555e918813284ab6cf05af1fb997f66b99c93035b03ca066cc52", + "1.3": "a71f49a4a9fae65db94da535e5b0318606151927dcacb68908ec937c71ac7b71", + "1.2.5": "c1c3740a912ef523a8bd46605ab5708643498330140cba175c7ce6f177e468e1", + "1.2.4": "ece050b625d1836a035197ed312dc59caf26429f4b386c81bdd84cdff3ca80cb", + "1.2.3": "ea15a60614f96a6bb9aecf865de213635c62d664124092d84bce240622c4a9d3", + "1.2.2": "5cc415005abc4931238c1af056f7f7aa650990951a58142d7ee855532b2b0fd8", + "1.2.1": "3c519ce303504c2f112f6d2f94f81cf1570c83ce81348df7db93ada317e33054", + "1.1": "e065e9681462aa1b78f3cc589ba880d9aae51994bcd4af8c67bcab5c47391ca0", + "1.0": "136e3dd54454e96175badf50bee2cdebdab9e7d66fee4fd6d135f39ead99eb58", + "b1.8.1": "3a61a9cb5b8b6cade30a8bfe21f6793122c7349d0c3bbe1cd0fc2a93add36f4a", + "b1.8": "5d861a5ae1ada4f659905dfde3ff76859a8fd687b2b040b2cc97b0e4e43d9358", + "b1.7.3": "af1fa04b8006d3ef78c7e24f8de4aa56f439a74d7f314827529062d5bab6db4c", + "b1.7.2": "16c8f63be9e5039f2ce974974a5688d772e93c9dba53731deac4b2434efb8d12", + "b1.7": "43efdd70ac7c7b1b7d5c2ccb6c1aad8d9fb7af110187fb4d1070b21d04648b7c", + "b1.6.6": "7764cb6cd9832d3b270d749a9ebf44e5ec8297faf5f14753146ab70cbd316b66", + "b1.6.5": "b203d6d62f5b97671d262d88c7bf8891b8501b401fe9ababc9e4ee2d960cd0a8", + "b1.6.4": "02d02cd94905c4a2c771b6c1696d236cb38150600a3dfb9946393d3e1431df0d", + "b1.6.3": "a88794d52654321527669ac38cb39764ab41cef804ad98eae8e7e00861bf845f", + "b1.6.2": "b3a6cff53774e1c0f7e9faee014f49728b72a7b1d1301fc9d39eb12cc20fc310", + "b1.6.1": "5108cdc9f7f9c5514ad2d16ee1b2a7bbfaaa13f1816aafdcd24e9ee975a4707a", + "b1.6": "a084d22ea2c872a564ebef4e5b99be9ec0727e3f7be6cfb4f6cb2f9dea4522f8", + "b1.5_01": "088e96109679889c62cf8a91c499bc44ca292252872c242ffefcad95be077618", + "b1.5": "d99cba867acc5aabf383f8252f047d5d1e9afb4f531c9b7205d1b845cd4fa7f0", + "b1.4_01": "36170e61a2ce9dcb6dda5af63440070d1bad82c0846d779f25c2fb0bdc14992e", + "b1.4": "c48c430cb60d3197ca4b9c0aa33821ea56fa10edba5a10a46b77aa46883a6908", + "b1.3_01": "e993b789dd450e1538667def644e1376bd702fe26eebec3197598d5157042684", + "b1.3b": "335ed6e306324ba4b7eb8ae16479839ae5678f0c1da62757e515d640801e1159", + "b1.2_02": "c803cd9c4a0815b98317efaee3dcc84863049a5a3b5d250c56776669c8996131", + "b1.2_01": "55df77ca089baa030c5a4e62905ea78f755485ea3a8e968a2e0ff549bfe13bc0", + "b1.2": "a0996c12b0bfd5a9c85a03542cecf02f573f4cd4dd60f16b05323e897750cc9c", + "b1.1_02": "3439c894641b07bb1b31b89209e9dc5818755353d1f646ee48e583610d6670f1", + "b1.1_01": "f5a4cf4b631c67cafc490dcb43c17dbbab0383d3ec1ffb946f0eb217374a8368", + "b1.0.2": "82e28d25b493ade968cc0015e5b1c2e9145bbbe247d7f1ea7d603e7ddfe056bd", + "b1.0_01": "61829173ce6d311246db4f68bf649bab5ef9f8e29c436cd7c9f2ae494db7f1bb", + "b1.0": "ad62df9cc678bfa15ff981f56aa8aa7ad0d60a1e584c39b981f9ec089ed450f0", + "a1.2.6": "63276bf2617068ffbaf2a1992d1f06f9339c96c21991eafc9583d5f3e7074b9c", + "a1.2.5": "fdde933363df3a1d95a76d180dbdb14464f8390f954d272613ed25ce42155787", + "a1.2.4_01": "047a05550de80c186a3f23d7c4c8b25056dcc0a775e73f4e4d919f39d8bd1c37", + "a1.2.3_04": "e80e49d2ea895198fcd2d3866e4541a4d464aa5263f3923d540cbaf1053f9eb9", + "a1.2.3_02": "bc23d23764761484472060a49353598bbaa33d574e6953015e75e192e03bb9f9", + "a1.2.3_01": "17c0463e56800aa257de3e76011d7f3db015b44fe2f3e846275fcee7f6129571", + "a1.2.3": "76b99d9a0d884bb0de99257740fd391c319abebc2ec30174a39bb63e5ebe69b1", + "a1.2.2b": "fe33a245d0c1a995ffc82f2673436029fd0c4f04b9597fa094a1fd6157cd65d7", + "a1.2.2a": "054c311b6ac2181f8d362d579ab8d3992ebff1867f19c480191e417c030e531a", + "a1.2.1_01": "7eab51320f26cf68ff9b06fcf34b64ee60aa7aa0ee5c0421e8fb0a265e811c18", + "a1.2.1": "7eab51320f26cf68ff9b06fcf34b64ee60aa7aa0ee5c0421e8fb0a265e811c18", + "a1.2.0_02": "cb4d712cbbe51a6a8b375ca771fdc4ff3fc70bc7bdd640e53a020ae7e687af56", + "a1.2.0_01": "e658a3a6eceac9eed89bd1e2a00768f72dd5aa8ad085fde59a9e1cff2f3c43b9", + "a1.2.0": "2139af187a74e4c60af45110665f38777fe6df07da0b72930fa08245c732e149", + "a1.1.2_01": "167a9cef74eb60417ab9670df10953634c0dba8371ebae1e44588e0ba0a7b07d", + "a1.1.2": "6beaee2c909ed33591a985ab6bd03d6962958c0b3d0aeb7a7a3cc144adf7f50d", + "a1.1.0": "0723b15a4b56b202a46574f2bf039da2760f838e8ea3ce5aac683b26dbf38e8e", + "a1.0.17_04": "34105933da180843e575909978246fb373f2db53b00905b92c8110cd19fad6f2", + "a1.0.17_02": "9505e19f30b79ce2964504de29594e18afeed7fb5dcb3f0960177705880dde0e", + "a1.0.16": "fd14110691b14ea51f9320a949e2b3c1855e38af2668bf4e743c19b08de00234", + "a1.0.15": "c9018315807056c610a30ff08d6d9515712a50997d6c06ff7d923676d61e87ce", + "a1.0.14": "0859315d26dab43e004454f19f51301351e826acf3ffaed636d7d2a5f6d5a584", + "a1.0.11": "a11120202e2ae0b474a60c2944b03cc5dac5c6d659a0926f8c881619de70c17c", + "a1.0.5_01": "5ef06e9c7e0421505e3cecc732f2d192a9e04b5a55840d91418a301bae375c70", + "a1.0.4": "e464928fdfc445de13b91b635d31c1aadf212c656f9d7a9f3ae54da5c2783f5f", + "inf-20100618": "26c18bbdb55c0c7f5858a8094ce082a76f28d76e7f6f3a035383129f75a365c0", + "c0.30_01c": "3bfaa9ceccfd49f62d4c1863d5cd565c24de1f83bab982a38e4811ac369993a5", + "c0.0.13a": "b3b5a88834c2351c948950c6d60c822d0d1d60f88c5adad4794f8ad6fe5b3a33", + "c0.0.13a_03": "40595f0c37adb1c581d0d4d836733806ff75af977836f815f1a5600fef0a43be", + "c0.0.11a": "2ca13b43ea3efc0388c5c1d4613854f6412f97d4c02f4b18729f648e46f02d1f", + "rd-161348": "cb8bda0074ac44d8d26bfe0c101f08edeb2291915f32ad1d2909831cd84934e0", + "rd-160052": "bbd6e24a276c5082f8aafae152bed450c161d56ff776472821de56c0daa7fe1e", + "rd-20090515": "cb8bda0074ac44d8d26bfe0c101f08edeb2291915f32ad1d2909831cd84934e0", + "rd-132328": "0627a893265fd697fff165626e000a2640c6a6ece6b64b75c3ab0194f960ed8b", + "rd-132211": "407460840eaeab01260b9e7951bd518f7c31e2e3f12a352e436502fa7050a6e7" + }, + "server": { + "21w38a": "56ebc9c2a2997a983df5777d7fa1e2d25ba38fa3b740b3cd1db8308819449e5a", + "21w37a": "051cf95cc9aa199f4872bcfaf873ab39841c0a266ac09093d2cad6c93e14d7b0", + "1.17.1": "e8c211b41317a9f5a780c98a89592ecb72eb39a6e475d4ac9657e5bc9ffaf55f", + "1.17.1-rc2": "b54452f67071054983935a02ec344a5d9e0c7ade5a7a4f7966c5f8cceb62335f", + "1.17.1-rc1": "643c414bfa6493f2e644b64bda0e558fb209a12bde27bcaaa508e2976006660a", + "1.17.1-pre3": "b4a23854646c7631aa0e39520184e2a36eaeb4674139264b480cc46d60ca88de", + "1.17.1-pre2": "dbe08754799fc8661f0140d34391885f367a64048437080cc2b3048b11032e19", + "1.17.1-pre1": "c710f543d60df1d0218848c34ba240044dc2eaaa7f210d825b83eb9a5c6af9fa", + "1.17": "7b390d8d9f6b5649b226d82686aec7f11bd9aa4430bb5cac9072ffd32f3c1f4b", + "1.17-rc2": "038782cd1017e75513c6954086b423e227691e91d76567ddeea749119af3756f", + "1.17-rc1": "64a0f55142ff835deac34b7d949f4173e01791e13bfc3825561b1f72156603e8", + "1.17-pre5": "b6ec94e542dc0783bfd76f822f44f0a3434a4dbc7205c545e3c6f42a4bb9b323", + "1.17-pre4": "885a42fb4243b6dc4d4f6fb4f32797f27b88bc74e402b1ae19b69a77b789a257", + "1.17-pre3": "3db83b98c5afb4a49ad2d5e8b1dde30db8edd35d470602b878e5c4b01bfd30a7", + "1.17-pre2": "58bc609735b32aade48a857b95ec92c16202eace828e2d6c6b5384e68083c5bc", + "1.17-pre1": "f2e66bc5d818f1afbf80d46cbcd178cd14fba10ad1ce21330fcf2426e5295b60", + "21w20a": "ea47315ba2b9abfe3ddc08a0e0da4713434b2a3cc8a404a907c1e12267ff6120", + "21w19a": "4242ddb3304917206ee57d4f8733ac6f271bfcd789c9b93cbe5ebb684bba366c", + "21w18a": "991df5a799aad616244022e52ce058c1b52c48eb67c2388f0e9bf514aaff8073", + "21w17a": "79c5fe68415b0198e6716706cdbb7f900b146b678af2883b5ca6edc42e783207", + "21w16a": "6d5fab818c5928c9bb9060f7b2d9968110c620a62def1ce92aaefc39ddc1a4f5", + "21w15a": "afb63475c6e2ba324fcdc4630ae9fefdc422ff86bd6a962c9ca3094ba6a6bff1", + "21w14a": "03467ee277f483cdadfa2f603e6473105f277b0f3fd5f9d5c77ffb2c34159a5c", + "21w13a": "fcd97c853dc0edc5c07cd4753b73bfb9a1ae72c616ba811c044f6075272eeb68", + "21w11a": "b5d0f819e6361327ca96661eed042f92751ff282a137b03acfca763707d6c7e5", + "21w10a": "cd8f9846c42332d4859a35f7645e325dc07b59d4ebdce76a3ff50d73a81b3ab5", + "21w08b": "d8ec1e31705409c641f3ed293b26207f62091b524e3364dbea344dec0a63f386", + "21w08a": "59ea0491b83285061bcfd8ecda579d2dc204f9182a093c4f1f539311a046f3bd", + "21w07a": "292e9f641eaf137b51cc32e68000ad96cd2bb6b6e89b3f31a82de2e2501f0f9a", + "21w06a": "18dac09c4014641ddd6bd17c84425a614a27290dc9cf499671750f7746a4f9f5", + "21w05b": "91583bcc0639113cec87b536de9d94da88cc8381e769d97c319b98a8a14ea17e", + "21w05a": "2c833407945fb4a21f845fe1e2611bdf940573ab6d2df9a307b1a6d3ffdb7079", + "21w03a": "ca3c83d4747ef8e9f458ecc5814561c6042095842aa4a7b604101dc1dca03e05", + "1.16.5": "58f329c7d2696526f948470aa6fd0b45545039b64cb75015e64c12194b373da6", + "1.16.5-rc1": "77417618470294df7df7e10ee1969441287c32f9a6807ced290c2b048fb3808b", + "20w51a": "311493da9f55ee4ba3d17f6615e44167abd69749f917758994952a17a15c665e", + "20w49a": "374f5a9fcc4d72b3a5b094fe04e7a075fdce2d27558d4115ad323d74fa6e577c", + "20w48a": "cd5167235cee40ee8b5487015b0e172964472c1753b9034de19058c31daaa332", + "20w46a": "d66328d61a0442b37ad7e125f2bf69ec4f38295e525e4d42c83bf5d990ffb764", + "20w45a": "0647b8dce42f02c807c7bd83a06c6d68845f917bb9ac1260529a83636b403675", + "1.16.4": "444d30d903a1ef489b6737bb9d021494faf23434ca8568fd72ce2e3d40b32506", + "1.16.4-rc1": "78e7cf12a0e07ae1f5c314720b36cc44ac936fde89263d57927dadd9889f18ba", + "1.16.4-pre2": "5131ae739be591e23e779beca823bcff1df8dc764b6eda2abec3c7f2a9c9df27", + "1.16.4-pre1": "0ae53adc1154fda746dc373dfb5af7b4d1c253f418c84118880d1840f362c468", + "1.16.3": "32e450e74c081aec06dcfbadfa5ba9aa1c7f370bd869e658caec0c3004f7ad5b", + "1.16.3-rc1": "e0b7fb222eb8c4225bf66441045f491da06de851382ece144bcaff38044ef88a", + "1.16.2": "2902ed3ff84e4f810a2c0620c6b6df9c3ef8488b272c61274d5eac2433876f39", + "1.16.2-rc2": "fe0cbac64b7d62ccc50519aa4b8c58a780d8aafca4629d964bbab9903774a37f", + "1.16.2-rc1": "bed63f508d1aa1dd7293ee7d965c62237ffe9e11a753164ede274759758cadfd", + "1.16.2-pre3": "fd81170c5abc101e41a79a218cca9ef59d44821a7cea540b3704d1a79159f098", + "1.16.2-pre2": "a35a434a4bce236c95828e95a4814c2c2938c39feffbcb707bedc430c08eb39d", + "1.16.2-pre1": "3f0eb69be02920a4ba5006047fba2534d3d2142e90df25de4fdee105f7e4894e", + "20w30a": "127b624cbd83fac5931f753ecc16ade2c6324d163ed6370c75b3561ed2381799", + "20w29a": "b8a34501e01b1f2e5cc7e34d90ec8fd54cd2f99557c7fb464ca5bbb6ab4d4237", + "20w28a": "c00f686fa78732a1446fc17d7c3a76695df64b8085005e6c816aea19622b3019", + "20w27a": "a01d3784a033cfee18eb43069f73f450f8a57ef64615ccff8d819af4d2394d85", + "1.16.1": "2782d547724bc3ffc0ef6e97b2790e75c1df89241f9d4645b58c706f5e6c935b", + "1.16": "7d2d2d127b90baf2bd8fc61092cbff42bea1bdfae30a2838f45edb31294979b9", + "1.16-rc1": "372963701a2a7ae47eccc8bf9879a11f2e283c2ef5f33a0275c44aa26daf9883", + "1.16-pre8": "ac4a92fad98af7b65e09e6d4a833860242b9f4938c94c74b8e7661ed5139ab5e", + "1.16-pre7": "d99c608ab3d8aa84fe9851684518fa97967a50d8d2c7ad45b449c4ed72e72a73", + "1.16-pre6": "b33afa7c5ff0586334e2961cc5d81700a1c5d8e7bfacbc155534d41fe4d20e4c", + "1.16-pre5": "cf7999b76c7659bf4a277943b61c0571b8bd7c3e8764f7bbce7a16d461ab7b2c", + "1.16-pre4": "70da4865676df62aae67a897b47684efcfbebef491f30bdfde0962e821b03727", + "1.16-pre3": "9c9b0163aaea79e9c437e7c27befa27482ec21284a77e0f07b2e7765d44e1b00", + "1.16-pre2": "cdba37b0fcb9ed958cf999d3f763d146738e2ec339ee7c9052db053b47e09f8c", + "1.16-pre1": "c9c62c1410601fdd9b14cf0e2545251577512fab586bc75c10c748ee1d9a8ed9", + "20w22a": "c155d45a4bbf6150d7b187ecce11eed199040299f9ccc789cf3e6d89e667a786", + "20w21a": "68fecc31f9ee1952d099269a946e7be8742b04ee90a447e9cb42dbc41ec46883", + "20w20b": "933a424ad1e82d33b0d782b54158e877969dd0893329f190495ca3ba287e8358", + "20w20a": "67c6a4ef7a0cfdf1212b6d9ef28de92a855a78a9b053e3ef5e6bca5e5bf01a41", + "20w19a": "bcc4c321cbe9c1d95ca4d46677f93488a10549337d224495ca2cc1b2fe01bf17", + "20w18a": "9deb9a207c9f4ecf43bd16fbd7d948a3f85642981d65e62715312a4f9e36114e", + "20w17a": "fc40e4759636430547f22df66d6431a99abe16406d29035283d8fb60b7286a55", + "20w16a": "3026f0a3e750b16b5a08c1e7a5172ebdb908ec662af52759228f00d1275a7c62", + "20w15a": "eecca6cee337657ece906dd0055b275283e0b9bf82e9703bebc2ad88b45328a4", + "20w14a": "0dfd9a8d5b09f0f5abf9b297855c8332e490b266c21e77ccfc3c60560dd1e5ed", + "20w14infinite": "1b31cb0c36471632c97b3ea30962c4b8f210f73094a81d9dab8cf9c6f15871fb", + "20w13b": "66fca31e1d3979161e2ac5114647c44faf3347e2eba2a1793c6769e71d2a50da", + "20w13a": "f216eded69f2f22cfbe3c3ed8baafea4a33b2c0f94a04778f09d2dc20236cf86", + "20w12a": "461a870208ff3ff51a5327d6067c8734f9b8cfef9633a70d3dc550cad31b3e17", + "20w11a": "bc2de25797bce59b753507cc9f3cda660c1065999a46c7ef16c3e1f51abc9413", + "20w10a": "fd52aa742e806c6fcfb7afab7f7c94528ff369995df47b474c9a6e5596527887", + "20w09a": "b5417d9821ecddb112eff4dfe5b5b456c2f000896518b10f7ecc0f767600f531", + "20w08a": "fec35ad3e793348ad344afb493cb22d17938a98caa27913da4c37a9782a98e74", + "20w07a": "16def0cf2d6db7f251496772ee44dd68c5af0c71512facd29765ff2ca16852f5", + "20w06a": "cbdee7195fb76e87d39d57fe07e6e10a82b5e9ce376129edb113e2b9cb6ce7e1", + "1.15.2": "80cf86dc2004ec6a2dc0183d1c75a9af3ba0669f7c332e4247afb1d76fb67e8a", + "1.15.2-pre2": "7271730b514ae2520bdefa4fc58614c40511c43ac0d9079e664ff68811226c30", + "1.15.2-pre1": "0452ef9adc2a75394474d7fbd84a043f24659e1a8ae885c4b1d61301e53ca858", + "1.15.1": "a0c062686bee5a92d60802ca74d198548481802193a70dda6d5fe7ecb7207993", + "1.15.1-pre1": "6a2a5c42bc79b24f13659fe90a37b30d470c4011f3477bdaedcdbc429ecdc548", + "1.15": "e0fe1749263b5ec211b358b598b46e787645bffa8411414f0c812a92bdc70c84", + "1.15-pre7": "4bdc556ac0deec5fee4d1ad13abbf5ba5798992f60381747767f33f6c3262653", + "1.15-pre6": "d574de39dd9897e3d6c616a04313fa54ac394c027e25f212defb99e4210f222a", + "1.15-pre5": "1a78bed6ad7c6c4e2f78ef5552b1085a98ace4190968f0d78a1663510b0116d6", + "1.15-pre4": "2313cc6f9e2b6d5111c513c68fcd7162fce255f836e67ac21473dc3a5c812924", + "1.15-pre3": "549bc3993c77ca5cfafbcc224b66dda0d8f01e194cd3df80daf7cdae66632934", + "1.15-pre2": "3a4bd1e1635e3cd56abe7903036b1fb70514204ca4f3d9e4195703e1a9cc324e", + "1.15-pre1": "7c9c58700f70bd8be4e07ff9ba10e6b0eae64735e7b7c784f277dbd7d26e9f61", + "19w46b": "e108ffa6fab53c29d159a3d38ea24fea0f5d2f7783dbf707a4059bc364748604", + "19w46a": "9823fa660bc1ec137f1b8f166267a36ce355dcd20a64f48e1ad36a6b915e3551", + "19w45b": "6d4583a47adbc27463e173880cbcaf55a35537806857105c7eafa7f808c73cae", + "19w45a": "76caafcd6f41017f2c53409b22927a720313ed2b6e6600dd2fcfc3a3569669d5", + "19w44a": "36f8714c89b3f2be48a36bd8e57a751abe3ad6b32bc7e5431cc8923d1f1d774a", + "19w42a": "a729692962823fbede0c86f03aa9e15517f55e7b99ca970415433263be08a88f", + "19w41a": "dcd0755feffdccb93ffabef6a70c85992c3549a0f3868a38187d6b05f7137820", + "19w40a": "6043b833358370fbb7455ab66f5384d37ce8efd5df1578f1426aa389bcece864", + "19w39a": "0ffd910953ab50a736dcc0ab214a0659966713857422e9b7211440b7b181a427", + "19w38b": "61966431ad3d6d70f01582d0e44a6fb88d397da45038b14e08739107fed27906", + "19w38a": "fd23eb1dde29585ba9fd483b13efbf5cbf0a363178fc7207d2754efb8fa68dcf", + "19w37a": "c5d58c9ee416dfa2ffb5523062eeec002048f3a3cd1d3512f3a1f7938a43278c", + "19w36a": "392ec1912c4ff94db122584a32110e2ac4b2a7bc0ac7ce01fc7fb956748890ae", + "19w35a": "5a32b44741e23f43aad504cf83be944b5c5bb75cae1d640f7ecfb7f7e339e744", + "19w34a": "e90576934e580b87b8467cbec0c2a8614e8ed4b829aa4a409d89b7442ea610cc", + "1.14.4": "5ecdedab3a6e129321a444490d0a467c25ea702a24a99cebe3b6aed41f8f5729", + "1.14.4-pre7": "23aba0515ddc24f0936461275651f25ab2b94c927302fdd58cbddd40b2114260", + "1.14.4-pre6": "f18763257d0f23e17906c1a45c1f0ccb123ea68b6b916d2725a8516409cbc110", + "1.14.4-pre5": "b9acac7a5963bbce7ca45d2dc70a4444b69965d007795d32ded9f5beb680aa13", + "1.14.4-pre4": "4b280aa3bba0bf8213c7347daf043bb2943d9a692da359beee841a9149bd3e80", + "1.14.4-pre3": "d86b1ca6eedd93c3b6c5768ab9901176c95fe07976ce949e4d01354698ad0b53", + "1.14.4-pre2": "1f894e747df7106ef604d9000462e5b8a0fb8347fcbda4ac72e534fec3f704f2", + "1.14.4-pre1": "f326a95cf33d4744258da423659dd4feb06d4c5bcfc1e7d5a3282063b8084102", + "1.14.3": "942256f0bfec40f2331b1b0c55d7a683b86ee40e51fa500a2aa76cf1f1041b38", + "1.14.3-pre4": "0f14bd6bcfb987bb98e1e445f1eaf0a330fe8c3f0dce577707bb6611e600af53", + "1.14.3-pre3": "4230cf4f2ed3940dce187cbc6d6f5ed5286b3cc0ec51cdb190bfb0f696b6a563", + "1.14.3-pre2": "6285e75d6d63e82ca07308369465640b5570eda46cfa28aa63dc47a83105e86a", + "1.14.3-pre1": "ca7ecc41eb29dfda1fec8a4d0a2122b1550ae8129aa7c6cb3e831140dcf90438", + "1.14.2": "b47fd85155ae77c2bc59e62a215310c4dce87c7dfdf7588385973fa20ff4655b", + "1.14.2 Pre-Release 4": "8be46e24663acb8a7905029d352af180b39fb98ef7123f609ce2ad208981340a", + "1.14.2 Pre-Release 3": "efb78b0d2f20ac0ec4ec2a27a1eeed7c39d6324060950f8f17eda81a32cf5329", + "1.14.2 Pre-Release 2": "33589482861ad3423c36625f76205498d4e55796956d44f20025ab9e972d34f7", + "1.14.2 Pre-Release 1": "47ff9fff64aea5460c1efbd4dd62c7477e2f28ecbb357edadedf7da558cc1cc5", + "1.14.1": "f822f0b730b7e1f05fca84248a6873400bac4ca449ff6762a55cab3d68b1f03e", + "1.14.1 Pre-Release 2": "0b8271aa0f4c0cdefd311d4d878747405efe60c95710e0a93518f1bc996ced9c", + "1.14.1 Pre-Release 1": "6aa6978e91f9b89c9ce4e878e4cf93ec3d4adffa1fdd276924ea544ba282cd28", + "1.14": "671e3d334dd601c520bf1aeb96e49038145172bef16bc6c418e969fd8bf8ff6c", + "1.14 Pre-Release 5": "dcd1365d9031c17c64a1aca227e0ea1b339f92f162b83aed4c30d784fe3dd690", + "1.14 Pre-Release 4": "008f97dd87b89afc8eaf672e72bf3c37ea4d7961cadda8fe502b51130c418136", + "1.14 Pre-Release 3": "b689a062f9958121da1f656862a5239c3ab70169f6e1851f824621da78636db2", + "1.14 Pre-Release 2": "c2868040da54c422bcd037aa139526558b0abd0aa9d25b995384b0a9322b60c5", + "1.14 Pre-Release 1": "89b5b5fcf3602657b65cacd7358923fb0a921dd9e3aef0a57fee89f3acfea6ef", + "19w14b": "dffe87aee391720976d3d9badff28f56886c50fc6bd9cc80e4248a6a6cb683eb", + "19w14a": "16f04769de4092d9fec1285fddc9d41432f09c93acc88eed1b817204d5109624", + "3D Shareware v1.34": "90d4fc6471ff11ddaff3f981327f851e26891c184716af74c84c6e2fbfd22e67", + "19w13b": "14e08085fc7b42936a9d7567c821cf94b4fe8b4e631ae71132a71e8e8cc189b3", + "19w13a": "60c5eb086681dc5b0f82b42ae10f940c6215c69e1dde558356cf04c8c63a03ce", + "19w12b": "db02070f79fd42d659bc2de8e373836a77504bd4624a62acf1f6b61a6a967614", + "19w12a": "7241d81af90bc685ef1b4b500119cfdbce97f60363a5286b863b00102ad33a2b", + "19w11b": "a95568c5f2255eb3ec1b6125574db35f4f0eca1c1ee1276748d2019d277d17be", + "19w11a": "c9326a467b8d503812035c9000fe527632dcfbc0feab3d3012f4e8c8def0755d", + "19w09a": "10623b38e5343c90aa5b56afa85b6a9a5508c509586afcba3ce75ff3de04b781", + "19w08b": "7674fb600ebcaaeec29ce4ae0975b85b8cfe768eb2b2dafaab1d2840c6d976d8", + "19w08a": "cee4a8ce0645e04ba95a5a7c3f7d98679979c1f781881aeb870a36d303993d24", + "19w07a": "ca225e2ef6b5f39c4c7558fe8d58931d6eb3c9cff8b69b24583b70443e83baeb", + "19w06a": "f3913592807eedd540faa160b914785ce39eaecfc908cdb5170f68e0b442973c", + "19w05a": "efa7ec1b4ab2102146969b9055fdf055f0ae651fca785fd83d32c419647d6a7e", + "19w04b": "1088d3c424dc084f1d10acf4da07315b61617ae7545dad2e0a0d99b5e4afda8d", + "19w04a": "4a70a201366bf22639b9e535ba7fe418a082bde36d4882acef6373589b9c793a", + "19w03c": "420a09a8011532b5f14aff99c4ffc3eaa63ac55732f7701998d9eea62ed8f16f", + "19w03b": "3b5f4bb672b9881bbf29edc935fab991124d5dc3d533cad5b4789f94d6678919", + "19w03a": "39af3a62ac674bab60af54559cad3dd1f37e8b3d097908173849551f0050b8eb", + "19w02a": "60a8947079785b4dfd6398b5fe5213205a18704953dfa9779ba1a76cb68cdf6d", + "18w50a": "a3a6e4519823971b04b8ec719d78443a5b2482161d0dd5d00711cb49cb342ffe", + "18w49a": "926ab61e8064b90d35f8f48d85703b1862056eb781769934551c8a20f4b37e13", + "18w48b": "1b03939ba45778497638df11582525ab9c436c3f53fc9b1d35f229240ec14fe1", + "18w48a": "86db5f64d2bba30a29d3c89dfc5eb1c3fa69db15ee864bbb159980105020cb17", + "18w47b": "e88d8a372a02c1c917f90e330c0b84425c60ebec79c323ebba93b5da2e1faa2a", + "18w47a": "c43648005b63737e2d62a9978a8ac3a6fa218952d64660deb9aade56d1ae20ee", + "18w46a": "0d1688a0aa0e5ccd5689949138fde0585e2039b775a1665e762f9a98d8d3860f", + "18w45a": "d9652f4958f840a34746e2dd349d8e91fb92b1d5193cfed0b799267a447d5c0f", + "18w44a": "f623d5a5f2c0360eef8397320f8784b4d25e7d513e05d27745e23696212196c0", + "18w43c": "91d8eeaef10c01de935fbd8018e9360b4606b4dfd000cfa25d99ceaf7faa1310", + "18w43b": "d64207c164f66248b168e6b8db1daad034f0e796ffdaec8dc3e9bf671eeac2b9", + "18w43a": "0d400f0ad9f31d789d779abaeaa455f120dfede7fc9aa4b429e4f63c7a20cc0f", + "1.13.2": "ffd3aa2c25c5ba68a706b59f2abdc69ac1748e115ca9d3b47941e197736f088e", + "1.13.2-pre2": "5d7e47d3dbe2f464dd4118ffedd37d5aafa09f601bab9806aeb61dc9f42a41f7", + "1.13.2-pre1": "029fab08d8ebd36d77c79e9649d88f5fde1d6d71ca50beb40ec754f95ab8ed43", + "1.13.1": "2ea6047e7651c429228340acd7d1e35f4f6c7af42f59f92b0b1cd476561253d1", + "1.13.1-pre2": "358feb0457aec2940f0a53252cd9ed66f5837710e97871f0d4d25dcfdf00b5f7", + "1.13.1-pre1": "7a6cb5c06dc582e2b5799fec44e8cc2d105ed1229c0576273749e2d067aafe44", + "18w33a": "b116e0785bd7e4853468a9e9f300c07d872f2e2339d682e7237ec62076174dd1", + "18w32a": "99de375c834c939347f025b1afd93d6a464fdf8c3d431cb70701cb535a11c90f", + "18w31a": "d27eb76f6143fe2cd6e641c388b588299e9417c156c498cd39be97c91639608b", + "18w30b": "6127da70d52b47d51adccb0c1627f692f56678ea68de404e9dd5e6a15e6a3ae8", + "18w30a": "6e83809a5a308e16ce266909424b8a6f539af47c82f2b964a57e355a30c65325", + "1.13": "e76f3927904d331c969a2c437d5661ac02f24be86062dd1c607bfd4ebdc550b9", + "1.13-pre10": "a8ff609f7891618b63f1f163d2416650779d2c4fcd6300e296fd994983736b5f", + "1.13-pre9": "33a1812ea0f128551cb2cad28f28808b11434e16c55f4af7b42ba9f9eee787d2", + "1.13-pre8": "a658c769b8f29ac4fc103a4c7b24cada1f0c48400658bd2a7def60b1152711b9", + "1.13-pre7": "21086e3ee7ab37f91dca33a57e682276f8f28d3dff37a5029d756d3dc36da0d3", + "1.13-pre6": "be788e95a3b60545a506e7841c091651f1f32d5a078ad12f9b4c74e57121bec9", + "1.13-pre5": "96acdd0a6389fdc049ba597818a1d27a4b6747385c43b7db2d1e9acd2ea3529a", + "1.13-pre4": "45a8d618b2b8771d91f8f689871cb2163228461eb7e637acf95dcf419f830b94", + "1.13-pre3": "3c9da719f5501ed27fb54cfa19e4fd96cbb69e07b92c0def37fdcb45782f4780", + "1.13-pre2": "c3bc9ef540ecb5819252c4ab85cd7bf492a5e077e250d3b4958c8ea0774cc8ee", + "1.13-pre1": "3fad9696de535038e90d2b4338d817256cde2d9b23cdead190ee8900aa9a2a83", + "18w22c": "4a176e787d8635afc5ddce5477a8fa992c8da07df7284062dd1e6139511be83e", + "18w22b": "d95f737959b708b7af63fcf71eed9d8266e668ca522a7eeb67a38460692bf6b4", + "18w22a": "c534aa4cd9251a005c701a22b60e69e35bb39d7479962dae3f3aff9b4ade0eb9", + "18w21b": "0bc312dbe2c1ee8fc411210708dea05ad51da13784d94c6445922e6541f29184", + "18w21a": "b38caf0c824db0a4fc3d3056a2f47d93ffa9f9aaca1bd4bca8d8ba0a35e0118c", + "18w20c": "24ace4e82c4b907bb3caa6953cefcdd33f60d4cc00f8f474365a9f1402ceceb3", + "18w20b": "37561559343a5fa0354d1d4408bbb5da190e2b5cf31d79cc939cdd494c27c34b", + "18w20a": "e9cd263e1608c8d40841a9254214cd3c93479711056cea004265a4dd322f2996", + "18w19b": "2c048d0accef680ffef7cd2722682f65765d2d085633bc8b5b7e5baf1d378207", + "18w19a": "548def192939baddf8717619ffb157d95cb22f069b6627e4f26008298cee4e10", + "18w16a": "16696c021eecb5150656f6041874a5ef7f40f1a01f1c53c68885b3f17ca249b1", + "18w15a": "b9b11bab9b5d1fef199fc277bd0b68533581d3150f49a5ecc7f8e39dcf627af1", + "18w14b": "b38223a2452ce41aa7e2266a7b05d2015c99faae2426b39713c9f1648665dfa2", + "18w14a": "58cfdfb099ac2bcc7fc67536b0a8b94488ac0acd099dba10bd280f82285c71ab", + "18w11a": "4b11887faa3725becf4385f8b0c014cbfb24d6745216a69177deb91c168250a1", + "18w10d": "c1b13e09fed92138b04151b65b6144be42fdf28f71d8a5035980422114302e0f", + "18w10c": "949ace0cc5a1193f4c1d5a54ea16b7db4ea5cf36643f6bc521df4b82ff12a990", + "18w10b": "d88febaf435c7858ef4faca46550f8cb8f06df2448332a1afaccb54a1385cf64", + "18w10a": "11ef7c590536ba0496e6d9aa089df5ad2b47d2ea1531bf62a30cfb6ea841c225", + "18w09a": "17b0dd2f4f75d30868b2bbe5251cef7d9a82e3c06a843aef54934f93f27ac139", + "18w08b": "1dc97992a9ec2558e348f5e4f67d55415cb906d494b2e8f5e44d7613cd2567b1", + "18w08a": "116d6ec30088b552a2c8f38da270ac11e49d4e0f6226716df81e023ab4dbb749", + "18w07c": "b25ec60a1cd18c878c99865db5767ab86c54d7e18e9aa0b6e5d379feffcd6f64", + "18w07b": "990777b050e015d99483a902990247c55ef402fe6a18aed4b53bfea01a92ef35", + "18w07a": "e40a0b64b3e8372cf8673953abd32b625c64115b0a482d4f16989bf340c15127", + "18w06a": "5f4d89418606c8c14be0d428e991b5cd62b58fd2762a6040284877f29eafc60b", + "18w05a": "399767cd2cbe45b4dfb2a3746b885a989151ca4fa4c2a22cede14333be28f8ea", + "18w03b": "a8d380b9a28a07e544308ab55e0c86f01dd509c890409a6a4a7d0a88f7b51fe8", + "18w03a": "5cf4d2680901e4483f3d7c09b7aecff612a07954eca35e12b4645640334e0b92", + "18w02a": "58bb24f857ecb851c0b34ec711225642fd75252ce62b55ac39a7650f89462631", + "18w01a": "d017bfc7ee82317140cac4c0f722da3f7e4f6bbe73f45f10ae8bd2cf84787f55", + "17w50a": "46e7c69769cf1da826258d9d6b17592273c2728bf91e21fd26b3508dca0bebb9", + "17w49b": "a68135f67ca398bbb9d74942f94db015458c0fba7f94968753decabf56dd9518", + "17w49a": "b1f37d282f5100b1ff46a40c1f25465983f9e1fa24fd511247c18f69bb2cc80a", + "17w48a": "af1cd881b372f8460467b4d55120f89fe39a2410c6d15f5ae34ba8d0fe65e12e", + "17w47b": "6d88ff769dade25bcfeb56c6292061487ba2e3613c29a313b8e97e624aafe02c", + "17w47a": "376f208c9f43356697f5e28d0a3f50c112b78da35358c3b0f89c99e171b6cd30", + "17w46a": "417b0b0b39f5c8f1775ec3dbb8cfdda116d3175f4b68fafa87092644ac8a22fb", + "17w45b": "79ca13a3ae15b69a665762dc7859c50769b6fcd2f4d3f55797a242851155d253", + "17w45a": "a230593306d30e11d2e3d33bacf33873461d060270e138dc083871ceaee96032", + "17w43b": "2f4d74b3fa01d970289e9c4a8e5e00b2930d8edb621b2d693ddd6bfbf7df8b99", + "17w43a": "0362895039ad160810c7a96886938cf01d79aef040da65feefa3e9ed8e60cc82", + "1.12.2": "fe1f9274e6dad9191bf6e6e8e36ee6ebc737f373603df0946aafcded0d53167e", + "1.12.2-pre2": "724f1b2560afceda2f0bae37b4b9d3cfada7203edab2f4e1ad5fba8d9085c67c", + "1.12.2-pre1": "c4413733ed6d43f77706c7c8888b9dd4301d613a96f9e1d91d11d3e0c3cb6380", + "1.12.1": "848912640bccfa7ea34a2cc1c76cb2b35f8467c4216d9603917c991660f91a8b", + "1.12.1-pre1": "95a52cd874e32fbc4e4e490c64a1ccb47628f693a910c49a132dab9907694c9e", + "17w31a": "cdee889556c01336f33040c24adb6d59e474f8008f93aeeb7cb09a38d08f487e", + "1.12": "feebff3834e41cc096522525707d2dd27adc2431b1f3145b9d0ccfc4c8a3dc09", + "1.12-pre7": "670fe62bc16e28dfac35816e72ea639cc6cee2c11113d5c777f7916bbf0a3e68", + "1.12-pre6": "0eaa41ac44f2821fadb8f795e0b1081588418972a4c00e700dfc943ce19497a4", + "1.12-pre5": "53860ea9b4c0635da1246af27349b2e76912b1fa74790a2ecd8b60b99b9a15fa", + "1.12-pre4": "ba153a70a4775e03149abf4d93ff4f5272fdba005f5a3b24f9c781fa31c8c965", + "1.12-pre3": "9441df4d269ac3f7adc4229c00bb16c1f6292badaf89b425a0ba936ac98de051", + "1.12-pre2": "7c049cca26ee35fb3758487019c9c6d4784400089283ea85c6874e7d4d1db3cb", + "1.12-pre1": "1ff015429a46b8d6433fbb775c5b3d93863bacd30e085b1ac52e32e3fbbe6c62", + "17w18b": "acd52df80ad8c90463184e2b0e2355695aa3857ebd65902f8b82fcb029a23310", + "17w18a": "4db1fb4e55b4d87c85e2b7580606c9104065ad4a5ebe09f3f9c9269099967540", + "17w17b": "a0cc570906ac5cda9995edd7648189f3df1c42b11e6ff48ee304fa6f0406b366", + "17w17a": "9a6f5985c4612a903b875c09a2ad058440e4c14ecd03b929d1cd83822c536988", + "17w16b": "1d0c3f8028495275fa58c765f55081ea07462c33bdd0860b3d7d726367b739dd", + "17w16a": "8eeead591b5cb4660a0a800af02800816c34e6d221d989e21d20d0b2e1cf87f2", + "17w15a": "1f5125fa4829159297073245aa26fdfeef19f75df958877a0ad980f85d051276", + "17w14a": "1bf64744a93be9efd6eb866bd66875e81dc0af93b0ef0d190c4ea2ff3b8d8dc4", + "17w13b": "24d86a3d9fde9ef9ac748732f641a5a3f600c4c3331c3e1994a7403c5f917926", + "17w13a": "407832c1add81e2f1d1c5f4349df90ab4d0206060399ca5fc5e27cf15029ac2a", + "17w06a": "2f9b96bcfc7b19f51c26e623e22c76d7acbd009ca00ae1483123baaeea97258d", + "1.11.2": "dec47d36b429fd05076b90b1f42c2a25138bc39204aa51b9674ef2a98d64d88a", + "1.11.1": "8002bc32fdba21bf73fa30d94524b0c823bf7256e9c71598d2f18fb319e72c98", + "16w50a": "1fcbc53dfbfcf653c7e27621168a2cc4b204f0c7b07f371ec19fd90a8ac4f4b1", + "1.11": "3277965fd83d26944dfbd1b9740d95cf206985da330a1b3733868e4de7dc6f83", + "1.11-pre1": "41a5a5d9ecbdb9876adb834fa44d17a617b263c2a909dcdd806182b1be47e053", + "16w44a": "5414d2c025591ab3b30e70054582ee21a8a4d1ef1607fec39f09ca2ab3a69820", + "16w43a": "4a9d0c7f6a1c3c78b864039078d5c5c0aa6e4dcf0571bebb32b93dc9e8d18649", + "16w42a": "ad5d72d4b40cde7ece1196d93518419822576bb9fb94ab914c82a1db22aa3606", + "16w41a": "44b8a5de5cca66562e6da8afbe195760292e65cae1ccdde2a0311adc9426695b", + "16w40a": "803679e3afb2ad21366b7b95f19d7e882a2009ff9d8e09220b75c3c220bf2b8f", + "16w39c": "400f18ed5ca26d870e8ff1cb143b02dc1fbcf262647ef2ef9fb3478b90f140d3", + "16w39b": "f3e2d0a4b1215b21eea13b11321c520493ff68d95a867334c16d25e188fa87d1", + "16w39a": "5d7fbc59215e694d8f6814426c47b79b28040081d30ad1d3c10814d6966fb85f", + "16w38a": "34dc55b0c358e68d22831f9da261939f407bc995e93b95d5c3c26c6bf443392e", + "16w36a": "69aec3df1fe9b087be60573111382c3c7fbbfc5e54f8a70e839bcf17ab9b1345", + "16w35a": "24f92327cdf6e836c2e36884a20f5d6b9eb62b2c326e73ec8c816b8ac8f91cd2", + "16w33a": "7ee55ace0109b7f31eab8496838e8b001da154c8c848b2d2cbc26952ef44107b", + "16w32b": "7c59216c96eb750e9e5a18dc255139645b90553942ec9569ea2618e5a88a9849", + "16w32a": "ff7cfbbe3d93e0ddd7d79b9185051d0a607c2d98bf3240c3663e74f9d219d6d8", + "1.10.2": "195f468227c5f9218f3919538b9b16ba34adced67fc7d7b652c508a5e8d07a21", + "1.10.1": "623c8d67e7357c7078f30cee450562c52a05a42c394f064474c7f2d45b4a7d3f", + "1.10": "dd44a72e920a01dedf57507b73642f4a9dd8c6052e1f42ff6cc0635008014201", + "1.10-pre2": "80970a1ad42fc434d306205dad3e71d74d2af057eca8a49756d742628b3725cd", + "1.10-pre1": "920f899b57892424a9c8117f8c21563a76f5fe91ca70f94da467efc7772dbba6", + "16w21b": "52d3b46dac4f555930879f5eb7d326e55dd79a90eef381bd178fcfca42a263da", + "16w21a": "4d9ecda93f23af7f9efef333a4b9ba4d0af2484b4664940c1b6f18f613002c4d", + "16w20a": "9b0b5b31b198e67bdd316b1de23c2df587692d064df1b20746d40d4394891105", + "1.9.4": "13fea7aa10d804dd14ed7ebde2493dc64c7d3c8173369309bd7f6ea4c0ea40ad", + "1.9.3": "0eb669eeca23bad6d4ee5237aa24cd51274dfbda15813477f8414f4bfc2f1f27", + "1.9.3-pre3": "fd57a52c156192c06490a9775d694433065fc8bf93f41d6286ebe68db3fb213f", + "1.9.3-pre2": "7a21a84b472a50ef865872c7eef5ed8c2d0a94aed8a459a11562ed6b1086a5d8", + "1.9.3-pre1": "367f81f9a5c3d8983db29e6531c048d8abb5e62eec666fe1c2725b2ea455b3c3", + "16w15b": "0141cc7bf999021b14be167e087806135878410aa283a1f904a86937593aedee", + "16w15a": "748877dfaedaf531cb0173b39e54d9f005628a19e89e30204cad5ce3823bd6ef", + "16w14a": "37047a269614bf730a6bd942cd1180f7cffff1c33aa3e962ec895d7b5a34b7b4", + "1.RV-Pre1": "b85a55a0fc78771d2c3571dc7c4d9c8f1a2aab9edcfb3e9fe3825dff5befc817", + "1.9.2": "a972d127be3b9d5fafe5bd610a173563cb24331b6664a3dc5f73b3cc76d77081", + "1.9.1": "fcc5393c191afbcc0b706bd94616fb171e2fadaa107a3e5f36298ed2abf76c41", + "1.9.1-pre3": "b62800a3e74c3be7ee99503a848dd523e56c762f69c23d7ebab99d22538a4fa5", + "1.9.1-pre2": "eb162b8f05c638bc5ab296ddc56dfb598849ea29ba349e0dff38a09398904e1a", + "1.9.1-pre1": "20dca2343649d91ef2e017ca61d71c5cace0d73159ac020ec44e156c583509da", + "1.9": "38a797f50c71f55202e2135a30302cf3a5c8cb494c6d225b88599542957d3a7d", + "1.9-pre4": "e55154e2b238be686385dd3a29f98a8aef4b2175cacbc68432037def340fb6fb", + "1.9-pre3": "d652eb5de18eddd95de594d9c45b0aca76c80b72b30ce7b929ce97a512f662b2", + "1.9-pre2": "f5037617e8b0235544f546b178799305e3f32829996e0827c9f1b496fc231047", + "1.9-pre1": "84644f7ea596b7f730cc98018660ba6629e6392323ccdf5c71fb85332bc088f9", + "16w07b": "342bab138d633ddd2470b9b88e65dcc39da5fbb8b65b7fc917600587f084ac54", + "16w07a": "6ecdb84ba29e8478464997f2a0f8774776e209052e9e124e5c015a02b6caa149", + "16w06a": "88d71f7a3eb597b06c0519de221746ecdaab2cd82707ce443828c3c0becdc0f6", + "16w05b": "344ee31868913e095301a9153d118ec1b87bc624251231ca5032978d726e6515", + "16w05a": "e070b7a3a46ec6bd03d950f745e0d298e68a88b360d242797f50c9295e0f7e41", + "16w04a": "dbe527c539d7a3fc2bd6a50b11988bc3e484cd941f93bf8e6c852f4ae194aae5", + "16w03a": "992f0ac1b195a538f4b84ebcbe137935a714658d8acaae197b11508f7744e23d", + "16w02a": "aba2d1579db0afef8064747402622741bd771195d6377bd3629f7dd73a8a5cf8", + "15w51b": "8c8e8a435c8bd832a2c85358124f86ed9ccc327c5c630c28a10b29bd01526c0b", + "15w51a": "2b046d0728a74c0b016c9448cffc6bd49c05d1642156b3133ffcd937b98298a6", + "15w50a": "e8b92400015342eabadcfba5e6f31372a1c71d07dd868e0139277c0de5400d85", + "15w49b": "e948e5aa52ea52e323fbfd25091c7bfc687ff75a529e12efe791ee2b7b6342aa", + "1.8.9": "c18e4245073aaff580eb7359902f0251436568b1647a9e443a924cdb73fa8312", + "15w49a": "8f281199d271ea365542523e486d9b623d061867709833046442d3512c4b4720", + "15w47c": "0e70fb9243c774f12cded32a7c50b812cbf341cafa637fb1be46f788a117b9d4", + "15w47b": "f38c0d3ab4f7a9d7ba75e02cbdaa0ca2761a7406249ee03d51dac130f52c8234", + "15w47a": "ca07d91f61fd9661c52be2fd56270900e2d2d6659ccc8166dc4dfc89bab5f6e7", + "15w46a": "ccdc14e21259818d824c1498f035347bbcb74e9c4d6dc20169d61f3896eeb963", + "15w45a": "863eebd07e30d5d4d9f18f419d6cf123f75ac2b6f2c12dfcd61decd4202d269c", + "15w44b": "bb56195201a68a4966efe30c3c317d69d74ceff756276e159340d17b2b9c44aa", + "15w44a": "0a13803768cb8097648131d681c17f99f75c2948d6e5c6567c220f33c9e579fc", + "15w43c": "dd85afaca19fe0d335819dfd59e6162ad2675b0c59300a27c644b535bf359a8a", + "15w43b": "5efe6b619b1ca31a63657b6b58a1b9d4c27e4f40ae7d50d1970c1d3e692b4ce8", + "15w43a": "074212c0b20aba62419277b7ebcefc64c68d3a80813e95db7966a72ad67f6679", + "15w42a": "a16bbe888735e68872ce6863d93cc9d89df39ed2cc9e227b425e2048c6bc9343", + "15w41b": "8926af4de6224a9fef9f010fe7eb114da54fd4af86e21b055faca20c6f61c2db", + "15w41a": "4ab8a919feb53f5f8cb7325c001ebad6f3d721f0387983459da883c8563af87a", + "15w40b": "e2569a83a80ba0fcdf5c1c495b677c3652d28a3bbc3b14f91a3cbde5a8da0e7a", + "15w40a": "2a4b746ea50cb855710dd0dc3e04bdafc7ffe02ec9501e45b7782a9eaf0c0b55", + "15w39c": "41ed25f97e6e767d47185c5e73e16b288f71398c895f8874fdab620de951a305", + "15w39b": "a840c2fa3f11a3d4591aeeb9235d46cff269959e5c03f40207e00bbbb997abee", + "15w39a": "ed755605c41790de977c07c3bdd283e1eb79d6ee2b4ca33e476a3ff87f8154dd", + "15w38b": "8f7729e2c9a433e4786a04074d78f78d9a8496cab4a28d993558b31d1b8442d4", + "15w38a": "b46119ef3d5c5c2509108f86466cbfc15b5bf87d21358f5d787241f9168b61cb", + "15w37a": "e3e16fdb2149a1cd1fe3a286ae822a84581db90cf79f8f3646d3a3d394a55f34", + "15w36d": "3bcca5c4b0bc4281fbaf9bb24d64c75f90e003fa742f5de996aed0b21a19afd2", + "15w36c": "7ee42fe9f4b8c4df8918c8cdaa806d07b67ec0ecc6a0ea3ebd45cf084a88cee3", + "15w36b": "0641d3294b319a1116633f8b6658433ff6a5d46106f0027529bbaaf73fd0d8fa", + "15w36a": "88b420ae14500ece0164b70470ae114732175ba242c792379d006c9a8b09e92b", + "15w35e": "74bf64ef314fee221d5e330be910aa4a6bf55a8c1ec65d2077fb89be41bd64fd", + "15w35d": "55e871006e45e156de6a0dd4e3516ff7ab41c6e6803ecc9a648117f8fb512b1e", + "15w35c": "3c9afb5bcf6fef5933314ad09dda469be9e6cfbd07b8ab5789e468e4f3f93833", + "15w35b": "c31ee139dcfb4cecfb08d12313cee5bf60dd0f05194d4ddf804df471a0e76446", + "15w35a": "f17463ced603699eeb075916a97ec3de3171c145a619a498fcc8a7f045f4da7e", + "15w34d": "a1b9a9a1fa47bc8ff7e1efc8f3603921ec150abef71f8fefa5f209bbbe079859", + "15w34c": "62a1ae63cbf38ff9f94288b5d3dcdd462918cd4c2d465a809fce3c47ac05baf5", + "15w34b": "9e3c2f29682f2be1a4c628eb94040ac4d67ba4270e98ba8330aa3ce33423ce12", + "15w34a": "46875a087599b89618ee5502dcfebaa33307babb03b221aa9d924d83c5ce3f48", + "15w33c": "f36b658c781660cf4218e3ccc45aacaa2800afd3bc81f136cbe9c13364b6963c", + "15w33b": "c2a8bed5a76a95afb35950c2ee3605bdd3fa774a905103edafe915283e9fb1c6", + "15w33a": "4422eaf4702b96b40a58e7e6d440ca29289669f562575ea4016971971eca8be3", + "15w32c": "e8f3c78a60b659297f4ebda8351779a183385eaa2edab954920eaee184f0ab88", + "15w32b": "5aed03f99346ed70cb2f63279382822abe754f047db3f4820f1581754a0bb843", + "15w32a": "dcdcebf422252abc2fb9f2ff8835c39e3bbd0a0197b293de93eda5818ab3beac", + "15w31c": "3c502fad30a1bcd1b1ffa077825885754bf524b9412e2cbf65628b8609efaccf", + "15w31b": "e0de11fe8a2c9f1020e35d0b178b0eba37476dc38e7f7a0a15bda52cdadd60b2", + "15w31a": "e4f9e7b1ebbef58f1ae99c35c6cd3d0fb13d7f4aa6b68995501f512561c6a63f", + "1.8.8": "39aef720dc5309476f56f2e96a516f3dd3041bbbf442cbfd47d63acbd06af31e", + "1.8.7": "5cf4a49762c996c94f6b8b119f1c80b4de3c12b2f5c53268801905bb7daa0644", + "1.8.6": "7fc66b2b54f0f4d65fdd6d6484a50f432c144ef02072d3435d5660f120f58e0d", + "1.8.5": "6a412e89009acfcd5c56084ddab4f9676c5561bb58a3f22d5ea4ba4ac5d3503e", + "1.8.4": "394a9d0d5bcd03272a58f036b8736a47d26d63b45a4e7c820629114876e72107", + "15w14a": "9920b744ac1f7de76ebf4f33fae5d0b53baef35bb175529a3633f53ae17f2e99", + "1.8.3": "a26751b18ccc80ceef488da645c3b785aa528e2ae20a6a6dbd46f6dc754e62bd", + "1.8.2": "d99f3b3478018cb454fbee36fd60e3c4acbe1132f1cb26b3657f9ad291e7035c", + "1.8.2-pre7": "d19c50683a17c43fb64563a5fe75b6417626c57e2ebd76d04f71316794499b72", + "1.8.2-pre6": "01a28ada45d313a1ef59757496ae0c50b79f9152d5ab9fd83b90e9628345a114", + "1.8.2-pre5": "b9694f042fc6028e3d36160383169fe6d9a5455a05d002733f547466e5bce69f", + "1.8.2-pre4": "0511885adad9255c4d8e1d0b193f8b0fe3cf5fb323629235571d337c46f6a342", + "1.8.2-pre3": "9e5d0000101e61f3c5de520743850954a62d4b0b85df0d75a58f9f9384941e8a", + "1.8.2-pre2": "c4acd0660a05bc76c24c6bd7dec0c8c05fafd03a6218071cda989dfc2ec9d6b8", + "1.8.2-pre1": "00514ad81d46b19a8b8e066f0f4716c2cb0a3275183cbff9db119b01ea5e48fd", + "1.8.1": "ef5f5a1a1a78087859b18153acf97efc6ecb12540ac08d82b9c95024249b9845", + "1.8.1-pre5": "f492628c8a192e9ec8f9f5a4283c3c36748fe703d6733d4304c0cbc725d20ee0", + "1.8.1-pre4": "970cdaccb4ff3acdf2bbc5b4cab07e2ea9a1b20271f09829ffbecaba7bcaf93a", + "1.8.1-pre3": "dac84ad1dbd16365e48d59b6772dde5758256c2c9a315d2bb61dd29854595e70", + "1.8.1-pre2": "0e19fd1db175aa8a957c9cf7ce60dfc430090ed71d4f981af502856f69227630", + "1.8.1-pre1": "c581dc7475b45f35f573ca6ba90e9cf7df9b95ebac007594c7d10b24c781550c", + "1.8": "40e23f3823d6f0e3cbadc491cedb55b8ba53f8ab516b68182ddd1536babeb291", + "1.8-pre3": "da969b62ffde078c8890c40b05c47082bc691ad53b026534c87057a2f4cd0118", + "1.8-pre2": "5863a5256894335787f9a048c96650b1d28ce7e049641268bec89fe6e083a310", + "1.8-pre1": "0927b56f23c480d94889c0a837932a9d44ed848059d04835764f78365ef27660", + "14w34d": "05d4a426e75245475d3c88cbbe913658964661df2d8605a0598200ece76b81de", + "14w34c": "069dd43e6c3235d488f945b675798f5c522a0e9cf811a7487a6fcf48db52a3ca", + "14w34b": "602e9e03cc853f9291aed021f3cbcdc87377d2afbfe19a7de314ba0b759676b4", + "14w34a": "19bdb3b1366ccfac7fab918b9f02f75f420dcdfc210129bf909c720f2cf7a51b", + "14w33c": "594aaf7505dbc447b2cf3b67cc026c4885581e45841107d065544824e6e34667", + "14w33b": "b935e611d9a55f464fe29d6b40ea628d7d5d3e87e03c2ee84a055aed40757b06", + "14w33a": "9f6dc04ea65c2404e3c910efbba33ba057428c873b55ea21b98ade7ebaf8b025", + "14w32d": "a59c224c33d0a5b24d41d13e21509fa79053e413e22b05471118e221cbf65864", + "14w32c": "d673d52b400459ed6b5003af36897149f9917c5146452b87bf806a851661378b", + "14w32b": "c2c4ff5940197e7ab6ef922cccce04f9cee0ef118052c4d607d9351b930f3ae5", + "14w32a": "6969688ad556ee91ae078d31e7d5d84b24dd907ea0ee55976b90a1b16981ddbb", + "14w31a": "a58dd363035c79925a5ce03ef3da2ba5a25d514122e842d0188d4e1055259375", + "14w30c": "55b92a96054b8f50c7b0d4a67fffa4ca9004fc87d88402699d66afe230b42773", + "14w30b": "ebf69ac57cfa9ed3146bcc5b4757cf3faf2b6729b61e016c12e25f8ce4bf01d0", + "14w30a": "a9fa0fe45a96412e1e3dcc065de6b0802f659836e366115c6f9557c572560ad8", + "14w29b": "445d21e51f68b5bd30fd905c5739662f2f42fd6bcff8bf9a8fedc2926d94b407", + "14w29a": "d2f52276df97dc039ffbb6dd2b9a2adfc6fe8227b394e98efdf125646a4c0e8d", + "14w28b": "30974fb655414499f186018c4c68dc970f0ae21cae868ce1b7ee1debaf58b6d3", + "14w28a": "b894cd9051fe26d0619b9a717237ce770f8c622b16b39e514ee9e804fd9afe90", + "14w27b": "05511f5e092e94ac7342ddf9685ec3ca820cda5dcc072ed9a9deadcc6c2856d8", + "14w27a": "a8efb5015780dd070b8fb92a11dd876a20b653031f4314b717c972608003bede", + "14w26c": "e6793c31489f25a76cbb64edb9b675e13db5e9d7e7d48ec12aa2c3f16812c25d", + "14w26b": "f65db21b7e44208ccdfdeec9b57d6e4ea640cbbd8481503ede4a27f3613522c4", + "14w26a": "e998161bd9dbb3ce60c3fc757d227dcabb3a95836ddebc1d8740dd2888c06638", + "14w25b": "96bb81e322c8ad08e6b085cc6f2eca068a069db44fde78afb4760e060bc4cb70", + "14w25a": "decd0e814c06860fc66a8642fccfb1deeaf60bebd7980cbf975a0797051f6509", + "14w21b": "2c9e15b5d47eed678da1172edd0e05f07fca9b2b9d21a91c6d3dc92558c3aac0", + "14w21a": "387c81b1aaffb6982cbf1ba1926620235dccddb27cadc14449cdb5b3befadf1b", + "14w20b": "66543e6776a72fa407cae0e804e9fca642ea41f22b54fcfefbef76f584763a1b", + "14w20a": "0bbb5bd0038c09401033fe5d28f4c207c44bec6920afe5d6cc3eaff1a7b49ac1", + "1.7.10": "c70870f00c4024d829e154f7e5f4e885b02dd87991726a3308d81f513972f3fc", + "1.7.10-pre4": "882648310f8e370b7fcb71a2e4f0dc578d59d3c23f6d24e0509810fc8ae3edf4", + "1.7.10-pre3": "376cf7df05a1d1f265a0aec0129040e9ca75ba43c279e929b9e43ae5adb781b8", + "1.7.10-pre2": "21a9c212e91c0af5b4d56bf0dd411227732554804b009a4fbd36d7b15a4fcbd8", + "1.7.10-pre1": "c0e908addbceaf60d42a424baa1b413264b9618584948794ec5ae151a81f8068", + "14w19a": "632fd260c009bcd29e5d0412657911cfffd8b7664b5f51802bbfa169684a7cfd", + "14w18b": "24d6d49d9f3b332bb3c5a530561279077f986c6e19e8a4b6f7be3842080afaf2", + "14w18a": "86ebb97a1b18657f8468c45c64d7de6ce3ef19e9897564a4adb17abd5dc88fb6", + "14w17a": "c7f77b08a3df25c30386daaf75ec36b3c8968e6621c558c995c427adbd327863", + "14w11b": "131a37f8f960c06bf119d435682bc40ceead6de48b73aef2bda25a418c1c239a", + "1.7.9": "f7b9150d05c2cf8c48541527de310557e6bd9bde73e8ac9479e8ffe722c60a21", + "1.7.8": "5907ef1103acf15952d6d50cde3db01e4fc5a95b9f5fec0be25fa56a5ef0d121", + "1.7.7": "74646f88ed76d878eaaf2b28c4ebd4043bb11255999e389345dc55d2f11c19a6", + "1.7.6": "5cdc6ab6168ae496ce1d1ae96c0b165360184518d030417429ec7687c0b9d527", + "14w11a": "508e7a5c272b0428414ea6a84ca4b22c11d48ff4cecca26ae083390de8983655", + "1.7.6-pre2": "c4e8751d2b38e4ddb9d72189d5d3f0bfb82d5b76ce2d4792878cf37f4162bda8", + "1.7.6-pre1": "bf0407bd78a9583c0957cc6e1657dec4d19813b8006c6ae1e8f1c49f64807981", + "14w10c": "9dae5fe55d939a5837887a1f69844d9154888331fd362b1c3f722516d4f4f5cc", + "14w10b": "83d4a315981a07e1f1951a95c02f4d0d7a0b7d5f7fe476c60af78b6d6410ae2c", + "14w10a": "4b2d17daf2a41a336abdf1e098825a5cbf3b163bfb8d992b9d8c4fc99fd418ff", + "14w08a": "a84139d1887b20fa3363f6b94dd93de41b26c8a1ba1697967ebf26d65de6879d", + "1.7.5": "caa9e13aee32112b3b5305c02ecc01c05502fe244cdb83faa7a01832937542e4", + "14w07a": "44a814b0e306a0368e569e0596719ff07897b355df131b21acbe85199e6f9ff0", + "14w06b": "7d0d40ec07935c79c46a2f1689b0e983dc1214b7f57b9a91c5f4c47b1aed1353", + "14w06a": "5d2f23dc66faf6ab6d1b7196e30c0016d324e3c3a4f2b1facffee2677f8415b7", + "14w05b": "08dd19438e280481c2b83bc912b162250709ff523b127f365cc35c696c452c87", + "14w05a": "0c61c3c1d4069c29cd7db31593061a4b4abbe2b74623b54fa86ef73eac9daf05", + "14w04b": "5492e4ec31762bcd059d1d7238adf5a33e2bb5b5816a5d72a9eee6dbbec8800e", + "14w04a": "d24129f6e93e69706b465134177186d22e404067d58099ccc07f3bec81941085", + "14w03b": "0342ee40cb4bc3e2f2142bc4efd27033093c68c856f8d08f10afef3d0f5f31af", + "14w03a": "7a2a49d7985a9d82bc2026ae0465705cfa12f32e65f9bc1bc4d05de7e3de4f69", + "14w02c": "13095b3c5871fbbe1ee3dee8908b53ba2ab05b50f51dde0681b58c3f24777742", + "14w02b": "d39db2835ff2b2b2f6d6cbaad7dd626a2ffa8097343bf82c72a6203306208ae2", + "14w02a": "aada0c408d7776a08617748cbf0a3fd4abea36474d87fca1639b453512b48980", + "1.7.4": "796d6ca283861a3185f2e87ec321b1233540cdf2638da6e00f1d96d47791031f", + "1.7.3": "027b0bf027910d9ed3c49ff643ee81fa875e95052cd66ab7648a9e602973f266", + "13w49a": "94218edaaed13d8ddffb83cdf2f2f9e4d6b1e43d2094fe0a9e8c231f610a867f", + "13w48b": "9d66f65d85ffecf0c90fdc751f3ae1151f90dcbe175811f198c7320f1b37ee0a", + "13w48a": "8dd6c1fde2cc68f314bb50d7a0eb63a33d2d8ddcf1e5e0d92475529d2006b656", + "13w47e": "8123fad577fae5a92744847f203c3dfc8937de1f42ee21154c9e4d656d783c5f", + "13w47d": "ed8a11184ad77069a92500d0e90ff2f05e0d3ac4f0f40b7e51dd3b6198f869bc", + "13w47c": "aa0ef4490c66e4eb9d013d15a6368c38a658a3706bd9d6ca74ad1e5939879317", + "13w47b": "e4ea5ea684c7cd3c2a435fa35d351e5eb33d5a74b8d6c091001ea3bf3aac6d98", + "13w47a": "e2e288bc12fc7823dcc69d6e8dfbcf9c5e41c7f7032af5f2bb23d1f684977485", + "1.7.2": "b4139899700c1bdbf72880eec4bdb9e46c2cf22d1724a48a018ee0330035462e", + "1.7.1": "78ddbd2dee1c68b5f1d92ff4752cfc6dec3cc15320b41df142e2049c6e3c0ed6", + "1.7": "5a4e9b5a4cdfd7a681195c6016c94d8e5f49e18d91ba0ececa1d81ba5fb15aa7", + "13w43a": "7542353677246babc76d0ab2de4f2c9770c3685ce31f7f08143583b3c7f4f2d7", + "13w42b": "bbbff097c83f1dbb9120eb9f3af0457e339ca58f54b3be612ed4eec09be0b03d", + "13w42a": "0f62c56b10d7b52df893377b60ee9417c956513b8e1f1606b8cb7bee59404642", + "13w41b": "e9ef9fc8304460c9b0efc73e7a07b6b183d2a90d21c4ed13f4087e41742b0aab", + "13w41a": "494126c0143fa85a1b28ac03ee36d1d404931acad8364ee5f5e2acdebb53ed73", + "13w39b": "99229c20fc69fce7994e6db1f0b22224ebc75d5489fbddb4ba85a503ba6ad0ef", + "13w39a": "b738f75209bc5acb3ce9baebd707ceb98306ca59395c7a60e69370d61b48a0b4", + "13w38c": "d42dc65f9a173815a67933b12ef0abed77428142c6559336155a7f9b231b3498", + "13w38b": "571b16ffc98af57ef0f197318b6b3674a07bac40c541acbb90b5e5cca64deebc", + "13w38a": "dd58e97a800b63d88f3a9599120fec9bcae0643dbc70f3aa602e2f620f99fa56", + "1.6.4": "81841a2fedfe0ce19983156a06fa5294335284beeb95c8ca872d3c1a5fcf5774", + "1.6.3": "5a7b3f4ec258b55c8e4feb956d7861b48501a61a618f5c6495fc86ae4985f0e0", + "13w37b": "75090b7f800c690df76b8ada5d31ff96f54e33a24f6337e1c13e71dff625a938", + "13w37a": "3531513b752a1eb65a3f500e14023c04ff9711c61f4769b48e75d31d92a57bad", + "13w36b": "d868871077dba9094990274b8791882ee60374c2696a3b19d04fab5aea6da399", + "13w36a": "1db816ff10df3f0d5a8e9728360ddc8090f6ba7bb5e8c17066ea7ed614303a88", + "1.6.2": "99a7f4088226f5574ec47fa69fda4779376499e5c9c5b8c2342563c7ac35368e", + "1.6.1": "d58a6cc07305bc3bc3915fd8a81ceb07ba4bede3111c971115815789e5674611", + "1.6": "7e6fd851b7646aa32964b0d3370ec33ddc64695074e5c26634d3ff0951617be1", + "13w26a": "3dd2c0ea0ce08c1569acd40e1f09145c620325eafa65ef522551139e1e039fa0", + "13w25c": "949f32be91f32f85f17511abd47b79cacc7bd5d8d8a5d4eb99f282b80bcfd156", + "13w25b": "0dbe9d6cd69551c8f17bcbaf2adecffad6a9c5bc1c0f0e189c2005fb5fa73fc5", + "13w25a": "a9143baab9a1f0d693ebab2420d9e876ff1d62274781a2b2d3112bc0f3da2540", + "13w24b": "b7cd0305793f8a61363781b8dd9800afe0c5bb65e9542407a0c1d15a573b14c2", + "13w24a": "99e504f1ee5dc1b11bf9a084a8d8afaa03f413471f987a39d99cdd561af7b5f2", + "13w23b": "ac28f91f4d9dbe4a2c2098e1231345cb9d15d52df4f0405e4d2beeab30943886", + "13w23a": "005b3ba8d7c26c370be32503868b83984fc67ad88dc5e46b9208bf980335919b", + "13w22a": "a5e21402916da0fb6663a0d0d5584de2b3bf58c2620d4aae6235efb74747fb33", + "13w21b": "3ed24ca37e193d647737ceee6e5f3acb1b40d2209b46bdec9d87df688ecceb77", + "13w21a": "7f4d1dd9cf844fa590cc30051040e1fd8c3c115cb4b48a4b9a200cbe10a5bc0d", + "13w19a": "3d0083272afe405518daa345de20824272669bb91b0568b14e28cad06f4de4d6", + "13w18c": "f0b5bb768e87d88334da5d30c8cbd1503448e248091aaacd8cb8b56bb30c3a76", + "13w18b": "a251665c17145c9f5b0a7aa9f6ed2b7bbb007c48fc7ff4e5dc4cd3b2efb8c4fa", + "13w18a": "46e96fff19bada9e994e20fbe044ec4bc1b226a31f855b4a1760825425676076", + "13w17a": "f2fa011daed1647006df3859147c08d5935f4a9700c0fbd300524c3d800d9fde", + "1.5.2": "4f0c7b79ca2b10716703436550f75fa14e784b999707ffad0ea4e9c38cc256a0", + "13w16b": "ee358d4f84f91f1623f746dcdf157bd526bb2897d5c5293a099ec7ecf72dab51", + "13w16a": "3d3d2f3d9480ef2cb1abd31a1c5879603af0260bd95fd298fed6f026bf42f7ef", + "1.5.1": "e8dc60c93992e495c3a9e6dac7517e6811af7a803a3419eea50dc78e97f51297", + "1.5": "71239880440f8f22a96aac5c00d954401859ddc8847b4f973f563de0b6a5d781", + "1.4.7": "96b7512aead2fb20ddf780d7dd74208d77f209e16058ea8944150179e65b4dd3", + "1.4.5": "b8af871d6b0a03dd2fe65ee9238bb52c60dd5e30d3ded0f37a9eb860e5df206d", + "1.4.6": "90b3b9cd466abcd6ed9e932e1b81f8e34c5771f536670ed9ac493188b021000b", + "1.4.4": "2ea46e24c3c2931dbce11e4d79a83668cd6f002b5bfe131645a98ece099430a3", + "1.4.3": "283c15e256ad4776906e6832de90cecc9a5fd2c28651c6800024c3bc90f5f9fe", + "1.4.2": "16bc7305231d5ceba8b81e43cca8bdcd19cc6d92a488ed270baa5ab827b0fa40", + "1.4.1": "a6ff759d3161ceb3dd9997daaa53c3916f2c8b8b61e38f850a4d9577ea0678ab", + "1.4": "49c50a2c9ad4ab78c1ff9048c1a7f000a4f4d0628000902190ddc1a64d293b71", + "1.3.2": "0795e098d970b459832750d4c6c2c4f58ef55a333a67281418318275e6026eba", + "1.3.1": "62b8c8a3691fb5f51af3bd7efc34d1bc5a227e6162072e9827f439744df994f2", + "1.3": "64ba1cc32240cf12c76b6a235b299c16110e90a535f9c83fc08d9e2e766da0a9", + "1.2.5": "19285d7d16aee740f5a0584f0d80a4940f273a97f5a3eaf251fc1c6c3f2982d1" + } +} \ No newline at end of file diff --git a/src/main/resources/certs/mojangcs.cer b/src/main/resources/certs/mojangcs.cer new file mode 100644 index 00000000..795f7306 --- /dev/null +++ b/src/main/resources/certs/mojangcs.cer @@ -0,0 +1,120 @@ +subject=C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority +issuer=C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i +2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ +2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ +-----END CERTIFICATE----- + +subject=C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5 +issuer=C=US, O=VeriSign, Inc., OU=Class 3 Public Primary Certification Authority +-----BEGIN CERTIFICATE----- +MIIE0DCCBDmgAwIBAgIQJQzo4DBhLp8rifcFTXz4/TANBgkqhkiG9w0BAQUFADBf +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT +LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw +HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv +ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8 +RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb +ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR +TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ +Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH +iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB +AAGjggGbMIIBlzAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0 +dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9 +BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy +aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwbQYI +KwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQU +j+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29t +L3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v +b2NzcC52ZXJpc2lnbi5jb20wPgYDVR0lBDcwNQYIKwYBBQUHAwEGCCsGAQUFBwMC +BggrBgEFBQcDAwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEBBQUA +A4GBABMC3fjohgDyWvj4IAxZiGIHzs73Tvm7WaGY5eE43U68ZhjTresY8g3JbT5K +lCDDPLq9ZVTGr0SzEK0saz6r1we2uIFjxfleLuUqZ87NMwwq14lWAyMfs77oOghZ +tOxFNfeKW/9mz1Cvxm1XjRl4t7mi0VfqH5pLr7rJjhJ+xr3/ +-----END CERTIFICATE----- + +subject=C=SE, ST=Stockholm, L=Stockholm, O=Mojang, OU=Digital ID Class 3 - Java Object Signing, CN=Mojang +issuer=C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)10, CN=VeriSign Class 3 Code Signing 2010 CA +-----BEGIN CERTIFICATE----- +MIIFRzCCBC+gAwIBAgIQWAyDGhMqlzv+buZKWtQ52DANBgkqhkiG9w0BAQUFADCB +tDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug +YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEuMCwGA1UEAxMl +VmVyaVNpZ24gQ2xhc3MgMyBDb2RlIFNpZ25pbmcgMjAxMCBDQTAeFw0xMjA0MDUw +MDAwMDBaFw0xNTA0MDUyMzU5NTlaMIGKMQswCQYDVQQGEwJTRTESMBAGA1UECBMJ +U3RvY2tob2xtMRIwEAYDVQQHEwlTdG9ja2hvbG0xDzANBgNVBAoUBk1vamFuZzEx +MC8GA1UECxMoRGlnaXRhbCBJRCBDbGFzcyAzIC0gSmF2YSBPYmplY3QgU2lnbmlu +ZzEPMA0GA1UEAxQGTW9qYW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEApK2feVemdO2gfn5ewbbUZGeRvSygG+bHwIj4fK8c/epUh169x+hDUCrrSI97 +7mrulhegTTI+zqsF36SRaWOxlDu75LUGSqp/WKCRQyBqiUB+ZIJXaemWIZipBcWv +rPRYm0bZLMJERT1W+KlshCQSkXDcof8zlFnV4HQ9X9zxlk+9uKhYlCuM1c09sjlK +7xegZUIDiu92g/sRIpVHrtyXLbnSpHRxDzFYZkJDPFhVDjK2x8NIuK4yNOf1nWoM +QYu5V/8tD7uG+HVFTiIg1SkOLNW4XCn1+0vUt2TANga+/NZxLSrlR0Zwtm8KPTNj +o7aOZ40dCfbZqQlqk9wS+2X6awIDAQABo4IBezCCAXcwCQYDVR0TBAIwADAOBgNV +HQ8BAf8EBAMCB4AwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cDovL2NzYzMtMjAxMC1j +cmwudmVyaXNpZ24uY29tL0NTQzMtMjAxMC5jcmwwRAYDVR0gBD0wOzA5BgtghkgB +hvhFAQcXAzAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20v +cnBhMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHEGCCsGAQUFBwEBBGUwYzAkBggrBgEF +BQcwAYYYaHR0cDovL29jc3AudmVyaXNpZ24uY29tMDsGCCsGAQUFBzAChi9odHRw +Oi8vY3NjMy0yMDEwLWFpYS52ZXJpc2lnbi5jb20vQ1NDMy0yMDEwLmNlcjAfBgNV +HSMEGDAWgBTPmanqeyb0S8mOj9fwBSbv49KnnTARBglghkgBhvhCAQEEBAMCBBAw +FgYKKwYBBAGCNwIBGwQIMAYBAQABAf8wDQYJKoZIhvcNAQEFBQADggEBAHT+RhnF +LoqUlSvo0bxl3eUj81FZg0neyCnpGZV1bFqmDwcwHAWRqOSkrOYxTed6v9cJl0q1 +FPXU/6ic0lfUWNqcn0uaS5vfVRpAhRnliLrGlfE5fQfE4lguOUQ4cILK6AxJpeKU +JVDUoeObG2ven83yIy0guevE/1so2VXnV1bFLTtdS5r6iqqMGCshDZMFVleYMo0S +uhfubZDtKIhd9pRLkpg3MzchYLmri5NB67vYZizW11W86QZIWoDIJG8NAWyz6HOJ +rS+ecFa1TBo4gkcKDrd6DT8dMBNUUTTECo6bTGpSfkxjaRK82ZZwS7ui2DMb7K7e +K2GyWhR2PnMg09o= +-----END CERTIFICATE----- + +subject=C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=Terms of use at https://www.verisign.com/rpa (c)10, CN=VeriSign Class 3 Code Signing 2010 CA +issuer=C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5 +-----BEGIN CERTIFICATE----- +MIIGCjCCBPKgAwIBAgIQUgDlqiVW/BqG7ZbJ1EszxzANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMTAwMjA4MDAwMDAwWhcNMjAwMjA3MjM1OTU5WjCBtDEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQg +aHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEuMCwGA1UEAxMlVmVy +aVNpZ24gQ2xhc3MgMyBDb2RlIFNpZ25pbmcgMjAxMCBDQTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAPUjS16l14q7MunUV/fv5Mcmfq0ZmP6onX2U9jZr +ENd1gTB/BGh/yyt1Hs0dCIzfaZSnN6Oce4DgmeHuN01fzjsU7obU0PUnNbwlCzin +jGOdF6MIpauw+81qYoJM1SHaG9nx44Q7iipPhVuQAU/Jp3YQfycDfL6ufn3B3fkF +vBtInGnnwKQ8PEEAPt+W5cXklHHWVQHHACZKQDy1oSapDKdtgI6QJXvPvz8c6y+W ++uWHd8a1VrJ6O1QwUxvfYjT/HtH0WpMoheVMF05+W/2kk5l/383vpHXv7xX2R+f4 +GXLYLjQaprSnTH69u08MPVfxMNamNo7WgHbXGS6lzX40LYkCAwEAAaOCAf4wggH6 +MBIGA1UdEwEB/wQIMAYBAf8CAQAwcAYDVR0gBGkwZzBlBgtghkgBhvhFAQcXAzBW +MCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vY3BzMCoGCCsG +AQUFBwICMB4aHGh0dHBzOi8vd3d3LnZlcmlzaWduLmNvbS9ycGEwDgYDVR0PAQH/ +BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8w +BwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZl +cmlzaWduLmNvbS92c2xvZ28uZ2lmMDQGA1UdHwQtMCswKaAnoCWGI2h0dHA6Ly9j +cmwudmVyaXNpZ24uY29tL3BjYTMtZzUuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggr +BgEFBQcwAYYYaHR0cDovL29jc3AudmVyaXNpZ24uY29tMB0GA1UdJQQWMBQGCCsG +AQUFBwMCBggrBgEFBQcDAzAoBgNVHREEITAfpB0wGzEZMBcGA1UEAxMQVmVyaVNp +Z25NUEtJLTItODAdBgNVHQ4EFgQUz5mp6nsm9EvJjo/X8AUm7+PSp50wHwYDVR0j +BBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJKoZIhvcNAQEFBQADggEBAFYi +5jSkxGHLSLkBrVaoZA/ZjJHEu8wM5a16oCJ/30c4Si1s0X9xGnzscKmx8E/kDwxT ++hVe/nSYSSSFgSYckRRHsExjjLuhNNTGRegNhSZzA9CpjGRt3HGS5kUFYBVZUTn8 +WBRr/tSk7XlrCAxBcuc3IgYJviPpP0SaHulhncyxkFz8PdKNrEI9ZTbUtD1AKI+b +EM8jJsxLIMuQH12MTDTKPNjlN9ZvpSC9NOsm2a4N58Wa96G0IZEzb4boWLslfHQO +WP51G2M/zjF8m48blp7FU3aEW5ytkfqs7ZO6XcghU8KCU2OvEg1QhxEbPVRSloos +nD2SGgiaBS7Hk6VIkdM= +-----END CERTIFICATE----- + diff --git a/src/main/resources/certs/readme.md b/src/main/resources/certs/readme.md new file mode 100644 index 00000000..1da9fafb --- /dev/null +++ b/src/main/resources/certs/readme.md @@ -0,0 +1,5 @@ +# Minecraft certificate chain + +Exported from the vanilla jar by extracting MOJANGCS.RSA from the jar and then running: + +`openssl pkcs7 -inform DER -in MOJANGCS.RSA -print_certs -out cert.pem` \ No newline at end of file diff --git a/src/main/resources/idea_run_config_template.xml b/src/main/resources/idea_run_config_template.xml index 55278f7b..635e7f5a 100644 --- a/src/main/resources/idea_run_config_template.xml +++ b/src/main/resources/idea_run_config_template.xml @@ -1,5 +1,5 @@ - +