From f20acadd0cff24404763579d4d9bda0b0bdb5014 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Tue, 22 Apr 2025 09:24:05 +0800 Subject: [PATCH 01/18] fix: wrong usage for hash --- .../includes/ExtractIncludes.kt | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/minecraft-codev-includes/src/main/kotlin/net/msrandom/minecraftcodev/includes/ExtractIncludes.kt b/minecraft-codev-includes/src/main/kotlin/net/msrandom/minecraftcodev/includes/ExtractIncludes.kt index bd3a84ac..2124d113 100644 --- a/minecraft-codev-includes/src/main/kotlin/net/msrandom/minecraftcodev/includes/ExtractIncludes.kt +++ b/minecraft-codev-includes/src/main/kotlin/net/msrandom/minecraftcodev/includes/ExtractIncludes.kt @@ -1,8 +1,12 @@ package net.msrandom.minecraftcodev.includes -import kotlinx.coroutines.* +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.runBlocking import net.msrandom.minecraftcodev.core.ListedFileHandler -import net.msrandom.minecraftcodev.core.utils.* +import net.msrandom.minecraftcodev.core.utils.hashFile +import net.msrandom.minecraftcodev.core.utils.toPath +import net.msrandom.minecraftcodev.core.utils.zipFileSystem import org.gradle.api.artifacts.transform.CacheableTransform import org.gradle.api.artifacts.transform.InputArtifact import org.gradle.api.artifacts.transform.InputArtifactDependencies @@ -12,9 +16,12 @@ import org.gradle.api.artifacts.transform.TransformParameters import org.gradle.api.file.FileCollection import org.gradle.api.file.FileSystemLocation import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import java.nio.file.StandardCopyOption -import kotlin.io.path.* +import kotlin.io.path.copyTo +import kotlin.io.path.deleteIfExists @CacheableTransform abstract class ExtractIncludes : TransformAction { @@ -51,11 +58,8 @@ abstract class ExtractIncludes : TransformAction { val inputHashes = runBlocking { classpath - .map { - async { - hashFileSuspend(it.toPath()) - } - }.awaitAll() + .map { async { hashFile(it.toPath()) } } + .awaitAll() .toHashSet() } From 37fa11c3ad16a4fa6a1622d2a0862db6cf130081 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Tue, 22 Apr 2025 09:20:26 +0800 Subject: [PATCH 02/18] feat(mixin): add mixin recording feature --- .../minecraftcodev/mixins/StripMixins.kt | 21 ++++++-- .../mixin/GradleMixinRecorderExtension.kt | 48 +++++++++++++++++++ .../mixins/mixin/GradleMixinService.kt | 6 +++ .../minecraftcodev/mixins/task/Mixin.kt | 30 ++++++++---- 4 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt index 9b292114..94860bc6 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt @@ -2,16 +2,22 @@ package net.msrandom.minecraftcodev.mixins import net.msrandom.minecraftcodev.core.utils.toPath import net.msrandom.minecraftcodev.core.utils.zipFileSystem -import org.gradle.api.artifacts.transform.* +import net.msrandom.minecraftcodev.mixins.mixin.GradleMixinRecorderExtension +import org.gradle.api.artifacts.transform.CacheableTransform +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters import org.gradle.api.file.FileSystemLocation import org.gradle.api.provider.Provider import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import java.nio.file.StandardCopyOption import kotlin.io.path.copyTo -import kotlin.io.path.deleteExisting import kotlin.io.path.extension import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.readLines +import kotlin.io.path.writeLines @CacheableTransform abstract class StripMixins : TransformAction { @@ -38,13 +44,18 @@ abstract class StripMixins : TransformAction { return } - val output = outputs.file("${input.nameWithoutExtension}-no-mixins.${input.extension}").toPath() + val output = outputs.file("${input.nameWithoutExtension}-mixins-stripped.${input.extension}").toPath() input.copyTo(output, StandardCopyOption.COPY_ATTRIBUTES) - zipFileSystem(output).use { + zipFileSystem(output).use { it -> val root = it.getPath("/") - handler.list(root).forEach { path -> root.resolve(path).deleteExisting() } + handler.list(root) + .mapNotNull { path -> GradleMixinRecorderExtension.RECORDED[path].takeIf { it.isNotEmpty() }?.let { it to path } } + .forEach { (mixins, path) -> + val path = root.resolve(path) + path.writeLines(path.readLines().filterNot { line -> mixins.any { line.contains(it) } }) + } handler.remove(root) } } diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt new file mode 100644 index 00000000..6d37410f --- /dev/null +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt @@ -0,0 +1,48 @@ +package net.msrandom.minecraftcodev.mixins.mixin + +import com.google.common.collect.MultimapBuilder +import com.google.common.collect.SetMultimap +import org.objectweb.asm.tree.ClassNode +import org.spongepowered.asm.mixin.MixinEnvironment +import org.spongepowered.asm.mixin.extensibility.IMixinInfo +import org.spongepowered.asm.mixin.transformer.ext.IExtension +import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext +import java.util.* + +class GradleMixinRecorderExtension : IExtension { + companion object { + val RECORDED: SetMultimap = + MultimapBuilder.SetMultimapBuilder.hashKeys().hashSetValues().build() + + private val targetClassContextClass = + Class.forName("org.spongepowered.asm.mixin.transformer.TargetClassContext") + private val classNameField = targetClassContextClass.getDeclaredField("className") + private val mixinsField = targetClassContextClass.getDeclaredField("mixins") + + init { + classNameField.isAccessible = true + mixinsField.isAccessible = true + } + + } + + override fun checkActive(environment: MixinEnvironment?) = true + + override fun preApply(context: ITargetClassContext) {} + + override fun postApply(context: ITargetClassContext) { + val className = classNameField.get(context) as String + val mixins = mixinsField.get(context) as SortedSet + if (mixins.isNotEmpty()) { + RECORDED.putAll(className, mixins.map { it.className }) + } + } + + override fun export( + env: MixinEnvironment?, + name: String?, + force: Boolean, + classNode: ClassNode? + ) { + } +} \ No newline at end of file diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt index 3475a085..2ca9430d 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt @@ -9,6 +9,7 @@ import org.spongepowered.asm.mixin.MixinEnvironment.Side import org.spongepowered.asm.mixin.Mixins import org.spongepowered.asm.mixin.transformer.IMixinTransformer import org.spongepowered.asm.mixin.transformer.IMixinTransformerFactory +import org.spongepowered.asm.mixin.transformer.ext.Extensions import org.spongepowered.asm.service.IClassBytecodeProvider import org.spongepowered.asm.service.IClassProvider import org.spongepowered.asm.service.IClassTracker @@ -35,6 +36,11 @@ class GradleMixinService : MixinServiceAbstract() { getInternal(IMixinTransformerFactory::class.java).createTransformer() } + override fun init() { + (transformer.extensions as Extensions).add(GradleMixinRecorderExtension()) + super.init() + } + /** * Thread safe accessor */ diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt index 85b00bfe..fd80a66b 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt @@ -1,7 +1,6 @@ package net.msrandom.minecraftcodev.mixins.task import net.msrandom.minecraftcodev.core.utils.getAsPath -import net.msrandom.minecraftcodev.core.utils.walk import net.msrandom.minecraftcodev.core.utils.zipFileSystem import net.msrandom.minecraftcodev.mixins.mixin.GradleMixinService import net.msrandom.minecraftcodev.mixins.mixinListingRules @@ -9,14 +8,27 @@ import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.* +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.CompileClasspath +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.TaskAction import org.spongepowered.asm.mixin.MixinEnvironment.Side import org.spongepowered.asm.mixin.Mixins import org.spongepowered.asm.service.MixinService import java.io.File -import java.nio.file.Path +import java.nio.file.FileVisitResult import java.nio.file.StandardCopyOption -import kotlin.io.path.* +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.copyTo +import kotlin.io.path.createParentDirectories +import kotlin.io.path.fileVisitor +import kotlin.io.path.readBytes +import kotlin.io.path.visitFileTree +import kotlin.io.path.writeBytes @CacheableTask abstract class Mixin : DefaultTask() { @@ -53,6 +65,7 @@ abstract class Mixin : DefaultTask() { side.convention(Side.UNKNOWN) } + @OptIn(ExperimentalPathApi::class) @TaskAction fun mixin() { val input = inputFile.getAsPath() @@ -80,12 +93,12 @@ abstract class Mixin : DefaultTask() { val root = inputFs.getPath("/") zipFileSystem(output, true).use { outputFs -> - root.walk { - for (path in filter(Path::isRegularFile)) { + root.visitFileTree(fileVisitor { + onVisitFile { path, attr -> val pathString = path.toString() val outputPath = outputFs.getPath(pathString) - outputPath.parent?.createDirectories() + outputPath.createParentDirectories() if (pathString.endsWith(".class")) { val pathName = root.relativize(path).toString() @@ -99,8 +112,9 @@ abstract class Mixin : DefaultTask() { } else { path.copyTo(outputPath, StandardCopyOption.COPY_ATTRIBUTES) } + return@onVisitFile FileVisitResult.CONTINUE } - } + }) } } } From c223ca74032fdd9221688ecc65b5af4ebab3973b Mon Sep 17 00:00:00 2001 From: SettingDust Date: Wed, 23 Apr 2025 12:50:23 +0800 Subject: [PATCH 03/18] chore(forge): remove signature file --- .../forge/task/ResolvePatchedMinecraft.kt | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/minecraft-codev-forge/src/main/kotlin/net/msrandom/minecraftcodev/forge/task/ResolvePatchedMinecraft.kt b/minecraft-codev-forge/src/main/kotlin/net/msrandom/minecraftcodev/forge/task/ResolvePatchedMinecraft.kt index e4c587f1..37c6a8bf 100644 --- a/minecraft-codev-forge/src/main/kotlin/net/msrandom/minecraftcodev/forge/task/ResolvePatchedMinecraft.kt +++ b/minecraft-codev-forge/src/main/kotlin/net/msrandom/minecraftcodev/forge/task/ResolvePatchedMinecraft.kt @@ -10,7 +10,6 @@ import net.msrandom.minecraftcodev.core.task.CachedMinecraftTask import net.msrandom.minecraftcodev.core.task.versionList import net.msrandom.minecraftcodev.core.utils.cacheExpensiveOperation import net.msrandom.minecraftcodev.core.utils.getAsPath -import net.msrandom.minecraftcodev.core.utils.walk import net.msrandom.minecraftcodev.core.utils.zipFileSystem import net.msrandom.minecraftcodev.forge.McpConfigFile import net.msrandom.minecraftcodev.forge.PatchLibrary @@ -31,18 +30,24 @@ import org.gradle.process.ExecOperations import java.io.Closeable import java.io.OutputStream import java.io.PrintStream +import java.nio.file.FileVisitResult import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardCopyOption import javax.inject.Inject +import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.copyTo import kotlin.io.path.deleteExisting +import kotlin.io.path.deleteIfExists +import kotlin.io.path.extension +import kotlin.io.path.fileVisitor import kotlin.io.path.isDirectory -import kotlin.io.path.isRegularFile import kotlin.io.path.listDirectoryEntries +import kotlin.io.path.name +import kotlin.io.path.visitFileTree import kotlin.io.path.writeLines -const val PATCH_OPERATION_VERSION = 1 +const val PATCH_OPERATION_VERSION = 2 abstract class ResolvePatchedMinecraft : CachedMinecraftTask() { abstract val version: Property @@ -99,6 +104,7 @@ abstract class ResolvePatchedMinecraft : CachedMinecraftTask() { ) } + @OptIn(ExperimentalPathApi::class) private fun resolve(cacheDirectory: Path, outputPath: Path, clientExtra: Path) { val isOffline = cacheParameters.getIsOffline().get() @@ -292,16 +298,19 @@ abstract class ResolvePatchedMinecraft : CachedMinecraftTask() { clientJar.copyTo(clientExtra, StandardCopyOption.REPLACE_EXISTING) zipFileSystem(clientExtra).use { clientZip -> - clientZip.getPath("/").walk { - for (path in filter(Path::isRegularFile)) { - if (path.toString().endsWith(".class") || path.startsWith("/META-INF")) { + clientZip.getPath("/").visitFileTree(fileVisitor { + onVisitFile { path, attr -> + if (path.extension == "class" || path.getName(0).name == "META-INF") { path.deleteExisting() } + FileVisitResult.CONTINUE } - } + }) } addMinecraftMarker(outputPath) + + zipFileSystem(outputPath).use { fs -> fs.getPath("META-INF/FORGE.SF").deleteIfExists() } } @TaskAction From 3ca18b44f44a245d8b9a2b64f7ae70bdbdae0caa Mon Sep 17 00:00:00 2001 From: SettingDust Date: Wed, 23 Apr 2025 12:50:51 +0800 Subject: [PATCH 04/18] perf(mixin): faster path concat --- .../minecraftcodev/mixins/task/Mixin.kt | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt index fd80a66b..ba8c117b 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt @@ -1,5 +1,6 @@ package net.msrandom.minecraftcodev.mixins.task +import com.google.common.base.Joiner import net.msrandom.minecraftcodev.core.utils.getAsPath import net.msrandom.minecraftcodev.core.utils.zipFileSystem import net.msrandom.minecraftcodev.mixins.mixin.GradleMixinService @@ -25,13 +26,19 @@ import java.nio.file.StandardCopyOption import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.copyTo import kotlin.io.path.createParentDirectories +import kotlin.io.path.extension import kotlin.io.path.fileVisitor +import kotlin.io.path.name import kotlin.io.path.readBytes import kotlin.io.path.visitFileTree import kotlin.io.path.writeBytes @CacheableTask abstract class Mixin : DefaultTask() { + companion object { + private val JOINER = Joiner.on('.') + } + abstract val inputFile: RegularFileProperty @InputFile @Classpath @@ -95,22 +102,21 @@ abstract class Mixin : DefaultTask() { zipFileSystem(output, true).use { outputFs -> root.visitFileTree(fileVisitor { onVisitFile { path, attr -> - val pathString = path.toString() - val outputPath = outputFs.getPath(pathString) + val outputPath = outputFs.getPath( + path.getName(0).name, + *path.drop(1).map { it.name }.toList().toTypedArray() + ) outputPath.createParentDirectories() - if (pathString.endsWith(".class")) { - val pathName = root.relativize(path).toString() + if (path.extension == "class") { + val pathName = JOINER.join(root.relativize(path)) - val name = - pathName - .substring(0, pathName.length - ".class".length) - .replace(File.separatorChar, '.') + val name = pathName.substring(0, pathName.length - ".class".length) outputPath.writeBytes(transformer.transformClassBytes(name, name, path.readBytes())) } else { - path.copyTo(outputPath, StandardCopyOption.COPY_ATTRIBUTES) + path.copyTo(outputPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING) } return@onVisitFile FileVisitResult.CONTINUE } From 8cd25de1f24595c36cdf66de2b5a98a2b359c670 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Wed, 23 Apr 2025 12:51:00 +0800 Subject: [PATCH 05/18] feat(mixin): add logger --- .../mixins/mixin/GradleMixinLogger.kt | 47 +++++++++++++++++++ .../mixins/mixin/GradleMixinService.kt | 4 ++ 2 files changed, 51 insertions(+) create mode 100644 minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt new file mode 100644 index 00000000..181555ed --- /dev/null +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt @@ -0,0 +1,47 @@ +package net.msrandom.minecraftcodev.mixins.mixin + +import org.slf4j.LoggerFactory +import org.spongepowered.asm.logging.Level +import org.spongepowered.asm.logging.LoggerAdapterAbstract + +class GradleMixinLogger(val name: String) : LoggerAdapterAbstract(name) { + companion object { + private const val PREFIX = "[Mixin] " + } + + private val logger = LoggerFactory.getLogger(name) + + override fun getType() = "Gradle Logger" + + override fun catching(level: Level, t: Throwable) = + logger.warn("${PREFIX}Catching {}: {}", t.javaClass.getName(), t.message, t) + + override fun log(level: Level, message: String, vararg params: Any) { + val message = PREFIX + message + when (level) { + Level.TRACE -> logger.trace(message, *params) + Level.DEBUG -> logger.debug(message, *params) + Level.INFO -> logger.info(message, *params) + Level.WARN -> logger.warn(message, *params) + Level.ERROR -> logger.error(message, *params) + Level.FATAL -> logger.error("[FATAL] $message", *params) + } + } + + override fun log(level: Level, message: String, t: Throwable) { + val message = PREFIX + message + when (level) { + Level.TRACE -> logger.trace(message, t) + Level.DEBUG -> logger.debug(message, t) + Level.INFO -> logger.info(message, t) + Level.WARN -> logger.warn(message, t) + Level.ERROR -> logger.error(message, t) + Level.FATAL -> logger.error("[FATAL] $message", t) + } + } + + override fun throwing(t: T): T { + logger.warn("${PREFIX}Throwing {}: {}", t.javaClass.getName(), t.message, t) + return t + } +} \ No newline at end of file diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt index 2ca9430d..89bfb60d 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt @@ -143,6 +143,10 @@ class GradleMixinService : MixinServiceAbstract() { this.phaseConsumer = phaseConsumer } + override fun createLogger(name: String): ILogger { + return GradleMixinLogger(name) + } + companion object { private val registeredConfigsField = Mixins::class.java.getDeclaredField("registeredConfigs").apply { isAccessible = true } private val sideField = MixinEnvironment::class.java.getDeclaredField("side").apply { isAccessible = true } From 85320d804de52fff74a92b7552abb4258492a6b4 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Wed, 23 Apr 2025 12:51:26 +0800 Subject: [PATCH 06/18] feat(mixin): use input classpath in service --- .../mixins/mixin/GradleMixinService.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt index 89bfb60d..9e14557d 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt @@ -3,6 +3,7 @@ package net.msrandom.minecraftcodev.mixins.mixin import org.objectweb.asm.ClassReader import org.objectweb.asm.tree.ClassNode import org.spongepowered.asm.launch.platform.container.IContainerHandle +import org.spongepowered.asm.logging.ILogger import org.spongepowered.asm.mixin.MixinEnvironment import org.spongepowered.asm.mixin.MixinEnvironment.Phase import org.spongepowered.asm.mixin.MixinEnvironment.Side @@ -17,7 +18,6 @@ import org.spongepowered.asm.service.MixinServiceAbstract import org.spongepowered.asm.util.IConsumer import java.io.File import java.io.FileNotFoundException -import java.net.URL import java.net.URLClassLoader import java.nio.file.Path import javax.annotation.concurrent.NotThreadSafe @@ -71,14 +71,24 @@ class GradleMixinService : MixinServiceAbstract() { override fun getClassProvider() = object : IClassProvider { @Deprecated("Deprecated in Java", ReplaceWith("emptyArray()", "java.net.URL")) - override fun getClassPath() = emptyArray() + override fun getClassPath() = + if (this@GradleMixinService::classpath.isInitialized) classpath.urLs else emptyArray() - override fun findClass(name: String) = Class.forName(name) + override fun findClass(name: String) = + if (this@GradleMixinService::classpath.isInitialized) classpath.loadClass(name) else Class.forName(name) override fun findClass( name: String, initialize: Boolean, - ) = Class.forName(name, initialize, javaClass.classLoader) + ): Class<*>? { + return try { + Class.forName(name, initialize, javaClass.classLoader) + } catch (e: ClassNotFoundException) { + if (this@GradleMixinService::classpath.isInitialized) + Class.forName(name, initialize, classpath) + else throw e + } + } override fun findAgentClass( name: String, @@ -131,7 +141,8 @@ class GradleMixinService : MixinServiceAbstract() { override fun getNestedContainers() = emptyList() } - override fun getResourceAsStream(name: String) = classpath.getResourceAsStream(name) ?: Path(name).takeIf(Path::exists)?.inputStream() + override fun getResourceAsStream(name: String) = + classpath.getResourceAsStream(name) ?: Path(name).takeIf(Path::exists)?.inputStream() @Deprecated("Deprecated in Java") override fun wire( @@ -148,7 +159,8 @@ class GradleMixinService : MixinServiceAbstract() { } companion object { - private val registeredConfigsField = Mixins::class.java.getDeclaredField("registeredConfigs").apply { isAccessible = true } + private val registeredConfigsField = + Mixins::class.java.getDeclaredField("registeredConfigs").apply { isAccessible = true } private val sideField = MixinEnvironment::class.java.getDeclaredField("side").apply { isAccessible = true } } } From 820fcd619dbf55cc14ccf9dc8d25978652df6a7b Mon Sep 17 00:00:00 2001 From: SettingDust Date: Wed, 23 Apr 2025 13:46:39 +0800 Subject: [PATCH 07/18] fix(mixin): use config as recorded key --- .../msrandom/minecraftcodev/mixins/StripMixins.kt | 15 ++++++++++++++- .../mixins/mixin/GradleMixinRecorderExtension.kt | 9 ++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt index 94860bc6..3a81bd00 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt @@ -44,6 +44,17 @@ abstract class StripMixins : TransformAction { return } + val needStrip = zipFileSystem(input).use { fs -> + val root = fs.getPath("/") + handler.list(root).any { GradleMixinRecorderExtension.CONFIG_TO_MIXINS.containsKey(it) } + } + + if (!needStrip) { + outputs.file(inputFile) + + return + } + val output = outputs.file("${input.nameWithoutExtension}-mixins-stripped.${input.extension}").toPath() input.copyTo(output, StandardCopyOption.COPY_ATTRIBUTES) @@ -51,7 +62,9 @@ abstract class StripMixins : TransformAction { zipFileSystem(output).use { it -> val root = it.getPath("/") handler.list(root) - .mapNotNull { path -> GradleMixinRecorderExtension.RECORDED[path].takeIf { it.isNotEmpty() }?.let { it to path } } + .mapNotNull { path -> + GradleMixinRecorderExtension.CONFIG_TO_MIXINS[path].takeIf { it.isNotEmpty() }?.let { it to path } + } .forEach { (mixins, path) -> val path = root.resolve(path) path.writeLines(path.readLines().filterNot { line -> mixins.any { line.contains(it) } }) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt index 6d37410f..5aa6c46f 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt @@ -11,16 +11,14 @@ import java.util.* class GradleMixinRecorderExtension : IExtension { companion object { - val RECORDED: SetMultimap = + val CONFIG_TO_MIXINS: SetMultimap = MultimapBuilder.SetMultimapBuilder.hashKeys().hashSetValues().build() private val targetClassContextClass = Class.forName("org.spongepowered.asm.mixin.transformer.TargetClassContext") - private val classNameField = targetClassContextClass.getDeclaredField("className") private val mixinsField = targetClassContextClass.getDeclaredField("mixins") init { - classNameField.isAccessible = true mixinsField.isAccessible = true } @@ -31,10 +29,11 @@ class GradleMixinRecorderExtension : IExtension { override fun preApply(context: ITargetClassContext) {} override fun postApply(context: ITargetClassContext) { - val className = classNameField.get(context) as String val mixins = mixinsField.get(context) as SortedSet if (mixins.isNotEmpty()) { - RECORDED.putAll(className, mixins.map { it.className }) + for (info in mixins) { + CONFIG_TO_MIXINS.put(info.config.name, info.name) + } } } From 880b214dd4cede8078b2c456bbc0efd0490b0c83 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Wed, 23 Apr 2025 22:41:04 +0800 Subject: [PATCH 08/18] fix(mixin): detect more refmap json --- .../net/msrandom/minecraftcodev/remapper/JarRemapper.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/minecraft-codev-remapper/src/main/kotlin/net/msrandom/minecraftcodev/remapper/JarRemapper.kt b/minecraft-codev-remapper/src/main/kotlin/net/msrandom/minecraftcodev/remapper/JarRemapper.kt index 4a6700d8..9982f07a 100644 --- a/minecraft-codev-remapper/src/main/kotlin/net/msrandom/minecraftcodev/remapper/JarRemapper.kt +++ b/minecraft-codev-remapper/src/main/kotlin/net/msrandom/minecraftcodev/remapper/JarRemapper.kt @@ -17,12 +17,11 @@ import java.io.File import java.nio.file.FileSystems import java.nio.file.Path import java.util.* -import java.util.EnumSet import java.util.concurrent.CompletableFuture import kotlin.io.path.exists import kotlin.io.path.listDirectoryEntries -const val REMAP_OPERATION_VERSION = 3 +const val REMAP_OPERATION_VERSION = 4 private fun mappingProvider(mappings: MappingTreeView, sourceNamespace: String, targetNamespace: String) = IMappingProvider { val rebuild = mappings.srcNamespace != sourceNamespace @@ -151,7 +150,7 @@ private fun mappingProvider(mappings: MappingTreeView, sourceNamespace: String, } private fun hasRefmaps(path: Path) = FileSystems.newFileSystem(path, null).use { - it.getPath("/").listDirectoryEntries("*.refmap.json").isNotEmpty() + it.getPath("/").listDirectoryEntries("*refmap.json").isNotEmpty() } object JarRemapper { From 8fac40c599c5e66874256dd9c69a87c31dd2d939 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Wed, 23 Apr 2025 22:41:18 +0800 Subject: [PATCH 09/18] refactor(mixin): runtime refmap remap --- gradle.properties | 2 +- minecraft-codev-mixins/build.gradle.kts | 3 +- .../mixins/mixin/MappingIoRemapperAdapter.kt | 43 +++++++++++++++++ .../minecraftcodev/mixins/task/Mixin.kt | 48 +++++++++++++++++-- 4 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt diff --git a/gradle.properties b/gradle.properties index cb26bc53..9fba829b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ org.gradle.configuration-cache=true # org.gradle.unsafe.isolated-projects=true group=net.msrandom -version=0.5.30 +version=0.5.31 kotlin.code.style=official diff --git a/minecraft-codev-mixins/build.gradle.kts b/minecraft-codev-mixins/build.gradle.kts index 0378a3ce..1af5599d 100644 --- a/minecraft-codev-mixins/build.gradle.kts +++ b/minecraft-codev-mixins/build.gradle.kts @@ -12,7 +12,8 @@ gradlePlugin { } dependencies { - api(group = "net.fabricmc", name = "sponge-mixin", version = "0.15.0+mixin.0.8.7") + api(group = "net.fabricmc", name = "sponge-mixin", version = "0.15.5+mixin.0.8.7") + api(group = "net.fabricmc", name = "mapping-io", version = "0.7.1") implementation(projects.minecraftCodevCore) } diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt new file mode 100644 index 00000000..d914c3c2 --- /dev/null +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt @@ -0,0 +1,43 @@ +package net.msrandom.minecraftcodev.mixins.mixin + +import net.fabricmc.mappingio.tree.MappingTreeView +import org.spongepowered.asm.mixin.extensibility.IRemapper + +class MappingIoRemapperAdapter( + val mappings: MappingTreeView, + sourceNamespace: String, + targetNamespace: String +) : IRemapper { + private val sourceId = mappings.getNamespaceId(sourceNamespace) + private val targetId = mappings.getNamespaceId(targetNamespace) + + private val methods = HashMap() + private val fields = HashMap() + + init { + for (classMapping in mappings.classes) { + for (methodMapping in classMapping.methods) { + methods.put(methodMapping.getName(sourceId)!!, methodMapping.getName(targetId)!!) + } + for (fieldMapping in classMapping.fields) { + fields.put(fieldMapping.getName(sourceId)!!, fieldMapping.getName(targetId)!!) + } + } + } + + override fun mapMethodName(owner: String?, name: String, desc: String?) = methods[name] ?: name + + override fun mapFieldName(owner: String?, name: String, desc: String?) = fields[name] ?: name + + override fun map(typeName: String) = + mappings.mapClassName(typeName, sourceId, targetId) + + override fun unmap(typeName: String) = + mappings.mapClassName(typeName, targetId, sourceId) + + override fun mapDesc(desc: String) = + mappings.mapDesc(desc, sourceId, targetId) + + override fun unmapDesc(desc: String) = + mappings.mapDesc(desc, targetId, sourceId) +} \ No newline at end of file diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt index ba8c117b..c4418182 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt @@ -1,9 +1,12 @@ package net.msrandom.minecraftcodev.mixins.task import com.google.common.base.Joiner +import net.fabricmc.mappingio.format.tiny.Tiny2FileReader +import net.fabricmc.mappingio.tree.MemoryMappingTree import net.msrandom.minecraftcodev.core.utils.getAsPath import net.msrandom.minecraftcodev.core.utils.zipFileSystem import net.msrandom.minecraftcodev.mixins.mixin.GradleMixinService +import net.msrandom.minecraftcodev.mixins.mixin.MappingIoRemapperAdapter import net.msrandom.minecraftcodev.mixins.mixinListingRules import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection @@ -16,16 +19,19 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction +import org.spongepowered.asm.mixin.MixinEnvironment import org.spongepowered.asm.mixin.MixinEnvironment.Side import org.spongepowered.asm.mixin.Mixins import org.spongepowered.asm.service.MixinService -import java.io.File import java.nio.file.FileVisitResult import java.nio.file.StandardCopyOption import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.copyTo import kotlin.io.path.createParentDirectories +import kotlin.io.path.deleteIfExists import kotlin.io.path.extension import kotlin.io.path.fileVisitor import kotlin.io.path.name @@ -57,6 +63,17 @@ abstract class Mixin : DefaultTask() { abstract val side: Property @Input get + abstract val mappings: RegularFileProperty + @InputFile + @PathSensitive(PathSensitivity.NONE) + get + + abstract val sourceNamespace: Property + @Input get + + abstract val targetNamespace: Property + @Input get + abstract val outputFile: RegularFileProperty @OutputFile get @@ -78,7 +95,28 @@ abstract class Mixin : DefaultTask() { val input = inputFile.getAsPath() val output = outputFile.getAsPath() - (MixinService.getService() as GradleMixinService).use(classpath + mixinFiles + project.files(input), side.get()) { + output.deleteIfExists() + + MixinEnvironment.getDefaultEnvironment().setOption( + MixinEnvironment.Option.REFMAP_REMAP, + System.getProperty("mixin.env.remapRefMap", "true").toBoolean()) + + (MixinService.getService() as GradleMixinService).use( + classpath + mixinFiles + project.files(input), + side.get() + ) { + val mappings = MemoryMappingTree() + + Tiny2FileReader.read(this@Mixin.mappings.asFile.get().reader(), mappings) + + MixinEnvironment.getDefaultEnvironment().remappers.add( + MappingIoRemapperAdapter( + mappings, + sourceNamespace.get(), + targetNamespace.get() + ) + ) + CLASSPATH@ for (mixinFile in mixinFiles + project.files(input)) { zipFileSystem(mixinFile.toPath()).use fs@{ val root = it.getPath("/") @@ -116,7 +154,11 @@ abstract class Mixin : DefaultTask() { outputPath.writeBytes(transformer.transformClassBytes(name, name, path.readBytes())) } else { - path.copyTo(outputPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING) + path.copyTo( + outputPath, + StandardCopyOption.COPY_ATTRIBUTES, + StandardCopyOption.REPLACE_EXISTING + ) } return@onVisitFile FileVisitResult.CONTINUE } From 54907e61dbcb4da647d0fc6dba0ba32a9067adcc Mon Sep 17 00:00:00 2001 From: SettingDust Date: Thu, 24 Apr 2025 10:20:39 +0800 Subject: [PATCH 10/18] feat(mixin): cache the applied mixins --- .../core/utils/SetMultimapSerializer.kt | 41 +++++++++++++++++++ .../minecraftcodev/mixins/StripMixins.kt | 33 +++++++++++++-- .../mixin/GradleMixinRecorderExtension.kt | 9 ++-- .../mixins/mixin/GradleMixinService.kt | 6 ++- .../minecraftcodev/mixins/task/Mixin.kt | 32 ++++++++++++++- .../minecraftcodev/remapper/RemapAction.kt | 3 -- 6 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 minecraft-codev-core/src/main/kotlin/net/msrandom/minecraftcodev/core/utils/SetMultimapSerializer.kt diff --git a/minecraft-codev-core/src/main/kotlin/net/msrandom/minecraftcodev/core/utils/SetMultimapSerializer.kt b/minecraft-codev-core/src/main/kotlin/net/msrandom/minecraftcodev/core/utils/SetMultimapSerializer.kt new file mode 100644 index 00000000..4e508d0d --- /dev/null +++ b/minecraft-codev-core/src/main/kotlin/net/msrandom/minecraftcodev/core/utils/SetMultimapSerializer.kt @@ -0,0 +1,41 @@ +package net.msrandom.minecraftcodev.core.utils + +import com.google.common.collect.Multimaps +import com.google.common.collect.SetMultimap +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.builtins.SetSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.mapSerialDescriptor +import kotlinx.serialization.descriptors.setSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@Suppress("UnstableApiUsage") +@OptIn(ExperimentalSerializationApi::class) +class SetMultimapSerializer( + private val keySerializer: KSerializer, + private val valueSerializer: KSerializer, + private val valueFactory: () -> MutableSet = { HashSet() } +) : KSerializer> { + override val descriptor = + SerialDescriptor( + "Multimap", + mapSerialDescriptor( + keySerializer.descriptor, setSerialDescriptor(valueSerializer.descriptor) + ) + ) + + override fun serialize(encoder: Encoder, value: SetMultimap) { + MapSerializer(keySerializer, SetSerializer(valueSerializer)).serialize(encoder, Multimaps.asMap(value)) + } + + override fun deserialize(decoder: Decoder): SetMultimap { + val map = Multimaps.newSetMultimap(HashMap(), valueFactory) + for (entry in MapSerializer(keySerializer, SetSerializer(valueSerializer)).deserialize(decoder)) { + map.putAll(entry.key, entry.value) + } + return map + } +} \ No newline at end of file diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt index 3a81bd00..1a95853d 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/StripMixins.kt @@ -1,15 +1,22 @@ package net.msrandom.minecraftcodev.mixins +import com.google.common.collect.HashMultimap +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import net.msrandom.minecraftcodev.core.utils.SetMultimapSerializer import net.msrandom.minecraftcodev.core.utils.toPath import net.msrandom.minecraftcodev.core.utils.zipFileSystem -import net.msrandom.minecraftcodev.mixins.mixin.GradleMixinRecorderExtension import org.gradle.api.artifacts.transform.CacheableTransform import org.gradle.api.artifacts.transform.InputArtifact import org.gradle.api.artifacts.transform.TransformAction import org.gradle.api.artifacts.transform.TransformOutputs import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.FileSystemLocation import org.gradle.api.provider.Provider +import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import java.nio.file.StandardCopyOption @@ -20,12 +27,24 @@ import kotlin.io.path.readLines import kotlin.io.path.writeLines @CacheableTransform -abstract class StripMixins : TransformAction { +abstract class StripMixins : TransformAction { + abstract class Parameters : TransformParameters { + abstract val appliedMixins: ConfigurableFileCollection + @InputFiles + @PathSensitive(PathSensitivity.NONE) + get + + init { + appliedMixins.convention() + } + } + abstract val inputFile: Provider @InputArtifact @PathSensitive(PathSensitivity.NONE) get + @OptIn(ExperimentalSerializationApi::class) override fun transform(outputs: TransformOutputs) { val input = inputFile.get().toPath() @@ -44,9 +63,15 @@ abstract class StripMixins : TransformAction { return } + val appliedMixins = HashMultimap.create() + val serializer = SetMultimapSerializer(String.serializer(), String.serializer()) + for (multimap in parameters.appliedMixins.map { Json.decodeFromStream(serializer, it.inputStream()) }) { + appliedMixins.putAll(multimap) + } + val needStrip = zipFileSystem(input).use { fs -> val root = fs.getPath("/") - handler.list(root).any { GradleMixinRecorderExtension.CONFIG_TO_MIXINS.containsKey(it) } + handler.list(root).any { appliedMixins.containsKey(it) } } if (!needStrip) { @@ -63,7 +88,7 @@ abstract class StripMixins : TransformAction { val root = it.getPath("/") handler.list(root) .mapNotNull { path -> - GradleMixinRecorderExtension.CONFIG_TO_MIXINS[path].takeIf { it.isNotEmpty() }?.let { it to path } + appliedMixins[path].takeIf { it.isNotEmpty() }?.let { it to path } } .forEach { (mixins, path) -> val path = root.resolve(path) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt index 5aa6c46f..bccd24f4 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinRecorderExtension.kt @@ -1,6 +1,5 @@ package net.msrandom.minecraftcodev.mixins.mixin -import com.google.common.collect.MultimapBuilder import com.google.common.collect.SetMultimap import org.objectweb.asm.tree.ClassNode import org.spongepowered.asm.mixin.MixinEnvironment @@ -11,9 +10,6 @@ import java.util.* class GradleMixinRecorderExtension : IExtension { companion object { - val CONFIG_TO_MIXINS: SetMultimap = - MultimapBuilder.SetMultimapBuilder.hashKeys().hashSetValues().build() - private val targetClassContextClass = Class.forName("org.spongepowered.asm.mixin.transformer.TargetClassContext") private val mixinsField = targetClassContextClass.getDeclaredField("mixins") @@ -21,9 +17,10 @@ class GradleMixinRecorderExtension : IExtension { init { mixinsField.isAccessible = true } - } + var appliedMixins: SetMultimap? = null + override fun checkActive(environment: MixinEnvironment?) = true override fun preApply(context: ITargetClassContext) {} @@ -32,7 +29,7 @@ class GradleMixinRecorderExtension : IExtension { val mixins = mixinsField.get(context) as SortedSet if (mixins.isNotEmpty()) { for (info in mixins) { - CONFIG_TO_MIXINS.put(info.config.name, info.name) + appliedMixins!!.put(info.config.name, info.name) } } } diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt index 9e14557d..a6741396 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt @@ -36,8 +36,10 @@ class GradleMixinService : MixinServiceAbstract() { getInternal(IMixinTransformerFactory::class.java).createTransformer() } + val recorderExtension = GradleMixinRecorderExtension() + override fun init() { - (transformer.extensions as Extensions).add(GradleMixinRecorderExtension()) + (transformer.extensions as Extensions).add(recorderExtension) super.init() } @@ -61,6 +63,8 @@ class GradleMixinService : MixinServiceAbstract() { Mixins.getConfigs().clear() + recorderExtension.appliedMixins = null + this.action() } diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt index c4418182..ade1f6c2 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt @@ -1,8 +1,14 @@ package net.msrandom.minecraftcodev.mixins.task import com.google.common.base.Joiner +import com.google.common.collect.HashMultimap +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToStream import net.fabricmc.mappingio.format.tiny.Tiny2FileReader import net.fabricmc.mappingio.tree.MemoryMappingTree +import net.msrandom.minecraftcodev.core.utils.SetMultimapSerializer import net.msrandom.minecraftcodev.core.utils.getAsPath import net.msrandom.minecraftcodev.core.utils.zipFileSystem import net.msrandom.minecraftcodev.mixins.mixin.GradleMixinService @@ -35,6 +41,7 @@ import kotlin.io.path.deleteIfExists import kotlin.io.path.extension import kotlin.io.path.fileVisitor import kotlin.io.path.name +import kotlin.io.path.outputStream import kotlin.io.path.readBytes import kotlin.io.path.visitFileTree import kotlin.io.path.writeBytes @@ -77,6 +84,9 @@ abstract class Mixin : DefaultTask() { abstract val outputFile: RegularFileProperty @OutputFile get + abstract val appliedMixins: RegularFileProperty + @OutputFile get + init { outputFile.convention( project.layout.file( @@ -86,10 +96,18 @@ abstract class Mixin : DefaultTask() { ), ) + appliedMixins.convention( + project.layout.file( + inputFile.map { + temporaryDir.resolve("${it.asFile.nameWithoutExtension}-applied-mixins.json") + }, + ), + ) + side.convention(Side.UNKNOWN) } - @OptIn(ExperimentalPathApi::class) + @OptIn(ExperimentalPathApi::class, ExperimentalSerializationApi::class) @TaskAction fun mixin() { val input = inputFile.getAsPath() @@ -99,7 +117,8 @@ abstract class Mixin : DefaultTask() { MixinEnvironment.getDefaultEnvironment().setOption( MixinEnvironment.Option.REFMAP_REMAP, - System.getProperty("mixin.env.remapRefMap", "true").toBoolean()) + System.getProperty("mixin.env.remapRefMap", "true").toBoolean() + ) (MixinService.getService() as GradleMixinService).use( classpath + mixinFiles + project.files(input), @@ -117,6 +136,9 @@ abstract class Mixin : DefaultTask() { ) ) + val appliedMixins = HashMultimap.create() + recorderExtension.appliedMixins = appliedMixins + CLASSPATH@ for (mixinFile in mixinFiles + project.files(input)) { zipFileSystem(mixinFile.toPath()).use fs@{ val root = it.getPath("/") @@ -165,6 +187,12 @@ abstract class Mixin : DefaultTask() { }) } } + + Json.encodeToStream( + SetMultimapSerializer(String.serializer(), String.serializer()), + appliedMixins, + this@Mixin.appliedMixins.asFile.get().outputStream() + ) } } } diff --git a/minecraft-codev-remapper/src/main/kotlin/net/msrandom/minecraftcodev/remapper/RemapAction.kt b/minecraft-codev-remapper/src/main/kotlin/net/msrandom/minecraftcodev/remapper/RemapAction.kt index 3414e88f..0e4fe42a 100644 --- a/minecraft-codev-remapper/src/main/kotlin/net/msrandom/minecraftcodev/remapper/RemapAction.kt +++ b/minecraft-codev-remapper/src/main/kotlin/net/msrandom/minecraftcodev/remapper/RemapAction.kt @@ -72,9 +72,6 @@ abstract class RemapAction : TransformAction { } } - abstract val objectFactory: ObjectFactory - @Inject get - abstract val inputFile: Provider @InputArtifact @PathSensitive(PathSensitivity.NONE) From e01425dbd413ce943e9006c02354520bcd353d90 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Thu, 24 Apr 2025 13:44:40 +0800 Subject: [PATCH 11/18] fix(mixin): null pointer since the tiny mapping may have null if needn't map --- .../mixins/mixin/MappingIoRemapperAdapter.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt index d914c3c2..8bbaa199 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt @@ -17,10 +17,14 @@ class MappingIoRemapperAdapter( init { for (classMapping in mappings.classes) { for (methodMapping in classMapping.methods) { - methods.put(methodMapping.getName(sourceId)!!, methodMapping.getName(targetId)!!) + val sourceName = methodMapping.getName(sourceId) ?: continue + val targetName = methodMapping.getName(targetId) ?: continue + methods.put(sourceName, targetName) } for (fieldMapping in classMapping.fields) { - fields.put(fieldMapping.getName(sourceId)!!, fieldMapping.getName(targetId)!!) + val sourceName = fieldMapping.getName(sourceId) ?: continue + val targetName = fieldMapping.getName(targetId) ?: continue + fields.put(sourceName, targetName) } } } From 6dd07784310eaa056e48222863e90e9daae58437 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Thu, 24 Apr 2025 20:20:01 +0800 Subject: [PATCH 12/18] fix(mixin): null pointer since the mixin can target null name method --- .../msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt | 2 +- .../minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt index 181555ed..de647267 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt @@ -14,7 +14,7 @@ class GradleMixinLogger(val name: String) : LoggerAdapterAbstract(name) { override fun getType() = "Gradle Logger" override fun catching(level: Level, t: Throwable) = - logger.warn("${PREFIX}Catching {}: {}", t.javaClass.getName(), t.message, t) + logger.info("${PREFIX}Catching {}: {}", t.javaClass.getName(), t.message, t) override fun log(level: Level, message: String, vararg params: Any) { val message = PREFIX + message diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt index 8bbaa199..a0c692a4 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MappingIoRemapperAdapter.kt @@ -29,9 +29,9 @@ class MappingIoRemapperAdapter( } } - override fun mapMethodName(owner: String?, name: String, desc: String?) = methods[name] ?: name + override fun mapMethodName(owner: String?, name: String?, desc: String?) = methods[name] ?: name - override fun mapFieldName(owner: String?, name: String, desc: String?) = fields[name] ?: name + override fun mapFieldName(owner: String?, name: String?, desc: String?) = fields[name] ?: name override fun map(typeName: String) = mappings.mapClassName(typeName, sourceId, targetId) From 5d9b19b87b785186bd0e3736242336d6f1cc838a Mon Sep 17 00:00:00 2001 From: SettingDust Date: Fri, 25 Apr 2025 07:42:50 +0800 Subject: [PATCH 13/18] chore(mixin): add source for mixin config --- .../minecraftcodev/mixins/mixin/GradleMixinService.kt | 9 ++------- .../net/msrandom/minecraftcodev/mixins/task/Mixin.kt | 7 +++++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt index a6741396..b8809b4c 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt @@ -2,6 +2,7 @@ package net.msrandom.minecraftcodev.mixins.mixin import org.objectweb.asm.ClassReader import org.objectweb.asm.tree.ClassNode +import org.spongepowered.asm.launch.platform.container.ContainerHandleVirtual import org.spongepowered.asm.launch.platform.container.IContainerHandle import org.spongepowered.asm.logging.ILogger import org.spongepowered.asm.mixin.MixinEnvironment @@ -135,14 +136,8 @@ class GradleMixinService : MixinServiceAbstract() { override fun getPlatformAgents() = listOf("org.spongepowered.asm.launch.platform.MixinPlatformAgentDefault") override fun getPrimaryContainer() = - object : IContainerHandle { - override fun getId() = "codev" - + object : ContainerHandleVirtual("codev") { override fun getDescription() = "Minecraft Codev Dummy Mixin Container" - - override fun getAttribute(name: String?) = null - - override fun getNestedContainers() = emptyList() } override fun getResourceAsStream(name: String) = diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt index ade1f6c2..825f17f5 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt @@ -28,6 +28,7 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction +import org.spongepowered.asm.launch.platform.container.ContainerHandleURI import org.spongepowered.asm.mixin.MixinEnvironment import org.spongepowered.asm.mixin.MixinEnvironment.Side import org.spongepowered.asm.mixin.Mixins @@ -41,7 +42,6 @@ import kotlin.io.path.deleteIfExists import kotlin.io.path.extension import kotlin.io.path.fileVisitor import kotlin.io.path.name -import kotlin.io.path.outputStream import kotlin.io.path.readBytes import kotlin.io.path.visitFileTree import kotlin.io.path.writeBytes @@ -152,7 +152,10 @@ abstract class Mixin : DefaultTask() { return@fs } - Mixins.addConfigurations(*handler.list(root).toTypedArray()) + Mixins.addConfigurations( + handler.list(root).toTypedArray(), + ContainerHandleURI(mixinFile.toPath().toUri()) + ) } } From fa52067b06b62c373f42a528524f943ed1d57651 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Fri, 25 Apr 2025 08:11:34 +0800 Subject: [PATCH 14/18] fix(mixin): clear the registered config missing before --- .../minecraftcodev/mixins/mixin/GradleMixinLogger.kt | 10 +--------- .../minecraftcodev/mixins/mixin/GradleMixinService.kt | 7 +++++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt index de647267..29227b66 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt @@ -29,15 +29,7 @@ class GradleMixinLogger(val name: String) : LoggerAdapterAbstract(name) { } override fun log(level: Level, message: String, t: Throwable) { - val message = PREFIX + message - when (level) { - Level.TRACE -> logger.trace(message, t) - Level.DEBUG -> logger.debug(message, t) - Level.INFO -> logger.info(message, t) - Level.WARN -> logger.warn(message, t) - Level.ERROR -> logger.error(message, t) - Level.FATAL -> logger.error("[FATAL] $message", t) - } + log(level, message, t as Any) } override fun throwing(t: T): T { diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt index b8809b4c..d7dc5fa8 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt @@ -9,6 +9,7 @@ import org.spongepowered.asm.mixin.MixinEnvironment import org.spongepowered.asm.mixin.MixinEnvironment.Phase import org.spongepowered.asm.mixin.MixinEnvironment.Side import org.spongepowered.asm.mixin.Mixins +import org.spongepowered.asm.mixin.transformer.Config import org.spongepowered.asm.mixin.transformer.IMixinTransformer import org.spongepowered.asm.mixin.transformer.IMixinTransformerFactory import org.spongepowered.asm.mixin.transformer.ext.Extensions @@ -61,8 +62,8 @@ class GradleMixinService : MixinServiceAbstract() { @Suppress("DEPRECATION") MixinEnvironment.getCurrentEnvironment().mixinConfigs.clear() - Mixins.getConfigs().clear() + (allConfigsField[null] as MutableMap).clear() recorderExtension.appliedMixins = null @@ -141,7 +142,8 @@ class GradleMixinService : MixinServiceAbstract() { } override fun getResourceAsStream(name: String) = - classpath.getResourceAsStream(name) ?: Path(name).takeIf(Path::exists)?.inputStream() + if (this@GradleMixinService::classpath.isInitialized) classpath.getResourceAsStream(name) + else Path(name).takeIf(Path::exists)?.inputStream() @Deprecated("Deprecated in Java") override fun wire( @@ -161,5 +163,6 @@ class GradleMixinService : MixinServiceAbstract() { private val registeredConfigsField = Mixins::class.java.getDeclaredField("registeredConfigs").apply { isAccessible = true } private val sideField = MixinEnvironment::class.java.getDeclaredField("side").apply { isAccessible = true } + private val allConfigsField = Config::class.java.getDeclaredField("allConfigs").apply { isAccessible = true } } } From 568ddd763666587134b9151c3b8f2ea07054ea1d Mon Sep 17 00:00:00 2001 From: SettingDust Date: Fri, 25 Apr 2025 09:28:28 +0800 Subject: [PATCH 15/18] fix(mixin): clear the processor mixins missing before --- .../mixins/mixin/GradleMixinLogger.kt | 4 +++ .../mixins/mixin/GradleMixinService.kt | 13 +-------- .../mixins/mixin/MixinCleaner.kt | 27 +++++++++++++++++++ 3 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt index 29227b66..d25fd5fe 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinLogger.kt @@ -17,6 +17,10 @@ class GradleMixinLogger(val name: String) : LoggerAdapterAbstract(name) { logger.info("${PREFIX}Catching {}: {}", t.javaClass.getName(), t.message, t) override fun log(level: Level, message: String, vararg params: Any) { + val level = when (message) { + "Mixin environment was unable to detect the current side, sided mixins will not be applied" -> Level.INFO + else -> level + } val message = PREFIX + message when (level) { Level.TRACE -> logger.trace(message, *params) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt index d7dc5fa8..f4864d5b 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt @@ -3,13 +3,10 @@ package net.msrandom.minecraftcodev.mixins.mixin import org.objectweb.asm.ClassReader import org.objectweb.asm.tree.ClassNode import org.spongepowered.asm.launch.platform.container.ContainerHandleVirtual -import org.spongepowered.asm.launch.platform.container.IContainerHandle import org.spongepowered.asm.logging.ILogger import org.spongepowered.asm.mixin.MixinEnvironment import org.spongepowered.asm.mixin.MixinEnvironment.Phase import org.spongepowered.asm.mixin.MixinEnvironment.Side -import org.spongepowered.asm.mixin.Mixins -import org.spongepowered.asm.mixin.transformer.Config import org.spongepowered.asm.mixin.transformer.IMixinTransformer import org.spongepowered.asm.mixin.transformer.IMixinTransformerFactory import org.spongepowered.asm.mixin.transformer.ext.Extensions @@ -55,15 +52,10 @@ class GradleMixinService : MixinServiceAbstract() { ) = synchronized(this) { this.classpath = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), javaClass.classLoader) - (registeredConfigsField[null] as MutableCollection<*>).clear() - sideField[MixinEnvironment.getCurrentEnvironment()] = Side.UNKNOWN MixinEnvironment.getCurrentEnvironment().side = side - @Suppress("DEPRECATION") - MixinEnvironment.getCurrentEnvironment().mixinConfigs.clear() - Mixins.getConfigs().clear() - (allConfigsField[null] as MutableMap).clear() + MixinCleaner.run(transformer) recorderExtension.appliedMixins = null @@ -160,9 +152,6 @@ class GradleMixinService : MixinServiceAbstract() { } companion object { - private val registeredConfigsField = - Mixins::class.java.getDeclaredField("registeredConfigs").apply { isAccessible = true } private val sideField = MixinEnvironment::class.java.getDeclaredField("side").apply { isAccessible = true } - private val allConfigsField = Config::class.java.getDeclaredField("allConfigs").apply { isAccessible = true } } } diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt new file mode 100644 index 00000000..fc1a2b43 --- /dev/null +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt @@ -0,0 +1,27 @@ +package net.msrandom.minecraftcodev.mixins.mixin + +import org.spongepowered.asm.mixin.MixinEnvironment +import org.spongepowered.asm.mixin.Mixins +import org.spongepowered.asm.mixin.extensibility.IMixinConfig +import org.spongepowered.asm.mixin.transformer.Config +import org.spongepowered.asm.mixin.transformer.IMixinTransformer + +object MixinCleaner { + private val registeredConfigsField = Mixins::class.java.getDeclaredField("registeredConfigs").apply { isAccessible = true } + private val allConfigsField = Config::class.java.getDeclaredField("allConfigs").apply { isAccessible = true } + + private val mixinTransformerClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinTransformer") + private val processorField = mixinTransformerClass.getDeclaredField("processor").apply { isAccessible = true } + + private val mixinProcessorClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinProcessor") + private val configsField = mixinProcessorClass.getDeclaredField("configs").apply { isAccessible = true } + + @Suppress("DEPRECATION") + fun run(transformer: IMixinTransformer) { + MixinEnvironment.getCurrentEnvironment().mixinConfigs.clear() + Mixins.getConfigs().clear() + (allConfigsField[null] as MutableMap).clear() + (registeredConfigsField[null] as MutableCollection<*>).clear() + (configsField[processorField[transformer]] as MutableList).clear() + } +} \ No newline at end of file From 30ec964d2746fe3eb2b15b4ccba28c132a32186d Mon Sep 17 00:00:00 2001 From: SettingDust Date: Fri, 25 Apr 2025 12:23:49 +0800 Subject: [PATCH 16/18] fix(mixin): clear the processor environment --- .../net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt index fc1a2b43..8bff34b3 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt @@ -15,6 +15,7 @@ object MixinCleaner { private val mixinProcessorClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinProcessor") private val configsField = mixinProcessorClass.getDeclaredField("configs").apply { isAccessible = true } + private val currentEnvironmentField = mixinProcessorClass.getDeclaredField("currentEnvironment").apply { isAccessible = true } @Suppress("DEPRECATION") fun run(transformer: IMixinTransformer) { @@ -22,6 +23,8 @@ object MixinCleaner { Mixins.getConfigs().clear() (allConfigsField[null] as MutableMap).clear() (registeredConfigsField[null] as MutableCollection<*>).clear() - (configsField[processorField[transformer]] as MutableList).clear() + val processor = processorField[transformer] + (configsField[processor] as MutableList).clear() + currentEnvironmentField.set(processor, null) } } \ No newline at end of file From 052de6d4cccf50b7abea6ca1bdc7c7e36fce8974 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Mon, 23 Mar 2026 21:28:46 +0800 Subject: [PATCH 17/18] feat(mixin): isolated mixin executor --- minecraft-codev-mixins/build.gradle.kts | 1 + minecraft-codev-mixins/gradle.properties | 2 +- .../mixins/MinecraftCodevMixinsPlugin.kt | 9 +- .../mixins/mixin/GradleMixinService.kt | 11 +- .../mixins/mixin/IsolatedMixinExecutor.kt | 170 ++++++++++++++++ .../mixins/mixin/IsolatingMixinClassLoader.kt | 73 +++++++ .../mixins/mixin/MixinCleaner.kt | 30 --- .../minecraftcodev/mixins/task/Mixin.kt | 191 ++++++++---------- 8 files changed, 334 insertions(+), 153 deletions(-) create mode 100644 minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatedMixinExecutor.kt create mode 100644 minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatingMixinClassLoader.kt delete mode 100644 minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt diff --git a/minecraft-codev-mixins/build.gradle.kts b/minecraft-codev-mixins/build.gradle.kts index 5745a0e3..49dc46cb 100644 --- a/minecraft-codev-mixins/build.gradle.kts +++ b/minecraft-codev-mixins/build.gradle.kts @@ -14,6 +14,7 @@ gradlePlugin { dependencies { api(group = "net.fabricmc", name = "sponge-mixin", version = "0.15.5+mixin.0.8.7") + api(group = "io.github.llamalad7", name = "mixinextras-common", version = "0.5.3") api(group = "net.fabricmc", name = "mapping-io", version = "0.7.1") implementation(projects.minecraftCodevCore) diff --git a/minecraft-codev-mixins/gradle.properties b/minecraft-codev-mixins/gradle.properties index af0eb1ce..3f83643e 100644 --- a/minecraft-codev-mixins/gradle.properties +++ b/minecraft-codev-mixins/gradle.properties @@ -1 +1 @@ -version=0.6.0 +version=0.6.1 diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/MinecraftCodevMixinsPlugin.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/MinecraftCodevMixinsPlugin.kt index f131b760..69bca970 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/MinecraftCodevMixinsPlugin.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/MinecraftCodevMixinsPlugin.kt @@ -3,13 +3,9 @@ package net.msrandom.minecraftcodev.mixins import net.msrandom.minecraftcodev.core.utils.applyPlugin import net.msrandom.minecraftcodev.core.utils.createSourceSetConfigurations import net.msrandom.minecraftcodev.core.utils.disambiguateName -import net.msrandom.minecraftcodev.mixins.mixin.GradleMixinService import org.gradle.api.Plugin import org.gradle.api.plugins.PluginAware import org.gradle.api.tasks.SourceSet -import org.spongepowered.asm.launch.MixinBootstrap -import org.spongepowered.asm.mixin.MixinEnvironment -import org.spongepowered.asm.service.MixinService val SourceSet.mixinsConfigurationName get() = disambiguateName(MinecraftCodevMixinsPlugin.MIXINS_CONFIGURATION) @@ -17,9 +13,8 @@ class MinecraftCodevMixinsPlugin : Plugin { override fun apply(target: T) = applyPlugin(target) { createSourceSetConfigurations(MIXINS_CONFIGURATION) - - MixinBootstrap.init() - (MixinService.getService() as GradleMixinService).phaseConsumer.accept(MixinEnvironment.Phase.DEFAULT) + // Mixin initialization is now done in IsolatedMixinExecutor + // within an isolated classloader for each mixin operation } companion object { diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt index f4864d5b..15bca37a 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/GradleMixinService.kt @@ -43,23 +43,20 @@ class GradleMixinService : MixinServiceAbstract() { } /** - * Thread safe accessor + * Set up classpath and side for mixin processing. + * Each isolated classloader instance runs independently, no synchronization needed. */ fun use( classpath: Iterable, side: Side, action: GradleMixinService.() -> R, - ) = synchronized(this) { + ): R { this.classpath = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), javaClass.classLoader) sideField[MixinEnvironment.getCurrentEnvironment()] = Side.UNKNOWN MixinEnvironment.getCurrentEnvironment().side = side - MixinCleaner.run(transformer) - - recorderExtension.appliedMixins = null - - this.action() + return this.action() } override fun getName() = "Gradle" diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatedMixinExecutor.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatedMixinExecutor.kt new file mode 100644 index 00000000..d38facb7 --- /dev/null +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatedMixinExecutor.kt @@ -0,0 +1,170 @@ +package net.msrandom.minecraftcodev.mixins.mixin + +import com.google.common.base.Joiner +import com.google.common.collect.HashMultimap +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToStream +import net.fabricmc.mappingio.format.tiny.Tiny2FileReader +import net.fabricmc.mappingio.tree.MemoryMappingTree +import net.msrandom.minecraftcodev.core.utils.SetMultimapSerializer +import net.msrandom.minecraftcodev.core.utils.zipFileSystem +import net.msrandom.minecraftcodev.mixins.MixinListingRule +import org.spongepowered.asm.launch.MixinBootstrap +import org.spongepowered.asm.launch.platform.container.ContainerHandleURI +import org.spongepowered.asm.mixin.MixinEnvironment +import org.spongepowered.asm.mixin.Mixins +import org.spongepowered.asm.service.MixinService +import java.io.File +import java.nio.file.FileVisitResult +import java.nio.file.StandardCopyOption +import java.util.ServiceLoader +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.copyTo +import kotlin.io.path.createParentDirectories +import kotlin.io.path.deleteIfExists +import kotlin.io.path.extension +import kotlin.io.path.fileVisitor +import kotlin.io.path.name +import kotlin.io.path.readBytes +import kotlin.io.path.visitFileTree +import kotlin.io.path.writeBytes + +/** + * Executor class that runs Mixin application in an isolated classloader context. + * All Mixin framework references resolve to classes in the isolated classloader. + * This class must be loaded by IsolatingMixinClassLoader. + */ +class IsolatedMixinExecutor { + + companion object { + private val JOINER = Joiner.on('.') + } + + /** + * Execute mixin application in an isolated context. + * All parameters are primitive/String types for cross-classloader safety. + */ + @OptIn(ExperimentalPathApi::class, ExperimentalSerializationApi::class) + fun execute( + inputFilePath: String, + outputFilePath: String, + mixinFilePaths: List, + classpathPaths: List, + side: String, + mappingsFilePath: String, + sourceNamespace: String, + targetNamespace: String, + appliedMixinsFilePath: String + ) { + // 1. Initialize Mixin system (fresh in this classloader!) + MixinBootstrap.init() + + val service = MixinService.getService() as GradleMixinService + service.phaseConsumer.accept(MixinEnvironment.Phase.DEFAULT) + + // 2. Convert string paths to File/Path objects + val inputFile = File(inputFilePath).toPath() + val outputFile = File(outputFilePath).toPath() + val mixinFiles = mixinFilePaths.map { File(it) } + val classpathFiles = classpathPaths.map { File(it) } + val mappingsFile = File(mappingsFilePath) + val appliedMixinsFile = File(appliedMixinsFilePath) + + val mixinSide = MixinEnvironment.Side.valueOf(side) + + // 3. Set up the classpath (all files including mixins and input) + val allClasspathFiles = classpathFiles + mixinFiles + listOf(inputFile.toFile()) + + service.use(allClasspathFiles, mixinSide) { + // 4. Set up mappings + val mappings = MemoryMappingTree() + Tiny2FileReader.read(mappingsFile.reader(), mappings) + + MixinEnvironment.getDefaultEnvironment().remappers.add( + MappingIoRemapperAdapter(mappings, sourceNamespace, targetNamespace) + ) + + MixinEnvironment.getDefaultEnvironment().setOption( + MixinEnvironment.Option.REFMAP_REMAP, + System.getProperty("mixin.env.remapRefMap", "true").toBoolean() + ) + + // 5. Set up recorder + val appliedMixins = HashMultimap.create() + recorderExtension.appliedMixins = appliedMixins + + // 6. Load mixin listing rules from this classloader + val mixinListingRules = ServiceLoader.load( + MixinListingRule::class.java, + this.javaClass.classLoader + ).toList() + + // 7. Add mixin configurations + for (mixinFile in mixinFiles + listOf(inputFile.toFile())) { + zipFileSystem(mixinFile.toPath()).use fs@{ fs -> + val root = fs.getPath("/") + + val handler = mixinListingRules.firstNotNullOfOrNull { rule -> + rule.load(root) + } + + if (handler == null) { + return@fs + } + + val configs = handler.list(root) + if (configs.isEmpty()) { + return@fs + } + + Mixins.addConfigurations( + configs.toTypedArray(), + ContainerHandleURI(mixinFile.toPath().toUri()) + ) + } + } + + // 8. Transform classes + outputFile.deleteIfExists() + + zipFileSystem(inputFile).use { inputFs -> + val root = inputFs.getPath("/") + + zipFileSystem(outputFile, true).use { outputFs -> + root.visitFileTree(fileVisitor { + onVisitFile { path, _ -> + val outputPath = outputFs.getPath( + path.getName(0).name, + *path.drop(1).map { it.name }.toList().toTypedArray() + ) + + outputPath.createParentDirectories() + + if (path.extension == "class") { + val pathName = JOINER.join(root.relativize(path)) + val name = pathName.substring(0, pathName.length - ".class".length) + outputPath.writeBytes(transformer.transformClassBytes(name, name, path.readBytes())) + } else { + path.copyTo( + outputPath, + StandardCopyOption.COPY_ATTRIBUTES, + StandardCopyOption.REPLACE_EXISTING + ) + } + FileVisitResult.CONTINUE + } + }) + } + } + + // 9. Write applied mixins + Json.encodeToStream( + SetMultimapSerializer(String.serializer(), String.serializer()), + appliedMixins, + appliedMixinsFile.outputStream() + ) + } + } +} diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatingMixinClassLoader.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatingMixinClassLoader.kt new file mode 100644 index 00000000..3718534d --- /dev/null +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatingMixinClassLoader.kt @@ -0,0 +1,73 @@ +package net.msrandom.minecraftcodev.mixins.mixin + +import java.net.URL +import java.net.URLClassLoader +import java.util.Collections +import java.util.Enumeration + +/** + * A child-first classloader that isolates Mixin framework classes. + * Each instance gets its own copy of static fields for isolated packages, + * enabling multiple independent Mixin operations. + */ +class IsolatingMixinClassLoader( + urls: Array, + parent: ClassLoader +) : URLClassLoader(urls, parent) { + + companion object { + private val ISOLATED_PREFIXES = listOf( + "org.spongepowered.asm.", + "com.llamalad7.mixinextras.", + "org.objectweb.asm.", + "net.fabricmc.mappingio.", + "net.msrandom.minecraftcodev.mixins.mixin.", + ) + } + + override fun loadClass(name: String, resolve: Boolean): Class<*> { + synchronized(getClassLoadingLock(name)) { + // Check if already loaded + var c = findLoadedClass(name) + if (c != null) { + if (resolve) resolveClass(c) + return c + } + + // For isolated packages, try child first (load from our URLs) + if (ISOLATED_PREFIXES.any { name.startsWith(it) }) { + try { + c = findClass(name) + if (resolve) resolveClass(c) + return c + } catch (_: ClassNotFoundException) { + // Fall through to parent + } + } + + // Default parent-first delegation + return super.loadClass(name, resolve) + } + } + + override fun getResource(name: String): URL? { + // For isolated packages, check child first + if (ISOLATED_PREFIXES.any { name.replace('/', '.').startsWith(it) }) { + findResource(name)?.let { return it } + } + return super.getResource(name) + } + + override fun getResources(name: String): Enumeration { + // For service loader files and isolated resources, include child resources first + if (name.startsWith("META-INF/services/org.spongepowered.asm.") || + name.startsWith("META-INF/services/net.msrandom.minecraftcodev.mixins.mixin.") + ) { + val childResources = findResources(name).toList() + if (childResources.isNotEmpty()) { + return Collections.enumeration(childResources) + } + } + return super.getResources(name) + } +} diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt deleted file mode 100644 index 8bff34b3..00000000 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/MixinCleaner.kt +++ /dev/null @@ -1,30 +0,0 @@ -package net.msrandom.minecraftcodev.mixins.mixin - -import org.spongepowered.asm.mixin.MixinEnvironment -import org.spongepowered.asm.mixin.Mixins -import org.spongepowered.asm.mixin.extensibility.IMixinConfig -import org.spongepowered.asm.mixin.transformer.Config -import org.spongepowered.asm.mixin.transformer.IMixinTransformer - -object MixinCleaner { - private val registeredConfigsField = Mixins::class.java.getDeclaredField("registeredConfigs").apply { isAccessible = true } - private val allConfigsField = Config::class.java.getDeclaredField("allConfigs").apply { isAccessible = true } - - private val mixinTransformerClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinTransformer") - private val processorField = mixinTransformerClass.getDeclaredField("processor").apply { isAccessible = true } - - private val mixinProcessorClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinProcessor") - private val configsField = mixinProcessorClass.getDeclaredField("configs").apply { isAccessible = true } - private val currentEnvironmentField = mixinProcessorClass.getDeclaredField("currentEnvironment").apply { isAccessible = true } - - @Suppress("DEPRECATION") - fun run(transformer: IMixinTransformer) { - MixinEnvironment.getCurrentEnvironment().mixinConfigs.clear() - Mixins.getConfigs().clear() - (allConfigsField[null] as MutableMap).clear() - (registeredConfigsField[null] as MutableCollection<*>).clear() - val processor = processorField[transformer] - (configsField[processor] as MutableList).clear() - currentEnvironmentField.set(processor, null) - } -} \ No newline at end of file diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt index 825f17f5..be406bd1 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt @@ -1,19 +1,10 @@ package net.msrandom.minecraftcodev.mixins.task -import com.google.common.base.Joiner -import com.google.common.collect.HashMultimap -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.encodeToStream -import net.fabricmc.mappingio.format.tiny.Tiny2FileReader import net.fabricmc.mappingio.tree.MemoryMappingTree -import net.msrandom.minecraftcodev.core.utils.SetMultimapSerializer import net.msrandom.minecraftcodev.core.utils.getAsPath -import net.msrandom.minecraftcodev.core.utils.zipFileSystem import net.msrandom.minecraftcodev.mixins.mixin.GradleMixinService +import net.msrandom.minecraftcodev.mixins.mixin.IsolatingMixinClassLoader import net.msrandom.minecraftcodev.mixins.mixin.MappingIoRemapperAdapter -import net.msrandom.minecraftcodev.mixins.mixinListingRules import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty @@ -28,29 +19,15 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction -import org.spongepowered.asm.launch.platform.container.ContainerHandleURI +import org.objectweb.asm.ClassReader +import org.objectweb.asm.tree.ClassNode +import org.spongepowered.asm.launch.MixinBootstrap import org.spongepowered.asm.mixin.MixinEnvironment import org.spongepowered.asm.mixin.MixinEnvironment.Side -import org.spongepowered.asm.mixin.Mixins -import org.spongepowered.asm.service.MixinService -import java.nio.file.FileVisitResult -import java.nio.file.StandardCopyOption -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.copyTo -import kotlin.io.path.createParentDirectories -import kotlin.io.path.deleteIfExists -import kotlin.io.path.extension -import kotlin.io.path.fileVisitor -import kotlin.io.path.name -import kotlin.io.path.readBytes -import kotlin.io.path.visitFileTree -import kotlin.io.path.writeBytes +import java.net.URL @CacheableTask abstract class Mixin : DefaultTask() { - companion object { - private val JOINER = Joiner.on('.') - } abstract val inputFile: RegularFileProperty @InputFile @@ -107,95 +84,93 @@ abstract class Mixin : DefaultTask() { side.convention(Side.UNKNOWN) } - @OptIn(ExperimentalPathApi::class, ExperimentalSerializationApi::class) @TaskAction fun mixin() { - val input = inputFile.getAsPath() - val output = outputFile.getAsPath() - - output.deleteIfExists() + // Target classpath URLs come first (so target's mixin deps are preferred) + val targetUrls = (classpath.files + mixinFiles.files + listOf(inputFile.asFile.get())) + .map { it.toURI().toURL() } - MixinEnvironment.getDefaultEnvironment().setOption( - MixinEnvironment.Option.REFMAP_REMAP, - System.getProperty("mixin.env.remapRefMap", "true").toBoolean() - ) + // Plugin JARs as fallback + val pluginUrls = collectIsolatedClasspathUrls() - (MixinService.getService() as GradleMixinService).use( - classpath + mixinFiles + project.files(input), - side.get() - ) { - val mappings = MemoryMappingTree() + // Target first, then plugin fallback + val allUrls = (targetUrls + pluginUrls).distinct() - Tiny2FileReader.read(this@Mixin.mappings.asFile.get().reader(), mappings) + val isolatedClassLoader = IsolatingMixinClassLoader( + allUrls.toTypedArray(), + javaClass.classLoader + ) - MixinEnvironment.getDefaultEnvironment().remappers.add( - MappingIoRemapperAdapter( - mappings, - sourceNamespace.get(), - targetNamespace.get() - ) + isolatedClassLoader.use { isolatedClassLoader -> + // Load IsolatedMixinExecutor in the isolated classloader + val executorClass = isolatedClassLoader.loadClass( + "net.msrandom.minecraftcodev.mixins.mixin.IsolatedMixinExecutor" + ) + val executor = executorClass.getDeclaredConstructor().newInstance() + + // Get the execute method + val executeMethod = executorClass.getDeclaredMethod( + "execute", + String::class.java, // inputFilePath + String::class.java, // outputFilePath + List::class.java, // mixinFilePaths + List::class.java, // classpathPaths + String::class.java, // side + String::class.java, // mappingsFilePath + String::class.java, // sourceNamespace + String::class.java, // targetNamespace + String::class.java // appliedMixinsFilePath ) - val appliedMixins = HashMultimap.create() - recorderExtension.appliedMixins = appliedMixins - - CLASSPATH@ for (mixinFile in mixinFiles + project.files(input)) { - zipFileSystem(mixinFile.toPath()).use fs@{ - val root = it.getPath("/") - - val handler = - mixinListingRules.firstNotNullOfOrNull { rule -> - rule.load(root) - } - - if (handler == null) { - return@fs - } - - Mixins.addConfigurations( - handler.list(root).toTypedArray(), - ContainerHandleURI(mixinFile.toPath().toUri()) - ) - } - } - - zipFileSystem(input).use { inputFs -> - val root = inputFs.getPath("/") - - zipFileSystem(output, true).use { outputFs -> - root.visitFileTree(fileVisitor { - onVisitFile { path, attr -> - val outputPath = outputFs.getPath( - path.getName(0).name, - *path.drop(1).map { it.name }.toList().toTypedArray() - ) - - outputPath.createParentDirectories() - - if (path.extension == "class") { - val pathName = JOINER.join(root.relativize(path)) - - val name = pathName.substring(0, pathName.length - ".class".length) - - outputPath.writeBytes(transformer.transformClassBytes(name, name, path.readBytes())) - } else { - path.copyTo( - outputPath, - StandardCopyOption.COPY_ATTRIBUTES, - StandardCopyOption.REPLACE_EXISTING - ) - } - return@onVisitFile FileVisitResult.CONTINUE - } - }) - } - } - - Json.encodeToStream( - SetMultimapSerializer(String.serializer(), String.serializer()), - appliedMixins, - this@Mixin.appliedMixins.asFile.get().outputStream() + // Prepare parameters (all String/List for cross-classloader safety) + val inputFilePath = inputFile.getAsPath().toAbsolutePath().toString() + val outputFilePath = outputFile.getAsPath().toAbsolutePath().toString() + val mixinFilePaths = mixinFiles.files.map { it.absolutePath } + val classpathPaths = classpath.files.map { it.absolutePath } + val sideStr = side.get().name + val mappingsFilePath = mappings.asFile.get().absolutePath + val sourceNs = sourceNamespace.get() + val targetNs = targetNamespace.get() + val appliedMixinsFilePath = appliedMixins.asFile.get().absolutePath + + // Invoke execute method + executeMethod.invoke( + executor, + inputFilePath, + outputFilePath, + mixinFilePaths, + classpathPaths, + sideStr, + mappingsFilePath, + sourceNs, + targetNs, + appliedMixinsFilePath ) } } + + /** + * Collect URLs of JARs containing classes that need to be isolated. + * These include Mixin framework, ASM, mapping-io, and our mixin service classes. + */ + private fun collectIsolatedClasspathUrls(): List { + val keyClasses = listOf( + // Our plugin classes + GradleMixinService::class.java, + MappingIoRemapperAdapter::class.java, + // Mixin library + MixinEnvironment::class.java, + MixinBootstrap::class.java, + // Mixin Extras + Class.forName("com.llamalad7.mixinextras.MixinExtrasBootstrap"), + // ASM + ClassReader::class.java, + ClassNode::class.java, + // mapping-io + MemoryMappingTree::class.java, + ) + return keyClasses.mapNotNull { + it.protectionDomain?.codeSource?.location + }.distinct() + } } From a45f6f105620d6c0fbef0805f62b21d5d2cffe07 Mon Sep 17 00:00:00 2001 From: SettingDust Date: Sun, 29 Mar 2026 14:08:57 +0800 Subject: [PATCH 18/18] feat(mixin): handle the fabric compat version --- .../gradle.properties | 2 +- minecraft-codev-core/gradle.properties | 2 +- minecraft-codev-decompiler/gradle.properties | 2 +- minecraft-codev-fabric/gradle.properties | 2 +- minecraft-codev-forge/gradle.properties | 2 +- .../forge/mixin/ForgeMixinListingRule.kt | 2 +- minecraft-codev-includes/gradle.properties | 2 +- minecraft-codev-mixins/build.gradle.kts | 2 +- minecraft-codev-mixins/gradle.properties | 2 +- .../mixins/mixin/IsolatedMixinExecutor.kt | 189 ++++++++++++++++-- .../minecraftcodev/mixins/task/Mixin.kt | 3 + minecraft-codev-runs/gradle.properties | 2 +- 12 files changed, 190 insertions(+), 22 deletions(-) diff --git a/minecraft-codev-access-widener/gradle.properties b/minecraft-codev-access-widener/gradle.properties index 240ac2f9..be60622e 100644 --- a/minecraft-codev-access-widener/gradle.properties +++ b/minecraft-codev-access-widener/gradle.properties @@ -1 +1 @@ -version=0.6.3 +version=0.6.4 diff --git a/minecraft-codev-core/gradle.properties b/minecraft-codev-core/gradle.properties index af4621a4..db1db5d6 100644 --- a/minecraft-codev-core/gradle.properties +++ b/minecraft-codev-core/gradle.properties @@ -1 +1 @@ -version=0.6.9 +version=0.6.10 diff --git a/minecraft-codev-decompiler/gradle.properties b/minecraft-codev-decompiler/gradle.properties index af0eb1ce..3f83643e 100644 --- a/minecraft-codev-decompiler/gradle.properties +++ b/minecraft-codev-decompiler/gradle.properties @@ -1 +1 @@ -version=0.6.0 +version=0.6.1 diff --git a/minecraft-codev-fabric/gradle.properties b/minecraft-codev-fabric/gradle.properties index f13969c2..50791624 100644 --- a/minecraft-codev-fabric/gradle.properties +++ b/minecraft-codev-fabric/gradle.properties @@ -1 +1 @@ -version=0.7.0 +version=0.7.1 diff --git a/minecraft-codev-forge/gradle.properties b/minecraft-codev-forge/gradle.properties index 19d3efe4..58b1003e 100644 --- a/minecraft-codev-forge/gradle.properties +++ b/minecraft-codev-forge/gradle.properties @@ -1 +1 @@ -version=0.8.3 +version=0.8.4 diff --git a/minecraft-codev-forge/src/main/kotlin/net/msrandom/minecraftcodev/forge/mixin/ForgeMixinListingRule.kt b/minecraft-codev-forge/src/main/kotlin/net/msrandom/minecraftcodev/forge/mixin/ForgeMixinListingRule.kt index 3687d573..2b16554f 100644 --- a/minecraft-codev-forge/src/main/kotlin/net/msrandom/minecraftcodev/forge/mixin/ForgeMixinListingRule.kt +++ b/minecraft-codev-forge/src/main/kotlin/net/msrandom/minecraftcodev/forge/mixin/ForgeMixinListingRule.kt @@ -44,7 +44,7 @@ class ForgeMixinListingRule : MixinListingRule { val mixinConfigsString = manifest.mainAttributes.getValue(MANIFEST_MIXINS_CONFIG) ?: return loadFromToml(directory) - val mixinConfigs = mixinConfigsString.split(",").map(String::trim) + val mixinConfigs = mixinConfigsString.split(",").map(String::trim).filter(String::isNotBlank) return ForgeMixinConfigHandler(mixinConfigs, true) } diff --git a/minecraft-codev-includes/gradle.properties b/minecraft-codev-includes/gradle.properties index 7743eff2..86152c29 100644 --- a/minecraft-codev-includes/gradle.properties +++ b/minecraft-codev-includes/gradle.properties @@ -1 +1 @@ -version=0.6.5 +version=0.6.6 diff --git a/minecraft-codev-mixins/build.gradle.kts b/minecraft-codev-mixins/build.gradle.kts index 49dc46cb..f6f6d513 100644 --- a/minecraft-codev-mixins/build.gradle.kts +++ b/minecraft-codev-mixins/build.gradle.kts @@ -13,7 +13,7 @@ gradlePlugin { } dependencies { - api(group = "net.fabricmc", name = "sponge-mixin", version = "0.15.5+mixin.0.8.7") + api(group = "net.fabricmc", name = "sponge-mixin", version = "0.17.0+mixin.0.8.7") api(group = "io.github.llamalad7", name = "mixinextras-common", version = "0.5.3") api(group = "net.fabricmc", name = "mapping-io", version = "0.7.1") diff --git a/minecraft-codev-mixins/gradle.properties b/minecraft-codev-mixins/gradle.properties index 3f83643e..e6e06da0 100644 --- a/minecraft-codev-mixins/gradle.properties +++ b/minecraft-codev-mixins/gradle.properties @@ -1 +1 @@ -version=0.6.1 +version=0.6.2 diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatedMixinExecutor.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatedMixinExecutor.kt index d38facb7..6176f2a9 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatedMixinExecutor.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/mixin/IsolatedMixinExecutor.kt @@ -5,7 +5,11 @@ import com.google.common.collect.HashMultimap import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.builtins.serializer import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.encodeToStream +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive import net.fabricmc.mappingio.format.tiny.Tiny2FileReader import net.fabricmc.mappingio.tree.MemoryMappingTree import net.msrandom.minecraftcodev.core.utils.SetMultimapSerializer @@ -13,23 +17,19 @@ import net.msrandom.minecraftcodev.core.utils.zipFileSystem import net.msrandom.minecraftcodev.mixins.MixinListingRule import org.spongepowered.asm.launch.MixinBootstrap import org.spongepowered.asm.launch.platform.container.ContainerHandleURI +import org.spongepowered.asm.mixin.FabricUtil import org.spongepowered.asm.mixin.MixinEnvironment import org.spongepowered.asm.mixin.Mixins +import org.spongepowered.asm.mixin.extensibility.IMixinConfig +import org.spongepowered.asm.mixin.transformer.Config import org.spongepowered.asm.service.MixinService import java.io.File +import java.nio.file.FileSystem import java.nio.file.FileVisitResult +import java.nio.file.Path import java.nio.file.StandardCopyOption -import java.util.ServiceLoader -import kotlin.io.path.ExperimentalPathApi -import kotlin.io.path.copyTo -import kotlin.io.path.createParentDirectories -import kotlin.io.path.deleteIfExists -import kotlin.io.path.extension -import kotlin.io.path.fileVisitor -import kotlin.io.path.name -import kotlin.io.path.readBytes -import kotlin.io.path.visitFileTree -import kotlin.io.path.writeBytes +import java.util.* +import kotlin.io.path.* /** * Executor class that runs Mixin application in an isolated classloader context. @@ -40,6 +40,139 @@ class IsolatedMixinExecutor { companion object { private val JOINER = Joiner.on('.') + + /** + * Loader version to Mixin compatibility version mapping. + * Must be in DESCENDING order (latest first). + */ + private val VERSION_MAPPINGS = listOf( + LoaderMixinVersionEntry("0.18.4", FabricUtil.COMPATIBILITY_0_17_0), + LoaderMixinVersionEntry("0.17.3", FabricUtil.COMPATIBILITY_0_16_5), + LoaderMixinVersionEntry("0.16.0", FabricUtil.COMPATIBILITY_0_14_0), + LoaderMixinVersionEntry("0.12.0", FabricUtil.COMPATIBILITY_0_10_0), + ) + + /** + * Compute Mixin compatibility level from minimum Fabric Loader version. + */ + fun computeMixinCompat(minLoaderVersion: String?): Int { + if (minLoaderVersion == null) { + return FabricUtil.COMPATIBILITY_0_9_2 + } + + val cleanVersion = minLoaderVersion.substringBefore('+').substringBefore('-') + val versionParts = cleanVersion.split('.') + + if (versionParts.size < 2) { + return FabricUtil.COMPATIBILITY_0_9_2 + } + + val major = versionParts.getOrNull(0)?.toIntOrNull() ?: 0 + val minor = versionParts.getOrNull(1)?.toIntOrNull() ?: 0 + val patch = versionParts.getOrNull(2)?.toIntOrNull() ?: 0 + + for (entry in VERSION_MAPPINGS) { + if (isVersionGreaterOrEqual(major, minor, patch, entry.loaderVersion)) { + return entry.mixinVersion + } + } + + return FabricUtil.COMPATIBILITY_0_9_2 + } + + private fun isVersionGreaterOrEqual( + major: Int, minor: Int, patch: Int, + reference: String + ): Boolean { + val refParts = reference.split('.') + val refMajor = refParts.getOrNull(0)?.toIntOrNull() ?: 0 + val refMinor = refParts.getOrNull(1)?.toIntOrNull() ?: 0 + val refPatch = refParts.getOrNull(2)?.toIntOrNull() ?: 0 + + return when { + major > refMajor -> true + major < refMajor -> false + minor > refMinor -> true + minor < refMinor -> false + else -> patch >= refPatch + } + } + } + + private data class LoaderMixinVersionEntry( + val loaderVersion: String, + val mixinVersion: Int + ) + + /** + * Extract the minimum Fabric Loader version from fabric.mod.json. + */ + private fun extractMinLoaderVersion(fileSystem: FileSystem): String? { + val modJson = fileSystem.getPath("/fabric.mod.json") + if (!modJson.exists()) { + return null + } + + return try { + modJson.inputStream().use { + val json = Json.decodeFromStream(it) + parseMinLoaderVersion(json) + } + } catch (e: Exception) { + null + } + } + + /** + * Parse minimum Fabric Loader version from fabric.mod.json content. + */ + private fun parseMinLoaderVersion(json: JsonObject): String? { + val depends = json["depends"]?.jsonObject ?: return null + + // Try both "fabricloader" and "fabric-loader" keys + val loaderDep = depends["fabricloader"] ?: depends["fabric-loader"] ?: return null + + val versionSpec = when { + loaderDep is JsonObject -> { + loaderDep["version"]?.jsonPrimitive?.content + } + loaderDep.jsonPrimitive.isString -> { + loaderDep.jsonPrimitive.content + } + else -> null + } ?: return null + + return extractMinVersion(versionSpec) + } + + /** + * Extract minimum version from a version range string. + */ + private fun extractMinVersion(versionSpec: String): String? { + val trimmed = versionSpec.trim() + + // Handle ">=x.y.z" or ">x.y.z" + if (trimmed.startsWith(">=")) { + return trimmed.substring(2).trim().split(" ").firstOrNull() + } + if (trimmed.startsWith(">")) { + return trimmed.substring(1).trim().split(" ").firstOrNull() + } + + // Handle range format "[x.y.z, ...)" + if (trimmed.startsWith("[")) { + val end = trimmed.indexOfAny(charArrayOf(',', ']')) + if (end > 1) { + return trimmed.substring(1, end).trim() + } + } + + // Handle plain version string + if (trimmed.matches(Regex("^[\\d.]+.*"))) { + return trimmed.split(" ").firstOrNull() + } + + return null } /** @@ -59,6 +192,16 @@ class IsolatedMixinExecutor { appliedMixinsFilePath: String ) { // 1. Initialize Mixin system (fresh in this classloader!) + val mixinServiceClass = MixinService::class.java + val getInstanceMethod = mixinServiceClass.getDeclaredMethod("getInstance") + getInstanceMethod.setAccessible(true) + val mixinService = getInstanceMethod.invoke(null) as MixinService + val propertyServiceField = mixinServiceClass.getDeclaredField("propertyService") + propertyServiceField.setAccessible(true) + propertyServiceField.set(mixinService, GradleGlobalPropertyService()) + + System.setProperty("mixin.service", GradleMixinService::class.java.name) + MixinBootstrap.init() val service = MixinService.getService() as GradleMixinService @@ -101,7 +244,9 @@ class IsolatedMixinExecutor { this.javaClass.classLoader ).toList() - // 7. Add mixin configurations + // 7. Add mixin configurations and set compatibility levels + val configToModMap = mutableMapOf() // config name -> min loader version + for (mixinFile in mixinFiles + listOf(inputFile.toFile())) { zipFileSystem(mixinFile.toPath()).use fs@{ fs -> val root = fs.getPath("/") @@ -119,6 +264,14 @@ class IsolatedMixinExecutor { return@fs } + // Get minimum loader version from fabric.mod.json + val minLoaderVersion = extractMinLoaderVersion(fs) + + // Store mapping for later decoration + configs.forEach { config -> + configToModMap[config] = minLoaderVersion + } + Mixins.addConfigurations( configs.toTypedArray(), ContainerHandleURI(mixinFile.toPath().toUri()) @@ -126,6 +279,18 @@ class IsolatedMixinExecutor { } } + // 7.1 Apply compatibility decorations to configs + for (rawConfig in Mixins.getConfigs()) { + val configName = rawConfig.name + val minLoaderVersion = configToModMap[configName] + val compatLevel = computeMixinCompat(minLoaderVersion) + + val config = rawConfig.config + if (config is IMixinConfig) { + config.decorate(FabricUtil.KEY_COMPATIBILITY, compatLevel) + } + } + // 8. Transform classes outputFile.deleteIfExists() diff --git a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt index be406bd1..21b96073 100644 --- a/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt +++ b/minecraft-codev-mixins/src/main/kotlin/net/msrandom/minecraftcodev/mixins/task/Mixin.kt @@ -1,6 +1,7 @@ package net.msrandom.minecraftcodev.mixins.task import net.fabricmc.mappingio.tree.MemoryMappingTree +import net.msrandom.minecraftcodev.core.utils.SetMultimapSerializer import net.msrandom.minecraftcodev.core.utils.getAsPath import net.msrandom.minecraftcodev.mixins.mixin.GradleMixinService import net.msrandom.minecraftcodev.mixins.mixin.IsolatingMixinClassLoader @@ -158,6 +159,8 @@ abstract class Mixin : DefaultTask() { // Our plugin classes GradleMixinService::class.java, MappingIoRemapperAdapter::class.java, + // minecraft-codev-core classes + SetMultimapSerializer::class.java, // Mixin library MixinEnvironment::class.java, MixinBootstrap::class.java, diff --git a/minecraft-codev-runs/gradle.properties b/minecraft-codev-runs/gradle.properties index 6e148ff4..af4621a4 100644 --- a/minecraft-codev-runs/gradle.properties +++ b/minecraft-codev-runs/gradle.properties @@ -1 +1 @@ -version=0.6.8 +version=0.6.9