1.3.2-1.5.2 FML CoreMod大事记
1.3.2 FML重写了Mod加载机制
加入了基于FML Relauncher的CoreMod机制
加载流程
(对加载流程不感兴趣的话可以直接跳过这一部分,以下代码来自1.5.2FML)
非常感谢您能耐心看到本章,这一章的主要目的是从FML CoreMod的起源来探究这一机制是如何一脉相承的。在这些版本中,没有LaunchWrapper、ModLauncher等专门负责ClassLoader、Transformer的库,所有相关的行为都需要FML自行处理,这些版本的实践也为LaunchWrapper等库的设计奠定了基础。
Forge从FML CoreMod起源开始就没有提供文档与教程,我们需要自行探索。
在RelaunchLibraryManager 中,FML进行了如下操作:
discoverCoreMods
方法中,FML会遍历coremods
文件夹中的所有jar
Copy File coreMods = setupCoreModDir(mcDir) ;
FilenameFilter ff = new FilenameFilter()
{
@ Override
public boolean accept ( File dir , String name)
{
return name . endsWith ( ".jar" );
}
};
...
for ( File coreMod : coreModList)
随后读取jar中的Manifest,并判断是否存在FMLCorePlugin
Copy mfAttributes = jar . getManifest () . getMainAttributes ();
...
String fmlCorePlugin = mfAttributes . getValue ( "FMLCorePlugin" );
if (fmlCorePlugin == null )
{
FMLRelaunchLog . severe ( "The coremod %s does not contain a valid jar manifest- it will be ignored" , coreMod . getName ());
continue ;
}
将CoreMod加入ClassPath,并获得IFMLLoadingPlugin
实例加入列表loadPlugins
Copy classLoader . addURL ( coreMod . toURI () . toURL ());
...
Class < ? > coreModClass = Class . forName (fmlCorePlugin , true , classLoader);
...
IFMLLoadingPlugin plugin = (IFMLLoadingPlugin) coreModClass . newInstance ();
loadPlugins . add (plugin);
handleLaunch
方法(长到可怕)中,遍历loadPlugins
,并调用getASMTransformerClass
读取IClassTransformer
的类,并加入到RelaunchClassLoader
的transformers
中
Copy for ( IFMLLoadingPlugin plug : loadPlugins)
{
if ( plug . getASMTransformerClass () != null )
{
for ( String xformClass : plug . getASMTransformerClass ())
{
actualClassLoader . registerTransformer (xformClass);
}
}
}
当一个类被加载时,RelaunchClassLoader
将会进行如下操作:
在findClass
方法中,通过runTransformers
间接逐个调用transforms
Copy byte [] transformedClass = runTransformers(untransformedName , transformedName , basicClass) ;
Copy for ( IClassTransformer transformer : transformers)
{
basicClass = transformer . transform (name , transformedName , basicClass);
}
制作方法
(以下代码适用于1.3.2-1.5.2)
通过分析,我们确定了三个关键信息——Manifest、IFMLLoadingPlugin
与IClassTransformer
,以下逐个介绍。
Manifest
Manifest有清单的意思,在这里指的是jar中的META-INF/MANIFEST.MF
文件,需要在这个文件中加入如下内容:
Copy FMLCorePlugin: com.example.ExamplePlugin
FMLCorePlugin
属性指定了IFMLLoadingPlugin
的类名
IFMLLoadingPlugin
IFMLLoadingPlugin
是FML提供的一个接口,需要实现以下几个方法:
getLibraryRequestClass
,返回依赖类
getASMTransformerClass
,返回IClassTransformer
的完整类名构成的数组,这是CoreMod的关键
getSetupClass
,返回IFMLCallhook
的实现类
injectData
,可以获得mcLocation
、coremodList
与coremodLocation
,分别是Minecraft文件夹File
、CoreMod列表List
、当前CoreMod文件File
。这个方法与IFMLCallhook
中的injectData
主要区别为这个方法是在Minecraft启动后调用的,可以操作Minecraft的class
同时,提供了以下几个注解来进行信息补充:
TransformerExclusions
,指定不被CoreMod修改的包名前缀数组,例如指定了com.example
后,com.example.something.Example
也不会被修改
MCVersion
,指定CoreMod适用的Minecraft版本
一个简单的IFMLLoadingPlugin
实现如下:
Copy package com . example ;
import java . util . Map ;
import cpw . mods . fml . relauncher . IFMLLoadingPlugin ;
public class ExamplePlugin implements IFMLLoadingPlugin {
@ Override
public String [] getLibraryRequestClass () {
return null ;
}
@ Override
public String [] getASMTransformerClass () {
return new String []{ "com.example.ClassTransformer" };
}
@ Override
public String getModContainerClass () {
return null ;
}
@ Override
public String getSetupClass () {
return null ;
}
@ Override
public void injectData ( Map < String , Object > data) {
}
}
IClassTransformer
IClassTransformer
是FML提供的接口,需要实现以下方法:
transform
,接收name
、transformedName
和bytes
三个参数,分别是原类名、mcp无混淆类名和class文件的byte
数组,需要返回修改后的class文件的byte
数组
需要特别注意的是:
name
原类名和bytes
class文件,在游戏运行时为notch混淆,开发环境测试时为mcp混淆
bytes
可能已被其他CoreMod甚至Forge本身修改过
切记无论如何都要返回一个有效的byte
数组,否则会导致ClassNotFoundException
、NoClassDefFoundError
等导致的崩溃
一个没有对class进行任何修改的IClassTransformer
实现如下:
Copy package com . example ;
import cpw . mods . fml . relauncher . IClassTransformer ;
public class ClassTransformer implements IClassTransformer {
public byte [] transform ( String name , String transformedName , byte [] bytes) {
return bytes; //特别注意需要返回bytes
}
}
一个对net.minecraft.src.GuiPlayerTabOverlay
的a
(notch)方法进行修改的实例:
Copy package com . example ;
import cpw . mods . fml . relauncher . IClassTransformer ;
import org . objectweb . asm . ClassReader ;
import org . objectweb . asm . ClassWriter ;
import org . objectweb . asm . tree . ClassNode ;
import org . objectweb . asm . tree . MethodNode ;
public class ClassTransformer implements IClassTransformer {
public byte [] transform ( String name , String transformedName , byte [] bytes) {
if ( ! "net.minecraft.src.GuiPlayerTabOverlay" . equals (transformedName))
return bytes;
//使用ASM读入bytes
ClassReader cr = new ClassReader(bytes) ;
ClassNode cn = new ClassNode() ;
cr . accept (cn , 0 );
//遍历methods
for ( MethodNode mn : cn . methods ) {
if ( ! "a" . equals ( mn . name ))
continue ;
//TODO: 在这里进行ASM操作
}
//返回修改后的bytes
ClassWriter cw = new ClassWriter( ClassWriter . COMPUTE_FRAMES | ClassWriter . COMPUTE_MAXS ) ;
cn . accept (cw);
return cw . toByteArray ();
}
}