理论存在
废话不多说,先看一段代码
import com.sun.tools.attach.VirtualMachine;
public class AttachExample {
public static void main(String[] args) throws Exception {
// 获取要附加到的 JVM 进程的进程 ID
String pid = args[0];
// 附加到该进程
VirtualMachine vm = VirtualMachine.attach(pid);
// 执行你想要的操作,例如:
vm.loadAgent("my-agent.jar");
// 不要忘记在最后断开连接
vm.detach();
}
}
众所周知,使用com.sun.tools.attach.VirtualMachine工具可以把一个agent注入到正在运行的JVM上加载
既然如此,那么是不是可以写一个agent,把这个agent注入到Minecraft客户端中,再让这个agent调用模组加载器(如Forge/Fabric)将我们想加载的mod加载进客户端呢?
public class MyInjectedCode {
public static void agentmain(String args, Instrumentation inst) throws Exception {
System.out.println("Hello from injected code!");
}
public static void premain(String args, Instrumentation inst) throws Exception
{
System.out.println("Hello from injected code!");
}
}
这段代码在执行的时候会打印出"Hello from injected code!"字符串。
接下来把这段代码编译并打包成jar,注入进指定进程
import com.sun.tools.attach.VirtualMachine;
public class AttachExample {
public static void main(String[] args) throws Exception {
// 获取要附加到的 JVM 进程的进程 ID
String pid = args[0];
// 附加到该进程
VirtualMachine vm = VirtualMachine.attach(pid);
// 加载并注入代码(执行)
vm.loadAgent("my-agent.jar", "MyInjectedCode");
// 不要忘记在最后断开连接
vm.detach();
}
}
这样似乎就成功将一个agent注入进JVM并调用了。
实践开始
接下来进行实际测试
被注入的代码
首先,写一段输出字符串的代码,保存到MyInjectedCode.java文件中
import java.lang.instrument.Instrumentation;
import java.io.*;
public class MyInjectedCode {
public static void agentmain(String args, Instrumentation inst) throws Exception {
System.out.println("(agentmain)Hello from injected code!");
System.out.println("Args:" + args);
}
public static void premain(String args, Instrumentation inst) throws Exception
{
System.out.println("(premain)Hello from injected code!");
System.out.println("Args:" + args);
Class[] classes = inst.getAllLoadedClasses();
for (Class clazz : classes)
{
System.out.println(clazz.getName());
}
}
}
编译这段代码,得到class文件
使用jar命令打包得到jar
打包命令:
jar --create --file "my-agent.jar" MyInjectedCode.class
用压缩软件打开jar文件,修改MANIFEST.MF文件,新增内容:
#建议去除上面Created-By这行
Agent-Class: MyInjectedCode
Premain-Class: MyInjectedCode
Can-Redine-Classes: true
Can-Retransform-Classes: true
注入工具
代码在下
import java.util.Properties;
import com.sun.tools.attach.VirtualMachine;
public class AttachExample {
public static void main(String[] args) throws Exception {
VirtualMachine vm = null;
String pid = args[0];
vm = VirtualMachine.attach(pid);
vm.loadAgent("C:/Users/xxxxxx/Desktop/测试/my-agent.jar", "MyInjectedCode");
//注意!!!这里必须用完整的路径!!
//否则抛异常Exception in thread "main" com.sun.tools.attach.AgentLoadException: Agent JAR not found or no Agent-Class attribute
at jdk.attach/sun.tools.attach.HotSpotVirtualMachine.loadAgent(HotSpotVirtualMachine.java:163)
//vm.executeAgent("MyInjectedCode");
vm.detach();
}
}
保存,编译完成之后,开始测试!
实际注入测试
这里拿MC来测试
先启动MC ( ̄︶ ̄*))
启动器里选择"测试游戏",方便查看日志输出
然后等待一会,等到MC不输出日志了再操作(方便看qwq)
然后得获取到MC进程的PID才行
注意此时MC日志输出:
现在,启动注入工具!
额,报错了
先看看MC的日志:
很显然,虽然我们的jar已经注入进去了,但是却没能成功加载
翻译一下,意思大概是这样:
类“MyInjectedCode”的签名者信息与同一包中其他类的签名者不匹配
此时不慌,先在MC里把Forge装上
和之前一样找到PID
成功被执行啦 φ(゜▽゜*)♪ !
(测试版本:1.19.3)
【但是谁会去往原版端里注入模组啊喂( ̄▽ ̄)"】
关于原版端注入的尝试
从上文得知,貌似因为Mojang给Minecraft Jar上了签名,导致咱们的Jar包签名和Mojang的不同引发异常
【获取到PID,图和步骤略】
那么现在,删除原版端的签名试一试吧!
首先,找到Jar文件!
(作者这里开了启动器的版本隔离)
用压缩软件打开这个jar文件,进入META-INF文件夹
删除名为所有后缀名为"RSA",“SF”,"DSA"的文件
编辑"MANIFEST.MF"文件
只保留最前面这两行,其他全删掉:
Manifest-Version: 1.0
Main-Class: net.minecraft.client.Main
但是!实际测试还是出现和上文相同的情况!
后来发现,在目录".minecraft\libraries\net\minecraft\client"中还有一些Jar文件,作者这里就不对其进行去签名注入测试了,感兴趣的小伙伴可以自己尝试一下
关于Fabric/Quilt/LiteLoader端的尝试
和上文一样,安装对应的加载器并运行注入工具进行测试
图片上传不了了,直接看结果吧!
Fabric端注入成功!
(测试版本:1.19.3)
Quilt Loader端注入成功!
(测试版本:1.19.3)
原版 + OptiFine 注入成功!
(测试版本:1.18.2)
LiteLoader 注入遇到问题
(测试版本:1.12.2)
这里是因为我启动Minecraft 1.12.2用的是Java11,而我编译Agent时用的是JDK18,所以到了这里就寄了
该问题由Java版本所致,解决起来很简单,这里作者就不做过多的测试了,感兴趣的小伙伴可以自己测试哦╰(°▽°)╯
注入模组的思路和这种方法的缺点
上文只是把代码注入进了游戏里,那么怎么让这段代码加载模组呢?
作者的思路是调用模组加载器提供的API等接口,让游戏加载
但是这种方法有一个缺点,即Mixin可能无法使用
Mixin为模组开发者提供了一种更方便地修改Minecraft源码的方式,使用Mixin的模组应该需要在Minecraft启动前由模组加载器加载到游戏中,现在直接把模组注入进已经加载好的Minecraft中,Mixin当然也就歇菜了