Skip to content

Commit 57bb389

Browse files
committed
Implement support for only applying mixins when specific versions of a mod are installed
1 parent 07f9317 commit 57bb389

13 files changed

Lines changed: 211 additions & 82 deletions

File tree

common/build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,14 @@ artifacts {
177177
}
178178

179179
dependencies {
180-
compileOnly "org.spongepowered:mixin:0.8.5"
180+
compileOnly "net.fabricmc:sponge-mixin:0.15.2+mixin.0.8.7"
181181

182182
// fabric and neoforge both bundle mixinextras, so it is safe to use it in common
183183
compileOnly "io.github.llamalad7:mixinextras-common:0.5.3"
184184
annotationProcessor "io.github.llamalad7:mixinextras-common:0.5.3"
185185

186+
implementation "org.apache.maven:maven-artifact:3.8.5"
187+
186188
api "dev.ryanhcode.sable-companion:sable-companion-common-$minecraft_version:$sable_companion_version"
187189

188190
implementation("foundry.veil:veil-common-${project.minecraft_version}:${project.veil_version}") {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package dev.ryanhcode.sable.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.TYPE)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface MixinModVersionConstraint {
11+
/**
12+
* The version range in maven format
13+
*/
14+
String value();
15+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package dev.ryanhcode.sable.mixin;
2+
3+
import com.mojang.logging.LogUtils;
4+
import dev.ryanhcode.sable.annotation.MixinModVersionConstraint;
5+
import dev.ryanhcode.sable.platform.SableLoaderPlatform;
6+
import foundry.veil.Veil;
7+
import foundry.veil.api.compat.SodiumCompat;
8+
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
9+
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
10+
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
11+
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
12+
import org.apache.maven.artifact.versioning.ArtifactVersion;
13+
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
14+
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
15+
import org.apache.maven.artifact.versioning.VersionRange;
16+
import org.objectweb.asm.Type;
17+
import org.objectweb.asm.tree.AnnotationNode;
18+
import org.objectweb.asm.tree.ClassNode;
19+
import org.slf4j.Logger;
20+
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
21+
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
22+
import org.spongepowered.asm.service.MixinService;
23+
import org.spongepowered.asm.util.Annotations;
24+
25+
import java.util.List;
26+
import java.util.Set;
27+
28+
public abstract class AbstractSableMixinPlugin implements IMixinConfigPlugin {
29+
public static final Logger LOGGER = LogUtils.getLogger();
30+
private final Object2BooleanMap<String> modLoadedCache = new Object2BooleanOpenHashMap<>();
31+
private boolean sodiumPresent;
32+
33+
@Override
34+
public void onLoad(final String mixinPackage) {
35+
this.sodiumPresent = SodiumCompat.isLoaded();
36+
37+
LOGGER.info("Using {} renderer mixins", this.sodiumPresent ? "Sodium" : "Vanilla");
38+
}
39+
40+
@Override
41+
public String getRefMapperConfig() {
42+
return null;
43+
}
44+
45+
@Override
46+
public boolean shouldApplyMixin(final String targetClassName, final String mixinClassName) {
47+
// TODO: Housekeeping
48+
if (mixinClassName.startsWith("dev.ryanhcode.sable.mixin.sublevel_render.impl")) {
49+
return this.sodiumPresent ? mixinClassName.startsWith("dev.ryanhcode.sable.mixin.sublevel_render.impl.sodium") : mixinClassName.startsWith("dev.ryanhcode.sable.mixin.sublevel_render.impl.vanilla");
50+
}
51+
52+
if (isCompatibilityMixin(mixinClassName)) {
53+
final String modId = extractModId(mixinClassName);
54+
if (modId == null) {
55+
return true;
56+
}
57+
58+
final boolean isModLoaded = this.modLoadedCache.computeIfAbsent(modId, x -> Veil.platform().isModLoaded(modId));
59+
return isModLoaded && MixinConstraints.handleClassAnnotation(mixinClassName, modId);
60+
}
61+
62+
return true;
63+
}
64+
65+
@Override
66+
public void acceptTargets(final Set<String> myTargets, final Set<String> otherTargets) {
67+
}
68+
69+
@Override
70+
public List<String> getMixins() {
71+
return null;
72+
}
73+
74+
@Override
75+
public void preApply(final String targetClassName, final ClassNode targetClass, final String mixinClassName, final IMixinInfo mixinInfo) {
76+
if (isCompatibilityMixin(mixinClassName)) {
77+
final String modId = extractModId(mixinClassName);
78+
if (modId == null) {
79+
return;
80+
}
81+
82+
// Handle removing methods if there are constraints on them
83+
targetClass.methods.removeIf(method -> {
84+
final List<AnnotationNode> nodes = method.visibleAnnotations;
85+
86+
try {
87+
return nodes != null && MixinConstraints.shouldApply(nodes, modId);
88+
} catch (final Throwable e) {
89+
throw new RuntimeException(e);
90+
}
91+
});
92+
}
93+
}
94+
95+
@Override
96+
public void postApply(final String targetClassName, final ClassNode targetClass, final String mixinClassName, final IMixinInfo mixinInfo) {
97+
}
98+
99+
static boolean isCompatibilityMixin(final String mixinClassName) {
100+
return mixinClassName.startsWith("dev.ryanhcode.sable.mixin.compatibility.") ||
101+
mixinClassName.startsWith("dev.ryanhcode.sable.neoforge.mixin.compatibility.") ||
102+
mixinClassName.startsWith("dev.ryanhcode.sable.fabric.mixin.compatibility.");
103+
}
104+
105+
static String extractModId(final String mixinClassName) {
106+
final String[] parts = mixinClassName.split("\\.");
107+
if (parts.length < 5) {
108+
return null;
109+
}
110+
111+
return parts[3].equals("mixin") ? parts[5] : parts[6];
112+
}
113+
114+
// Constraint handling
115+
static class MixinConstraints {
116+
private static final Object2ObjectMap<String, String> MOD_VERSION_CACHE = new Object2ObjectOpenHashMap<>();
117+
118+
// Looks for if there's a @MixinModVersionConstraint annotation which declares a range for when a mixin should be loaded
119+
static boolean handleClassAnnotation(final String mixinClassName, final String modId) {
120+
try {
121+
final List<AnnotationNode> nodes = MixinService.getService().getBytecodeProvider().getClassNode(mixinClassName).visibleAnnotations;
122+
123+
return nodes != null && shouldApply(nodes, modId);
124+
} catch (final Throwable e) {
125+
throw new RuntimeException(e);
126+
}
127+
}
128+
129+
static boolean shouldApply(final List<AnnotationNode> nodes, final String modId) throws InvalidVersionSpecificationException {
130+
for (final AnnotationNode node : nodes) {
131+
if (node.desc.equals(Type.getDescriptor(MixinModVersionConstraint.class))) {
132+
final String range = Annotations.getValue(node, "value");
133+
final VersionRange versionRange = VersionRange.createFromVersionSpec(range);
134+
135+
final String modVersion = MOD_VERSION_CACHE.computeIfAbsent(modId, x -> SableLoaderPlatform.INSTANCE.getModVersion(modId));
136+
final ArtifactVersion artifactVersion = new DefaultArtifactVersion(modVersion);
137+
138+
return versionRange.containsVersion(artifactVersion);
139+
}
140+
}
141+
142+
return true;
143+
}
144+
}
145+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.ryanhcode.sable.platform;
2+
3+
import org.jetbrains.annotations.ApiStatus;
4+
5+
@ApiStatus.Internal
6+
public interface SableLoaderPlatform {
7+
SableLoaderPlatform INSTANCE = SablePlatformUtil.load(SableLoaderPlatform.class);
8+
9+
String getModVersion(String modId);
10+
}

common/src/main/java/dev/ryanhcode/sable/sublevel/render/AbstractSableMixinPlugin.java

Lines changed: 0 additions & 73 deletions
This file was deleted.

fabric/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ dependencies {
1717
modImplementation fabricApi.module("fabric-resource-loader-v0", project.fabric_version)
1818
modImplementation fabricApi.module("fabric-convention-tags-v2", project.fabric_version)
1919

20+
include(implementation("org.apache.maven:maven-artifact:3.8.5"))
21+
2022
include(modApi("dev.ryanhcode.sable-companion:sable-companion-fabric-$minecraft_version:$sable_companion_version"))
2123

2224
modCompileOnly "maven.modrinth:sodium:$rootProject.sodium_version"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dev.ryanhcode.sable.fabric.platform;
2+
3+
import dev.ryanhcode.sable.platform.SableLoaderPlatform;
4+
import net.fabricmc.loader.api.FabricLoader;
5+
6+
public class SableLoaderPlatformImpl implements SableLoaderPlatform {
7+
@Override
8+
public String getModVersion(final String modId) {
9+
return FabricLoader.getInstance()
10+
.getModContainer(modId)
11+
.orElseThrow()
12+
.getMetadata()
13+
.getVersion()
14+
.getFriendlyString();
15+
}
16+
}
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package dev.ryanhcode.sable.plugin;
22

3-
import dev.ryanhcode.sable.sublevel.render.AbstractSableMixinPlugin;
3+
import dev.ryanhcode.sable.mixin.AbstractSableMixinPlugin;
44
import org.objectweb.asm.tree.ClassNode;
55
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
66

77
public class SableMixinPlugin extends AbstractSableMixinPlugin {
8-
98
@Override
109
public void preApply(final String targetClassName, final ClassNode targetClass, final String mixinClassName, final IMixinInfo mixinInfo) {
11-
10+
super.preApply(targetClassName, targetClass, mixinClassName, mixinInfo);
1211
}
1312

1413
@Override
1514
public void postApply(final String targetClassName, final ClassNode targetClass, final String mixinClassName, final IMixinInfo mixinInfo) {
16-
15+
super.postApply(targetClassName, targetClass, mixinClassName, mixinInfo);
1716
}
1817
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dev.ryanhcode.sable.fabric.platform.SableLoaderPlatformImpl
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.ryanhcode.sable.neoforge.platform;
2+
3+
import dev.ryanhcode.sable.platform.SableLoaderPlatform;
4+
import net.neoforged.fml.loading.LoadingModList;
5+
6+
public class SableLoaderPlatformImpl implements SableLoaderPlatform {
7+
@Override
8+
public String getModVersion(final String modId) {
9+
return LoadingModList.get().getModFileById(modId).versionString();
10+
}
11+
}

0 commit comments

Comments
 (0)