diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d90d613e..61f7cb9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,6 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v3 with: - dependency-graph: generate-and-submit gradle-home-cache-cleanup: true gradle-home-cache-excludes: | gradle.properties diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9a8c42a..f902b5ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,21 +50,23 @@ jobs: matrix: include: - name: Maven - environment: maven task: publish + url: https://maven.su5ed.dev/releases/dev/su5ed/sinytra/Connector - name: GitHub - environment: github task: publishGithub + url: https://github.com/Sinytra/Connector/releases - name: CurseForge - environment: curseforge task: publishCurseforge + url: https://www.curseforge.com/minecraft/mc-mods/sinytra-connector - name: Modrinth - environment: modrinth task: publishModrinth + url: https://modrinth.com/mod/connector name: Publish ${{ matrix.name }} needs: build runs-on: ubuntu-22.04 - environment: ${{ matrix.environment }} + environment: + name: ${{ matrix.name }} + url: ${{ matrix.url }} steps: - uses: actions/checkout@v4 with: diff --git a/build.gradle.kts b/build.gradle.kts index fd8645bd..8fce86fa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -315,7 +315,7 @@ dependencies { shade(group = "org.ow2.sat4j", name = "org.ow2.sat4j.pb", version = "2.3.6") shade(group = "net.minecraftforge", name = "srgutils", version = "0.5.4") shade(group = "net.fabricmc", name = "access-widener", version = versionAccessWidener) - shade(group = "dev.su5ed.sinytra", name = "ForgeAutoRenamingTool", version = versionForgeAutoRenamingTool) + shade(group = "org.sinytra", name = "ForgeAutoRenamingTool", version = versionForgeAutoRenamingTool) shade(group = "org.sinytra.adapter", name = "definition", version = versionAdapterDefinition) { isTransitive = false } shade(group = "io.github.steelwoolmc", name = "mixin-transmogrifier", version = versionMixinTransmog) adapterData(group = "org.sinytra.adapter", name = "adapter", version = versionAdapter) diff --git a/gradle.properties b/gradle.properties index a171d256..edd5d3b1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,14 +4,14 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=true # Versions -versionConnector=1.0.0-beta.39 -versionAdapter=1.11.30-1.20.1-20240308.152751 -versionAdapterDefinition=1.11.30 +versionConnector=1.0.0-beta.40 +versionAdapter=1.11.32-1.20.1-20240314.234555 +versionAdapterDefinition=1.11.34 versionMc=1.20.1 versionForge=47.1.3 -versionForgeAutoRenamingTool=1.0.9 -versionFabricLoader=2.7.1+0.15.3+1.20.1 +versionForgeAutoRenamingTool=1.0.11 +versionFabricLoader=2.7.2+0.15.3+1.20.1 versionAccessWidener=2.1.0 versionFabricApi=0.92.0+1.11.2+1.20.1 versionMixin=0.12.5+mixin.0.8.5 diff --git a/src/main/java/dev/su5ed/sinytra/connector/locator/DependencyResolver.java b/src/main/java/dev/su5ed/sinytra/connector/locator/DependencyResolver.java index 219b9817..52ca5953 100644 --- a/src/main/java/dev/su5ed/sinytra/connector/locator/DependencyResolver.java +++ b/src/main/java/dev/su5ed/sinytra/connector/locator/DependencyResolver.java @@ -83,7 +83,7 @@ public static void removeAliasedModDependencyConstraints(LoaderModMetadata metad .map(dep -> { // Aliased mods typically don't follow the same version convention as the original, // therefore we must widen all dependency constraints to wildcards - if (aliases.values().contains(dep.getModId())) { + if (aliases.keys().contains(dep.getModId()) || aliases.values().contains(dep.getModId())) { return dep.getKind() == ModDependency.Kind.BREAKS ? null : uncheck(() -> new ModDependencyImpl(dep.getKind(), dep.getModId(), List.of("*"))); } return dep; diff --git a/src/main/java/dev/su5ed/sinytra/connector/transformer/MixinPatchTransformer.java b/src/main/java/dev/su5ed/sinytra/connector/transformer/MixinPatchTransformer.java index 8f1b010a..b49dd9f1 100644 --- a/src/main/java/dev/su5ed/sinytra/connector/transformer/MixinPatchTransformer.java +++ b/src/main/java/dev/su5ed/sinytra/connector/transformer/MixinPatchTransformer.java @@ -21,6 +21,7 @@ import org.sinytra.adapter.patch.api.MixinClassGenerator; import org.sinytra.adapter.patch.api.MixinConstants; import org.sinytra.adapter.patch.api.Patch; +import org.sinytra.adapter.patch.api.PatchContext; import org.sinytra.adapter.patch.api.PatchEnvironment; import org.sinytra.adapter.patch.fixes.FieldTypePatchTransformer; import org.sinytra.adapter.patch.fixes.FieldTypeUsageTransformer; @@ -224,6 +225,11 @@ public ClassEntry process(ClassEntry entry) { patchResult = patchResult.or(patch.apply(node, this.environment)); } } + else { + for (ClassTransform transform : CLASS_TRANSFORMS) { + patchResult = patchResult.or(transform.apply(node, null, PatchContext.create(node, List.of(), this.environment))); + } + } // TODO if a mixin method is extracted, roll back the status from compute frames to apply, // Alternatively, change the order of patches so that extractmixin comes first diff --git a/src/main/java/dev/su5ed/sinytra/connector/transformer/MixinPatches.java b/src/main/java/dev/su5ed/sinytra/connector/transformer/MixinPatches.java index 55fad9b1..42c9a819 100644 --- a/src/main/java/dev/su5ed/sinytra/connector/transformer/MixinPatches.java +++ b/src/main/java/dev/su5ed/sinytra/connector/transformer/MixinPatches.java @@ -54,6 +54,18 @@ public static List getPatches() { .modifyMethodAccess(new ModifyMethodAccess.AccessChange(false, Opcodes.ACC_STATIC)) .extractMixin("net/minecraftforge/common/extensions/IForgeBlockState") .build(), + Patch.builder() + .targetClass("net/minecraft/client/multiplayer/MultiPlayerGameMode") + .targetMethod("m_105267_") + .targetInjectionPoint("Lnet/minecraft/world/level/block/Block;m_5707_(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/entity/player/Player;)V") + .modifyInjectionPoint("Lnet/minecraft/world/level/block/state/BlockState;onDestroyedByPlayer(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/player/Player;ZLnet/minecraft/world/level/material/FluidState;)Z") + .build(), + Patch.builder() + .targetClass("net/minecraft/world/level/block/FarmBlock") + .targetMethod("m_53258_(Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;)Z") + .targetInjectionPoint("Lnet/minecraft/world/level/material/FluidState;m_205070_(Lnet/minecraft/tags/TagKey;)Z") + .modifyInjectionPoint("Lnet/minecraft/world/level/block/state/BlockState;canBeHydrated(Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/material/FluidState;Lnet/minecraft/core/BlockPos;)Z") + .build(), Patch.builder() .targetClass("net/minecraft/world/entity/LivingEntity") .targetMethod("m_6075_") diff --git a/src/main/java/dev/su5ed/sinytra/connector/transformer/OptimizedRenamingTransformer.java b/src/main/java/dev/su5ed/sinytra/connector/transformer/OptimizedRenamingTransformer.java index 0996b3f4..e01e3eac 100644 --- a/src/main/java/dev/su5ed/sinytra/connector/transformer/OptimizedRenamingTransformer.java +++ b/src/main/java/dev/su5ed/sinytra/connector/transformer/OptimizedRenamingTransformer.java @@ -24,11 +24,7 @@ import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.InvokeDynamicInsnNode; import org.objectweb.asm.tree.LdcInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.analysis.SourceInterpreter; -import org.objectweb.asm.tree.analysis.SourceValue; -import org.sinytra.adapter.patch.analysis.MethodCallAnalyzer; import org.sinytra.adapter.patch.selector.AnnotationHandle; import org.sinytra.adapter.patch.util.MethodQualifier; @@ -47,19 +43,14 @@ public final class OptimizedRenamingTransformer extends RenamingTransformer { private static final String CLASS_DESC_PATTERN = "^L[a-zA-Z0-9/$_]+;$"; private static final String FQN_CLASS_NAME_PATTERN = "^([a-zA-Z0-9$_]+\\.)*[a-zA-Z0-9$_]+$"; - private final SourceInterpreter reflectionRemapper; - public static Transformer create(ClassProvider classProvider, Consumer log, IMappingFile mappingFile, IntermediateMapping flatMappings) { IntermediaryClassProvider reverseProvider = new IntermediaryClassProvider(classProvider, mappingFile, mappingFile.reverse(), log); EnhancedRemapper enhancedRemapper = new MixinAwareEnhancedRemapper(reverseProvider, mappingFile, flatMappings, log); - SourceInterpreter reflectionRemapper = new ReflectionRemapperInterpreter(Opcodes.ASM9, mappingFile, flatMappings); - return new OptimizedRenamingTransformer(enhancedRemapper, false, reflectionRemapper); + return new OptimizedRenamingTransformer(enhancedRemapper, false); } - public OptimizedRenamingTransformer(EnhancedRemapper remapper, boolean collectAbstractParams, SourceInterpreter reflectionRemapper) { + public OptimizedRenamingTransformer(EnhancedRemapper remapper, boolean collectAbstractParams) { super(remapper, collectAbstractParams); - - this.reflectionRemapper = reflectionRemapper; } @Override @@ -95,8 +86,6 @@ protected void postProcess(ClassNode node) { } } } - - MethodCallAnalyzer.analyzeInterpretMethod(method, this.reflectionRemapper); } for (FieldNode field : node.fields) { field.value = postProcessRemapper.mapValue(field.value); @@ -270,62 +259,6 @@ public String mapPackageName(String name) { } } - private static class ReflectionRemapperInterpreter extends SourceInterpreter { - private static final Type STR_TYPE = Type.getType(String.class); - - private final Collection seen = new HashSet<>(); - private final IMappingFile mappings; - private final IntermediateMapping fastMappings; - - public ReflectionRemapperInterpreter(int api, IMappingFile mappings, IntermediateMapping fastMappings) { - super(api); - this.mappings = mappings; - this.fastMappings = fastMappings; - } - - @Override - public SourceValue naryOperation(AbstractInsnNode insn, List values) { - if (insn instanceof MethodInsnNode methodInsn && !this.seen.contains(methodInsn)) { - this.seen.add(methodInsn); - // Try to remap reflection method call args - Type[] args = Type.getArgumentTypes(methodInsn.desc); - if (args.length >= 3 && STR_TYPE.equals(args[0]) && STR_TYPE.equals(args[1]) && STR_TYPE.equals(args[2]) && values.size() >= 3) { - LdcInsnNode ownerInsn = getSingleLDCString(values.get(0)); - LdcInsnNode nameInsn = getSingleLDCString(values.get(1)); - LdcInsnNode descInsn = getSingleLDCString(values.get(2)); - if (ownerInsn != null && nameInsn != null && descInsn != null) { - String owner = (String) ownerInsn.cst; - IMappingFile.IClass cls = this.mappings.getClass(owner.replace('.', '/')); - if (cls != null) { - String name = (String) nameInsn.cst; - String desc = (String) descInsn.cst; - IMappingFile.IMethod mtd = cls.getMethod(name, desc); - if (mtd != null) { - ownerInsn.cst = owner.contains(".") ? cls.getMapped().replace('/', '.') : cls.getMapped(); - nameInsn.cst = mtd.getMapped(); - descInsn.cst = mtd.getMappedDescriptor(); - } - else { - String mappedName = this.fastMappings.mapMethod(name, desc); - if (mappedName != null) { - ownerInsn.cst = owner.contains(".") ? cls.getMapped().replace('/', '.') : cls.getMapped(); - nameInsn.cst = mappedName; - descInsn.cst = this.mappings.remapDescriptor(desc); - } - } - } - } - } - } - return super.naryOperation(insn, values); - } - - @Nullable - private static LdcInsnNode getSingleLDCString(SourceValue value) { - return value.insns.size() == 1 && value.insns.iterator().next() instanceof LdcInsnNode ldc && ldc.cst instanceof String ? ldc : null; - } - } - private static class MixinTargetAnalyzer extends ClassVisitor { private final Set targets = new HashSet<>(); diff --git a/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/EarlyJSCoremodTransformer.java b/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/EarlyJSCoremodTransformer.java new file mode 100644 index 00000000..ecc811de --- /dev/null +++ b/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/EarlyJSCoremodTransformer.java @@ -0,0 +1,121 @@ +package dev.su5ed.sinytra.connector.transformer.jar; + +import com.mojang.logging.LogUtils; +import cpw.mods.modlauncher.ClassTransformer; +import cpw.mods.modlauncher.LaunchPluginHandler; +import cpw.mods.modlauncher.TransformStore; +import cpw.mods.modlauncher.TransformationServiceDecorator; +import cpw.mods.modlauncher.TransformingClassLoader; +import cpw.mods.modlauncher.api.IEnvironment; +import cpw.mods.modlauncher.api.ITransformationService; +import cpw.mods.modlauncher.api.ITransformer; +import cpw.mods.modlauncher.api.ITransformerActivity; +import net.minecraftforge.coremod.CoreModProvider; +import net.minecraftforge.fart.api.ClassProvider; +import net.minecraftforge.fml.loading.moddiscovery.CoreModFile; +import net.minecraftforge.fml.loading.moddiscovery.ModFile; +import net.minecraftforge.fml.loading.moddiscovery.ModFileParser; +import net.minecraftforge.fml.unsafe.UnsafeHacks; +import net.minecraftforge.forgespi.locating.IModFile; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; + +import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static cpw.mods.modlauncher.api.LamdbaExceptionUtils.uncheck; + +public class EarlyJSCoremodTransformer implements ClassProvider { + private static final MethodHandle GET_CORE_MODS = uncheck(() -> MethodHandles.privateLookupIn(ModFileParser.class, MethodHandles.lookup()).findStatic(ModFileParser.class, "getCoreMods", MethodType.methodType(List.class, ModFile.class))); + private static final MethodHandle TRANSFORM = uncheck(() -> MethodHandles.privateLookupIn(ClassTransformer.class, MethodHandles.lookup()).findVirtual(ClassTransformer.class, "transform", MethodType.methodType(byte[].class, byte[].class, String.class, String.class))); + private static final Logger LOGGER = LogUtils.getLogger(); + + private final ClassProvider provider; + private final ClassTransformer transformer; + + public static EarlyJSCoremodTransformer create(ClassProvider classProvider, Iterable loadedMods) { + try { + CoreModProvider provider = new CoreModProvider(); + for (IModFile mf : loadedMods) { + List list = (List) GET_CORE_MODS.invoke((ModFile) mf); + if (!list.isEmpty()) { + for (CoreModFile f : list) { + provider.addCoreMod(f); + } + } + } + List> transformers = provider.getCoreModTransformers(); + + TransformStore transformStore = new TransformStore(); + ITransformationService service = new DummyService(transformers); + + Constructor cst = TransformationServiceDecorator.class.getDeclaredConstructor(ITransformationService.class); + cst.setAccessible(true); + TransformationServiceDecorator decorator = cst.newInstance(service); + decorator.gatherTransformers(transformStore); + + LaunchPluginHandler plugins = UnsafeHacks.newInstance(LaunchPluginHandler.class); + Field pluginsField = LaunchPluginHandler.class.getDeclaredField("plugins"); + pluginsField.setAccessible(true); + pluginsField.set(plugins, new HashMap<>()); + + Constructor xformCst = ClassTransformer.class.getDeclaredConstructor(TransformStore.class, LaunchPluginHandler.class, TransformingClassLoader.class); + xformCst.setAccessible(true); + ClassTransformer classTransformer = xformCst.newInstance(transformStore, plugins, null); + return new EarlyJSCoremodTransformer(classProvider, classTransformer); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + public EarlyJSCoremodTransformer(ClassProvider provider, ClassTransformer transformer) { + this.provider = provider; + this.transformer = transformer; + } + + @Override + public Optional getClass(String s) { + return this.provider.getClass(s); + } + + @Override + public Optional getClassBytes(String s) { + return this.provider.getClassBytes(s) + .map(bytes -> { + try { + return (byte[]) TRANSFORM.invoke(this.transformer, bytes, s, ITransformerActivity.COMPUTING_FRAMES_REASON); + } catch (Throwable t) { + LOGGER.error("Error transforming class " + s, t); + return bytes; + } + }); + } + + @Override + public void close() throws IOException { + this.provider.close(); + } + + @SuppressWarnings("rawtypes") + private record DummyService(List transformers) implements ITransformationService { + @Override + @NotNull + public String name() { + return "connector_early_js_coremods"; + } + + @Override + public void initialize(IEnvironment environment) {} + + @Override + public void onLoad(IEnvironment env, Set otherServices) {} + } +} diff --git a/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/JarTransformInstance.java b/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/JarTransformInstance.java index 877c80e6..fca33e41 100644 --- a/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/JarTransformInstance.java +++ b/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/JarTransformInstance.java @@ -5,6 +5,7 @@ import com.google.gson.JsonElement; import com.mojang.logging.LogUtils; import com.mojang.serialization.JsonOps; +import dev.su5ed.sinytra.connector.transformer.patch.ReflectionRenamingTransformer; import org.sinytra.adapter.patch.LVTOffsets; import org.sinytra.adapter.patch.api.GlobalReferenceMapper; import org.sinytra.adapter.patch.api.Patch; @@ -120,6 +121,7 @@ public void transformJar(File input, Path output, FabricModFileMetadata metadata MappingResolverImpl resolver = FabricLoaderImpl.INSTANCE.getMappingResolver(); RefmapRemapper.RefmapFiles refmap = RefmapRemapper.processRefmaps(input.toPath(), metadata.refmaps(), this.remapper, this.libs); IMappingFile srgToIntermediary = resolver.getMap(OBF_NAMESPACE, SOURCE_NAMESPACE); + IMappingFile intermediaryToSrg = resolver.getCurrentMap(SOURCE_NAMESPACE); AccessorRedirectTransformer accessorRedirectTransformer = new AccessorRedirectTransformer(srgToIntermediary); List extraPatches = Stream.concat(this.adapterPatches.stream(), AccessorRedirectTransformer.PATCHES.stream()).toList(); @@ -132,7 +134,8 @@ public void transformJar(File input, Path output, FabricModFileMetadata metadata .add(new JarSignatureStripper()) .add(new ClassNodeTransformer( new FieldToMethodTransformer(metadata.modMetadata().getAccessWidener(), srgToIntermediary), - accessorRedirectTransformer + accessorRedirectTransformer, + new ReflectionRenamingTransformer(intermediaryToSrg, IntermediateMapping.get(SOURCE_NAMESPACE)) )) .add(this.remappingTransformer) .add(new ClassNodeTransformer(new ClassAnalysingTransformer())) @@ -140,7 +143,8 @@ public void transformJar(File input, Path output, FabricModFileMetadata metadata .add(refmapRemapper) .add(new ModMetadataGenerator(metadata.modMetadata().getId())) .logger(s -> LOGGER.trace(TRANSFORM_MARKER, s)) - .debug(s -> LOGGER.trace(TRANSFORM_MARKER, s)); + .debug(s -> LOGGER.trace(TRANSFORM_MARKER, s)) + .ignoreJarPathPrefix("assets/", "data/"); if (!metadata.containsAT()) { builder.add(new AccessWidenerTransformer(metadata.modMetadata().getAccessWidener(), resolver, IntermediateMapping.get(SOURCE_NAMESPACE))); } diff --git a/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/JarTransformer.java b/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/JarTransformer.java index 46e98b33..50429d32 100644 --- a/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/JarTransformer.java +++ b/src/main/java/dev/su5ed/sinytra/connector/transformer/jar/JarTransformer.java @@ -128,9 +128,10 @@ private static List transformJars(List paths, L JarTransformInstance transformInstance; try { ClassProvider classProvider = ClassProvider.fromPaths(libs.toArray(Path[]::new)); - ILaunchPluginService.ITransformerLoader loader = name -> classProvider.getClassBytes(name.replace('.', '/')).orElseThrow(() -> new ClassNotFoundException(name)); + EarlyJSCoremodTransformer transformingClassProvider = EarlyJSCoremodTransformer.create(classProvider, loadedMods); + ILaunchPluginService.ITransformerLoader loader = name -> transformingClassProvider.getClassBytes(name.replace('.', '/')).orElseThrow(() -> new ClassNotFoundException(name)); setMixinClassProvider(loader); - transformInstance = new JarTransformInstance(classProvider, loadedMods, libs); + transformInstance = new JarTransformInstance(transformingClassProvider, loadedMods, libs); } finally { initProgress.complete(); } diff --git a/src/main/java/dev/su5ed/sinytra/connector/transformer/patch/ReflectionRenamingTransformer.java b/src/main/java/dev/su5ed/sinytra/connector/transformer/patch/ReflectionRenamingTransformer.java new file mode 100644 index 00000000..7e3a3c83 --- /dev/null +++ b/src/main/java/dev/su5ed/sinytra/connector/transformer/patch/ReflectionRenamingTransformer.java @@ -0,0 +1,104 @@ +package dev.su5ed.sinytra.connector.transformer.patch; + +import dev.su5ed.sinytra.connector.transformer.jar.IntermediateMapping; +import net.minecraftforge.srgutils.IMappingFile; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.SourceInterpreter; +import org.objectweb.asm.tree.analysis.SourceValue; +import org.sinytra.adapter.patch.analysis.MethodCallAnalyzer; +import org.sinytra.adapter.patch.api.Patch; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +public class ReflectionRenamingTransformer implements ClassNodeTransformer.ClassProcessor { + private final IMappingFile mappingFile; + private final IntermediateMapping flatMappings; + + public ReflectionRenamingTransformer(IMappingFile mappingFile, IntermediateMapping flatMappings) { + this.mappingFile = mappingFile; + this.flatMappings = flatMappings; + } + + @Override + public Patch.Result process(ClassNode node) { + boolean applied = false; + for (MethodNode method : node.methods) { + ReflectionRemapperInterpreter interpreter = new ReflectionRemapperInterpreter(Opcodes.ASM9, this.mappingFile, this.flatMappings); + MethodCallAnalyzer.analyzeInterpretMethod(method, interpreter); + applied |= interpreter.remapApplied(); + } + return applied ? Patch.Result.APPLY : Patch.Result.PASS; + } + + private static class ReflectionRemapperInterpreter extends SourceInterpreter { + private static final Type STR_TYPE = Type.getType(String.class); + + private final Collection seen = new HashSet<>(); + private final IMappingFile mappings; + private final IntermediateMapping fastMappings; + private boolean remapApplied = false; + + public ReflectionRemapperInterpreter(int api, IMappingFile mappings, IntermediateMapping fastMappings) { + super(api); + this.mappings = mappings; + this.fastMappings = fastMappings; + } + + public boolean remapApplied() { + return this.remapApplied; + } + + @Override + public SourceValue naryOperation(AbstractInsnNode insn, List values) { + if (insn instanceof MethodInsnNode methodInsn && !this.seen.contains(methodInsn)) { + this.seen.add(methodInsn); + // Try to remap reflection method call args + Type[] args = Type.getArgumentTypes(methodInsn.desc); + if (args.length >= 3 && STR_TYPE.equals(args[0]) && STR_TYPE.equals(args[1]) && STR_TYPE.equals(args[2]) && values.size() >= 3) { + LdcInsnNode ownerInsn = getSingleLDCString(values.get(0)); + LdcInsnNode nameInsn = getSingleLDCString(values.get(1)); + LdcInsnNode descInsn = getSingleLDCString(values.get(2)); + if (ownerInsn != null && nameInsn != null && descInsn != null) { + String owner = (String) ownerInsn.cst; + IMappingFile.IClass cls = this.mappings.getClass(owner.replace('.', '/')); + if (cls != null) { + String name = (String) nameInsn.cst; + String desc = (String) descInsn.cst; + IMappingFile.IMethod mtd = cls.getMethod(name, desc); + if (mtd != null) { + ownerInsn.cst = owner.contains(".") ? cls.getMapped().replace('/', '.') : cls.getMapped(); + nameInsn.cst = mtd.getMapped(); + descInsn.cst = mtd.getMappedDescriptor(); + this.remapApplied = true; + } + else { + String mappedName = this.fastMappings.mapMethod(name, desc); + if (mappedName != null) { + ownerInsn.cst = owner.contains(".") ? cls.getMapped().replace('/', '.') : cls.getMapped(); + nameInsn.cst = mappedName; + descInsn.cst = this.mappings.remapDescriptor(desc); + this.remapApplied = true; + } + } + } + } + } + } + return super.naryOperation(insn, values); + } + + @Nullable + private static LdcInsnNode getSingleLDCString(SourceValue value) { + return value.insns.size() == 1 && value.insns.iterator().next() instanceof LdcInsnNode ldc && ldc.cst instanceof String ? ldc : null; + } + } +}