mirror of
https://github.com/LegacyModdingMC/UniMixins.git
synced 2026-10-27 15:03:39 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93d8ebf6c6 | ||
|
|
4ed4fde3cf | ||
|
|
c47090bcae | ||
|
|
b7e3384ceb | ||
|
|
c5c5cbeca1 | ||
|
|
6e8f572f7e | ||
|
|
e60b681529 |
No files matched your search
@@ -1,5 +1,9 @@
|
||||
# Developer usage
|
||||
|
||||
To publish a release, you must publish a **draft release** (not pre-release) on GitHub. Make sure to create a tag (or pick the latest one if you screwed up the release), autogenerate (or manually write, I suppose) the description, and set it as the latest release.
|
||||
|
||||
If that doesn't get the GH action to build the mod, attach it to the release, and mark it un-draft, please figure out what broke and correct this section.
|
||||
|
||||
An example mod showcasing the mod's various features is available in the `example-*` branches of the repo:
|
||||
- [`example-fg-g4`](https://github.com/LegacyModdingMC/UniMixins/tree/example-fg-g4) - ForgeGradle-based build script with Gradle 4
|
||||
- [`example-fg-g6`](https://github.com/LegacyModdingMC/UniMixins/tree/example-fg-g6) - ForgeGradle-based build script with Gradle 6
|
||||
|
||||
@@ -147,7 +147,7 @@ tasks.withType(ShadowJar) {
|
||||
'FMLCorePluginContainsFMLMod': true,
|
||||
'ForceLoadAsMod': true,
|
||||
'FMLCorePlugin': 'io.github.legacymoddingmc.unimixins.all.AllCore',
|
||||
'MixinConfigs': 'mixins.gtnhmixins.json,mixingasm.mixin.json',
|
||||
'MixinConfigs': 'mixins.gtnhmixins.json,mixingasm.init.mixin.json,mixingasm.mixin.json',
|
||||
'Premain-Class': 'org.spongepowered.tools.agent.MixinAgent',
|
||||
'Agent-Class': 'org.spongepowered.tools.agent.MixinAgent',
|
||||
'Can-Redefine-Classes': true,
|
||||
|
||||
+69
-70
@@ -2,7 +2,6 @@ package io.github.legacymoddingmc.unimixins.compat.asm;
|
||||
|
||||
import static io.github.legacymoddingmc.unimixins.compat.CompatCore.LOGGER;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -10,13 +9,11 @@ import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.commons.Remapper;
|
||||
import org.objectweb.asm.commons.RemappingClassAdapter;
|
||||
import org.objectweb.asm.tree.AnnotationNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>The ASM package name used by Mixin differs between mixin loaders. This transformer remaps references to ASM to use the
|
||||
@@ -28,12 +25,12 @@ import java.util.stream.Collectors;
|
||||
* <li><code>org.objectweb.asm</code> (Mixin 0.8) in classes implementing {@link org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin} or annotated with {@link io.github.legacymoddingmc.unimixins.compat.api.RemapASMForMixin}</li>
|
||||
*/
|
||||
|
||||
// TODO Measure the performance impact of this and switch to doing it selectively if it would improve things.
|
||||
public class ASMRemapperTransformer implements IClassTransformer {
|
||||
|
||||
private static final String ASM_PACKAGE_UNSHADED = "org/objectweb/asm/";
|
||||
private static final String ASM_PACKAGE_LEGACY = "org/spongepowered/asm/lib/";
|
||||
private static final String ASM_PACKAGE_MBL = "org/spongepowered/libraries/org/objectweb/asm/";
|
||||
private static final String REMAP_ANNOTATION = "Lio/github/legacymoddingmc/unimixins/compat/api/RemapASMForMixin;";
|
||||
|
||||
private static final List<String> ASM_PACKAGE_PREFIXES = Arrays.asList(
|
||||
ASM_PACKAGE_UNSHADED,
|
||||
@@ -41,79 +38,95 @@ public class ASMRemapperTransformer implements IClassTransformer {
|
||||
ASM_PACKAGE_MBL
|
||||
);
|
||||
|
||||
private static final List<byte[]> SHADED_ASM_PACKAGE_PREFIXES_RAW = Arrays.asList(
|
||||
ASM_PACKAGE_LEGACY.getBytes(StandardCharsets.UTF_8),
|
||||
ASM_PACKAGE_MBL.getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
private static String realASMPackagePrefix;
|
||||
private static List<byte[]> wrongASMPackagePrefixesRaw;
|
||||
private static List<String> wrongASMPackagePrefixes;
|
||||
private final BytePatternMatcher wrongAsmMatcher;
|
||||
private final BytePatternMatcher shadedAsmMatcher;
|
||||
private final BytePatternMatcher remapAnnotationMatcher;
|
||||
|
||||
public ASMRemapperTransformer() {
|
||||
final String[] wrongAsmPackagePrefixes = ASM_PACKAGE_PREFIXES.stream()
|
||||
.filter(x -> !x.equals(getRealASMPackagePrefix()))
|
||||
.toArray(String[]::new);
|
||||
|
||||
final String[] shadedAsmPackagePrefixes = new String[] { ASM_PACKAGE_LEGACY, ASM_PACKAGE_MBL };
|
||||
|
||||
this.wrongAsmMatcher = new BytePatternMatcher(wrongAsmPackagePrefixes, BytePatternMatcher.Mode.Contains);
|
||||
this.shadedAsmMatcher = new BytePatternMatcher(shadedAsmPackagePrefixes, BytePatternMatcher.Mode.Contains);
|
||||
this.remapAnnotationMatcher = new BytePatternMatcher(REMAP_ANNOTATION, BytePatternMatcher.Mode.Equals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] basicClass) {
|
||||
if(basicClass == null) {
|
||||
if (basicClass == null) {
|
||||
return null;
|
||||
}
|
||||
if(transformedName.startsWith("io.github.legacymoddingmc.unimixins.compat.asm.")
|
||||
if (transformedName.startsWith("io.github.legacymoddingmc.unimixins.compat.asm.")
|
||||
|| transformedName.startsWith("com.google.")
|
||||
|| transformedName.startsWith("org.apache.")
|
||||
|| transformedName.startsWith("org.objectweb.asm.")
|
||||
) return basicClass;
|
||||
) {
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
boolean foundWrongAsm = containsAnyPattern(basicClass, getWrongASMPackagePrefixesRaw());
|
||||
ClassReader classReader = new ClassReader(basicClass);
|
||||
|
||||
if(!foundWrongAsm) return basicClass;
|
||||
boolean foundWrongAsm = matchUtf8Constant(classReader, basicClass, wrongAsmMatcher);
|
||||
if (!foundWrongAsm) return basicClass;
|
||||
|
||||
boolean foundShadedAsm = containsAnyPattern(basicClass, SHADED_ASM_PACKAGE_PREFIXES_RAW);
|
||||
boolean doRemap = matchUtf8Constant(classReader, basicClass, shadedAsmMatcher);
|
||||
|
||||
boolean doRemap = foundShadedAsm;
|
||||
|
||||
if(!doRemap) {
|
||||
ClassNode classNode = new ClassNode();
|
||||
ClassReader classReaderForNode = new ClassReader(basicClass);
|
||||
classReaderForNode.accept(classNode, 0);
|
||||
|
||||
if(classNode.interfaces != null) {
|
||||
for (String itf : classNode.interfaces) {
|
||||
if (itf.equals("org/spongepowered/asm/mixin/extensibility/IMixinConfigPlugin")) {
|
||||
doRemap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!doRemap) {
|
||||
if(classNode.visibleAnnotations != null) {
|
||||
for (AnnotationNode ann : classNode.visibleAnnotations) {
|
||||
if (ann.desc.equals("Lio/github/legacymoddingmc/unimixins/compat/api/RemapASMForMixin;")) {
|
||||
doRemap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!doRemap) {
|
||||
for (String itf : classReader.getInterfaces()) {
|
||||
if (itf.equals("org/spongepowered/asm/mixin/extensibility/IMixinConfigPlugin")) {
|
||||
doRemap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(doRemap) {
|
||||
ClassReader classReader = new ClassReader(basicClass);
|
||||
LOGGER.info("Transforming class " + transformedName + " to fit current mixin environment.");
|
||||
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
RemappingClassAdapter remapAdapter = new SpongepoweredASMRemappingAdapter(classWriter);
|
||||
classReader.accept(remapAdapter, ClassReader.EXPAND_FRAMES);
|
||||
basicClass = classWriter.toByteArray();
|
||||
if (!doRemap) {
|
||||
doRemap = matchUtf8Constant(classReader, basicClass, remapAnnotationMatcher);
|
||||
}
|
||||
return basicClass;
|
||||
|
||||
if (!doRemap) {
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
LOGGER.info("Transforming class {} to fit current mixin environment.", transformedName);
|
||||
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
RemappingClassAdapter remapAdapter = new SpongepoweredASMRemappingAdapter(classWriter);
|
||||
classReader.accept(remapAdapter, ClassReader.EXPAND_FRAMES);
|
||||
return classWriter.toByteArray();
|
||||
}
|
||||
|
||||
private static boolean containsAnyPattern(byte[] array, List<byte[]> patterns) {
|
||||
if(array == null) {
|
||||
return false;
|
||||
}
|
||||
for(byte[] pattern : patterns) {
|
||||
if(Bytes.indexOf(array, pattern) != -1) {
|
||||
private static boolean matchUtf8Constant(ClassReader classReader, byte[] basicClass, BytePatternMatcher matcher) {
|
||||
final int itemCount = classReader.getItemCount();
|
||||
|
||||
// Constant pool entries start from index 1
|
||||
for (int i = 1; i < itemCount; i++) {
|
||||
final int itemOffset = classReader.getItem(i);
|
||||
|
||||
// Long and double take two CP slots, the second slot has no item
|
||||
if (itemOffset == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only match the UTF8 constant (which tag is 1).
|
||||
// getItem(i) points after the tag byte, i.e. at the first length byte.
|
||||
// [tag][u2 length][bytes...]
|
||||
if (basicClass[itemOffset - 1] != 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Length takes 2 bytes, so we read them and skip these 2 bytes
|
||||
final int utfLen = classReader.readUnsignedShort(itemOffset);
|
||||
final int utfStart = itemOffset + 2;
|
||||
|
||||
if (matcher.matches(basicClass, utfStart, utfLen)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -146,20 +159,6 @@ public class ASMRemapperTransformer implements IClassTransformer {
|
||||
}
|
||||
return realASMPackagePrefix;
|
||||
}
|
||||
|
||||
private static List<String> getWrongASMPackagePrefixes() {
|
||||
if(wrongASMPackagePrefixes == null) {
|
||||
wrongASMPackagePrefixes = ASM_PACKAGE_PREFIXES.stream().filter(x -> !x.equals(getRealASMPackagePrefix())).collect(Collectors.toList());
|
||||
}
|
||||
return wrongASMPackagePrefixes;
|
||||
}
|
||||
|
||||
private static List<byte[]> getWrongASMPackagePrefixesRaw() {
|
||||
if(wrongASMPackagePrefixesRaw == null) {
|
||||
wrongASMPackagePrefixesRaw = getWrongASMPackagePrefixes().stream().map(x -> x.getBytes(StandardCharsets.UTF_8)).collect(Collectors.toList());
|
||||
}
|
||||
return wrongASMPackagePrefixesRaw;
|
||||
}
|
||||
|
||||
private static class SpongepoweredASMRemappingAdapter extends RemappingClassAdapter {
|
||||
public SpongepoweredASMRemappingAdapter(ClassWriter classWriter) {
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package io.github.legacymoddingmc.unimixins.compat.asm;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Was taken from <a href="https://github.com/GTNewHorizons/RetroFuturaBootstrap/pull/20">RFB</a>
|
||||
*/
|
||||
public class BytePatternMatcher {
|
||||
final Mode mode;
|
||||
// first byte -> matched patterns
|
||||
final byte[][][] byFirst = new byte[256][][];
|
||||
int minPatternLen = Integer.MAX_VALUE;
|
||||
|
||||
public enum Mode {
|
||||
/** Checks if the constant pool entry contains a pattern */
|
||||
Contains,
|
||||
/** Checks if the whole constant pool entry equals a pattern */
|
||||
Equals,
|
||||
/** Checks if the constant pool entry starts with a pattern */
|
||||
StartsWith
|
||||
}
|
||||
|
||||
public BytePatternMatcher(String strPattern, Mode mode) {
|
||||
this(new String[] {strPattern}, mode);
|
||||
}
|
||||
|
||||
public BytePatternMatcher(String[] strPatterns, Mode mode) {
|
||||
this.mode = mode;
|
||||
|
||||
final byte[][] patterns = new byte[strPatterns.length][];
|
||||
final int[] bucketSizes = new int[256];
|
||||
final int[] bucketIndices = new int[Math.min(strPatterns.length, 256)];
|
||||
int bucketsCount = 0;
|
||||
|
||||
for (int i = 0; i < strPatterns.length; i++) {
|
||||
final byte[] pattern = strPatterns[i].getBytes(StandardCharsets.UTF_8);
|
||||
patterns[i] = pattern;
|
||||
|
||||
if (pattern.length < minPatternLen) {
|
||||
minPatternLen = pattern.length;
|
||||
}
|
||||
|
||||
final int bucketIndex = pattern[0] & 0xFF;
|
||||
if (bucketSizes[bucketIndex]++ == 0) {
|
||||
bucketIndices[bucketsCount++] = bucketIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Ascending sorting by length
|
||||
Arrays.sort(patterns, (a, b) -> Integer.compare(a.length, b.length));
|
||||
|
||||
for (int i = 0; i < bucketsCount; i++) {
|
||||
final int bucketIndex = bucketIndices[i];
|
||||
byFirst[bucketIndex] = new byte[bucketSizes[bucketIndex]][];
|
||||
bucketSizes[bucketIndex] = 0; // reuse as write index
|
||||
}
|
||||
|
||||
for (final byte[] pattern : patterns) {
|
||||
final int bucketIndex = pattern[0] & 0xFF;
|
||||
byFirst[bucketIndex][bucketSizes[bucketIndex]++] = pattern;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(byte[] bytes, int start, int len) {
|
||||
if (len < minPatternLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case Contains:
|
||||
return contains(bytes, start, len);
|
||||
case Equals:
|
||||
return equals(bytes, start, len);
|
||||
case StartsWith:
|
||||
return startsWith(bytes, start, len);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean contains(byte[] bytes, int start, int len) {
|
||||
final int end = start + len;
|
||||
|
||||
for (int pos = start; pos <= end - minPatternLen; pos++) {
|
||||
final byte[][] patterns = byFirst[bytes[pos] & 0xFF];
|
||||
if (patterns == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (final byte[] pattern : patterns) {
|
||||
if (pattern.length > end - pos) {
|
||||
break;
|
||||
}
|
||||
|
||||
int k = pattern.length - 1;
|
||||
while (k > 0 && bytes[pos + k] == pattern[k]) k--;
|
||||
if (k == 0) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean equals(byte[] bytes, int start, int len) {
|
||||
final byte[][] patterns = byFirst[bytes[start] & 0xFF];
|
||||
if (patterns == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final byte[] pattern : patterns) {
|
||||
if (pattern.length < len) {
|
||||
continue;
|
||||
}
|
||||
if (pattern.length > len) {
|
||||
break;
|
||||
}
|
||||
|
||||
int k = pattern.length - 1;
|
||||
while (k > 0 && bytes[start + k] == pattern[k]) k--;
|
||||
if (k == 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean startsWith(byte[] bytes, int start, int len) {
|
||||
final byte[][] patterns = byFirst[bytes[start] & 0xFF];
|
||||
if (patterns == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final byte[] pattern : patterns) {
|
||||
if (pattern.length > len) {
|
||||
break;
|
||||
}
|
||||
|
||||
int k = pattern.length - 1;
|
||||
while (k > 0 && bytes[start + k] == pattern[k]) k--;
|
||||
if (k == 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+5
-9
@@ -24,8 +24,6 @@ public class HackClasspathModDiscoveryTransformer implements IClassTransformer {
|
||||
if(basicClass == null) {
|
||||
return null;
|
||||
}
|
||||
if(transformedName.startsWith("org.objectweb.asm.")) return basicClass;
|
||||
|
||||
if(transformedName.equals("cpw.mods.fml.common.discovery.ModDiscoverer")) {
|
||||
basicClass = doTransformModDiscoverer(basicClass);
|
||||
}
|
||||
@@ -41,22 +39,20 @@ public class HackClasspathModDiscoveryTransformer implements IClassTransformer {
|
||||
classReader.accept(classNode, 0);
|
||||
for(MethodNode m : classNode.methods) {
|
||||
if(m.name.equals("findClasspathMods")) {
|
||||
Iterator<AbstractInsnNode> it = m.instructions.iterator();
|
||||
|
||||
while(it.hasNext()) {
|
||||
AbstractInsnNode i = it.next();
|
||||
for (AbstractInsnNode i = m.instructions.getFirst(); i != null; i = i.getNext()) {
|
||||
if(i.getOpcode() == INVOKESTATIC) {
|
||||
MethodInsnNode mi = (MethodInsnNode)i;
|
||||
if(mi.owner.equals("cpw/mods/fml/relauncher/CoreModManager") && mi.name.equals("getReparseableCoremods") && mi.desc.equals("()Ljava/util/List;")) {
|
||||
m.instructions.insertBefore(mi, new MethodInsnNode(INVOKESTATIC, "io/github/legacymoddingmc/unimixins/compat/asm/HackClasspathModDiscoveryTransformer$Hooks", "redirectGetReparseableCoremods", mi.desc));
|
||||
it.remove();
|
||||
m.instructions.insertBefore(mi, new MethodInsnNode(INVOKESTATIC, "io/github/legacymoddingmc/unimixins/compat/asm/HackClasspathModDiscoveryTransformer$Hooks", "redirectGetReparseableCoremods", mi.desc, false));
|
||||
m.instructions.remove(mi);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
||||
ClassWriter writer = new ClassWriter(0);
|
||||
classNode.accept(writer);
|
||||
return writer.toByteArray();
|
||||
} catch(Exception e) {
|
||||
|
||||
+3
-2
@@ -74,11 +74,12 @@ public class IgnoreDuplicateJarsTransformer implements IClassTransformer {
|
||||
if(m.name.equals("identifyMods")) {
|
||||
InsnList patch = new InsnList();
|
||||
patch.add(new VarInsnNode(ALOAD, 0));
|
||||
patch.add(new MethodInsnNode(INVOKESTATIC, "io/github/legacymoddingmc/unimixins/compat/asm/IgnoreDuplicateJarsTransformer$Hooks", "preIdentifyMods", "(Lcpw/mods/fml/common/discovery/ModDiscoverer;)V"));
|
||||
patch.add(new MethodInsnNode(INVOKESTATIC, "io/github/legacymoddingmc/unimixins/compat/asm/IgnoreDuplicateJarsTransformer$Hooks", "preIdentifyMods", "(Lcpw/mods/fml/common/discovery/ModDiscoverer;)V", false));
|
||||
m.instructions.insert(patch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
||||
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
classNode.accept(writer);
|
||||
return writer.toByteArray();
|
||||
} catch(Exception e) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fabricMixinVersion=0.15.3+mixin.0.8.7
|
||||
unimixMixinVersion=0.15.5+mixin.0.8.7
|
||||
unimixMixinVersion=0.15.6+mixin.0.8.7
|
||||
spongepoweredMixinVersion=0.8.7
|
||||
|
||||
asmVersion=9.9.1
|
||||
@@ -21,7 +21,7 @@ shadowJar {
|
||||
'TweakClass': 'org.spongepowered.asm.launch.MixinTweaker',
|
||||
'FMLCorePluginContainsFMLMod': true,
|
||||
'ForceLoadAsMod': true,
|
||||
'MixinConfigs': 'mixingasm.mixin.json',
|
||||
'MixinConfigs': 'mixingasm.init.mixin.json,mixingasm.mixin.json',
|
||||
'TweakOrder': 0,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.6",
|
||||
"target": "@env(INIT)",
|
||||
"package": "makamys.mixingasm.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [],
|
||||
"plugin": "makamys.mixingasm.MixinConfigPlugin"
|
||||
}
|
||||
Reference in new issue
Block a user