Skip to content

Commit 03b2395

Browse files
committed
Add custom agent to work around FabricMC/fabric-loader#817
1 parent e023829 commit 03b2395

File tree

7 files changed

+183
-1
lines changed

7 files changed

+183
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ media
55
classes/
66
.architectury-transformer/
77
fabric/fabricloader.log
8+
fabric/test_run
89

910
# Changelog
1011
CHANGELOG.md

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ allprojects {
9393
}
9494
}
9595

96-
subprojects {
96+
configure(subprojects.findAll {it.name == "common" || it.name == "forge" || it.name == "fabric"}) {
9797
apply plugin: "dev.architectury.loom"
9898

9999
loom {

fabric/build.gradle

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ configurations {
2222

2323
include.extendsFrom modIncludeImplementation
2424
modImplementation.extendsFrom modIncludeImplementation
25+
26+
testAgent {
27+
canBeConsumed = false
28+
}
2529
}
2630

2731
dependencies {
@@ -51,10 +55,22 @@ dependencies {
5155
testImplementation("org.assertj:assertj-core:3.19.0")
5256
testImplementation("com.google.guava:guava-testlib:21.0")
5357
testImplementation("org.mockito:mockito-junit-jupiter:5.3.1")
58+
59+
testAgent(project("path": ":test_agent", "configuration": "agentJar"))
5460
}
5561

5662
test {
5763
useJUnitPlatform()
64+
def runDir = file('test_run')
65+
doFirst() {
66+
runDir.mkdir()
67+
}
68+
workingDir = runDir
69+
70+
// inject our custom agent to fix #817
71+
FileCollection agentFile = configurations.getByName("testAgent")
72+
jvmArgs "-javaagent:${agentFile.singleFile.absolutePath}"
73+
dependsOn(agentFile)
5874
}
5975

6076
processResources {

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pluginManagement {
77
}
88
}
99

10+
include("test_agent")
1011
include("common")
1112
include("fabric")
1213
include("forge")

test_agent/build.gradle

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
plugins {
2+
//id 'com.github.johnrengelman.shadow' version '7.1.2'
3+
id 'java'
4+
}
5+
6+
group 'org.embeddedt'
7+
archivesBaseName = 'modernfix-test-agent'
8+
version '1.0'
9+
10+
sourceCompatibility = '1.8'
11+
targetCompatibility = '1.8'
12+
13+
repositories {
14+
mavenCentral()
15+
mavenLocal()
16+
maven {
17+
name = 'forge'
18+
url = 'https://maven.minecraftforge.net/'
19+
}
20+
}
21+
22+
/*
23+
24+
shadowJar {
25+
relocate 'net.bytebuddy.agent', 'org.embeddedt.modernfix.testing.shadow.bytebuddyagent'
26+
relocate 'org.objectweb.asm', 'org.embeddedt.modernfix.testing.shadow.asm'
27+
}
28+
29+
30+
31+
shadowJar {
32+
project.configurations.implementation.canBeResolved = true
33+
configurations = [project.configurations.implementation]
34+
}
35+
*/
36+
dependencies {
37+
compileOnly "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
38+
implementation "org.ow2.asm:asm-tree:9.1"
39+
implementation "org.ow2.asm:asm-commons:9.1"
40+
implementation "org.ow2.asm:asm-util:9.1"
41+
42+
//implementation('net.bytebuddy:byte-buddy-agent:1.12.22')
43+
}
44+
45+
tasks.withType(JavaCompile) {
46+
// ensure that the encoding is set to UTF-8, no matter what the system default is
47+
// this fixes some edge cases with special characters not displaying correctly
48+
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
49+
// If Javadoc is generated, this must be specified in that task too.
50+
options.encoding = "UTF-8"
51+
}
52+
53+
jar {
54+
manifest {
55+
attributes(
56+
"Premain-Class": "org.embeddedt.modernfix.testing.Agent",
57+
"Can-Redefine-Classes": false,
58+
"Can-Set-Native-Method-Prefix": false
59+
)
60+
}
61+
}
62+
/*
63+
64+
shadowJar {
65+
archiveBaseName.set('modernfix-test-agent')
66+
archiveClassifier.set('')
67+
archiveVersion.set('v1')
68+
}
69+
70+
*/
71+
72+
configurations {
73+
agentJar {
74+
canBeConsumed = true
75+
canBeResolved = false
76+
}
77+
}
78+
79+
artifacts {
80+
agentJar(jar)
81+
}
82+
/*
83+
project.tasks.shadowJar.dependsOn build
84+
defaultTasks 'shadowJar'
85+
86+
*/
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.embeddedt.modernfix.testing;
2+
3+
import org.objectweb.asm.ClassReader;
4+
import org.objectweb.asm.ClassWriter;
5+
import org.objectweb.asm.Opcodes;
6+
import org.objectweb.asm.tree.AbstractInsnNode;
7+
import org.objectweb.asm.tree.ClassNode;
8+
import org.objectweb.asm.tree.MethodInsnNode;
9+
import org.objectweb.asm.tree.MethodNode;
10+
11+
import java.lang.instrument.ClassFileTransformer;
12+
import java.lang.instrument.IllegalClassFormatException;
13+
import java.lang.instrument.Instrumentation;
14+
import java.security.ProtectionDomain;
15+
import java.util.ListIterator;
16+
17+
public class Agent {
18+
/**
19+
* Simple agent that transforms Fabric Loader to never mark game JARs as system libraries.
20+
*
21+
* Ugly, but usable workaround for <a href="https://github.com/FabricMC/fabric-loader/issues/817">issue #817</a>
22+
* on the Loader bug tracker.
23+
*/
24+
public static void premain(String args, Instrumentation instrumentation) {
25+
instrumentation.addTransformer(new ClassFileTransformer() {
26+
@Override
27+
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
28+
if(className.equals("net/fabricmc/loader/impl/game/LibClassifier")) {
29+
ClassNode node = new ClassNode();
30+
ClassReader reader = new ClassReader(classfileBuffer);
31+
reader.accept(node, 0);
32+
for(MethodNode m : node.methods) {
33+
if(m.name.equals("<init>")) {
34+
ListIterator<AbstractInsnNode> iter = m.instructions.iterator();
35+
int addMatches = 0;
36+
while(iter.hasNext()) {
37+
AbstractInsnNode n = iter.next();
38+
if(n instanceof MethodInsnNode) {
39+
MethodInsnNode invokeNode = (MethodInsnNode)n;
40+
if(invokeNode.name.equals("add") && invokeNode.owner.equals("java/util/Set") && invokeNode.desc.equals("(Ljava/lang/Object;)Z")) {
41+
addMatches++;
42+
if(addMatches == 2) {
43+
iter.set(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/embeddedt/modernfix/testing/AgentHooks", "addLibraryWithCheck", "(Ljava/util/Set;Ljava/lang/Object;)Z", false));
44+
break;
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
51+
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
52+
node.accept(writer);
53+
byte[] finalArray = writer.toByteArray();
54+
//dumpDebugClass(className, finalArray);
55+
return finalArray;
56+
}
57+
return classfileBuffer;
58+
}
59+
});
60+
}
61+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.embeddedt.modernfix.testing;
2+
3+
import java.nio.file.Path;
4+
import java.util.Set;
5+
6+
@SuppressWarnings("unused")
7+
public class AgentHooks {
8+
@SuppressWarnings({"unchecked", "rawtypes" })
9+
public static boolean addLibraryWithCheck(Set pathSet, Object path) {
10+
boolean shouldAdd;
11+
if(path instanceof Path) {
12+
shouldAdd = !((Path)path).toString().contains("minecraft-merged");
13+
} else
14+
shouldAdd = true;
15+
return shouldAdd && pathSet.add(path);
16+
}
17+
}

0 commit comments

Comments
 (0)