所谓的coremod,是forge提供的代码注入机制,用来方便开发者修改原版代码或者控制mod加载用的,要实现coremod,首先应该实现IFMLLoadingPlugin接口:
public class WarhammerLoader implements IFMLLoadingPlugin {
public String[] getASMTransformerClass(){
//代码注入需要用到asm,你的asm操作一般在IClassTransformer中进行,这个类用来获取实现了IClassTransformer的类
//LaunchClassLoader.registerTransformer()中IClassTransformer的实现会被ClassLoader加载
return new String[]{
"com.forestbat.warhammer.asm.ChebishevTransformer","com.forestbat.warhammer.asm.ParentTransformer"
};
}
public String getModContainerClass(){
//用来获取实现了ModContainer的类
return "com.forestbat.warhammer.asm.WarhammerModContainer";
}
public String getSetupClass(){
//用来获取实现了IFMLCallHook的类
return null;
}
public void injectData(Map<String,Object> data){
//注入一些重要的数据,例如MC路径
data.put("mcLocation", File mcDir);//MC路径
data.put("coremodList", List loadPlugins);//coremod表单
data.put("runtimeDeobfuscationEnabled", !deobfuscatedEnvironment);//确认运行时反混淆开启
data.put("coremodLocation", File location);//coremod路径
}
public String getAccessTransformerClass()
{
//AccessTransformer用来改变类的访问权限,例如从public改成private,forge已经自带
return "net.minecraftforge.fml.common.asm.transformers.AccessTransformer";
}
}
getASMTransformerClass()
众所周知,Optifine的快速渲染是使用了自己的sin和cos算法,但可惜optifine是不开源的,那我们能不能实现自己的算法,以图对原版的算法实现优化呢?答案是肯定的。
在这里跟大家介绍一下切比雪夫逼近:切比雪夫多项式(百度百科)
比起泰勒公式,切比雪夫的优势在于可以以更低的次数逼近目标函数,降低运算量。出于简洁,使用第一类切比雪夫多项式去逼近sin(x),也就是sin(x)=aT0(x)+bT1(x)+cT2(x)+……这样的形式。
为了保证精度,我们选用六次切比雪夫逼近式,可以达到误差限4e-7,远高于原版算法的精度1e-6级别,最后的公式如下:
- <font color="Black">sin = (((((-0.000960664 * value + 0.0102697866) * value - 0.00198601997) * value - 0.1656067221)
- * value - 0.0002715666) * value + 1.000026227) * value;
- //value是传入的弧度参数,范围为(0,π/2),其他角度都可以通过诱导公式变过来
- </font>
好的,式子得到了,那么是时候将其置入游戏体验一下了。
如果你只是给自己的mod用一下,就不会有接下来的东西,直接声明一个static方法调用即可;如果你像我一样的“大公无私”,想让原版游戏的算法也变成这样大家一起用,下面的内容或许就很重要了。
准备好的了话,请看下面↓↓↓或这里
- <font color="Black">public class ChebishevTransformer implements IClassTransformer {
- //IClassTransformer:MC自带的launchwrapper工具所提供的类转换接口
- @Override
- public byte[] transform(String name, String transformedName, byte[] basicClass) {
- if (transformedName.equals("net.minecraft.util.math.MathHelper")) {
- FMLLog.log.warn("[Transforming...]");
- //在这里输出一个告警信息,告诉大家你在做什么,可选项
- ClassNode classNode=new ClassNode(ASM5);
- //声明一个ASM5的classNode
- ClassReader classReader=new ClassReader(basicClass);
- //声明一个读取basicClass的classReader
- classReader.accept(classNode,0);
- //让classReader接收到要修改的classNode/classVisitor,以产生事件
- List<MethodNode> methodNodeList=new ArrayList<>(classNode.methods);
- //由于我们的目标是要修改方法,因此要遍历一下MethodNode,将其保存在list中
- for(MethodNode methodNode:methodNodeList)
- if (methodNode.name.equals("func_76126_a(F)F")) {
- //上文的transformedName是转换后的类名,因为你看到的代码是根据notch name反混淆出来的,methodName是它的srg name, //(F)F指参数为float,返回float
- methodNode.instructions.clear();
- //清除methodNode下的结构
- methodNode.instructions.add(new InsnNode(FLOAD))
- //添加你自己的实现,FLOAD代表传入float参数,其在ASM中规定的操作码(Opcode)是23
- methodNode.instructions.add(new MethodInsnNode
- (ACC_PUBLIC + ACC_STATIC, "Lcom/forestbat/warhammer/asm/ChebishevTransformer", "sin", "(F)D", false));
- //添加上你自己的实现,ACC_PUBLIC,ACC_STATIC是ASM中public和static的操作码(分别是0x0001和0x0008);
- //“Lcom/forestbat/warhammer/asm/ChebishevTransformer”是owner参数,表明方法的源头类;
- //“sin”是name参数,表明我们的方法名;
- //“(F)D”是“float形参,返回double”的description(描述符);
- //false表明了owner类(不是)一个接口,若是,则为true
- methodNode.instructions.add(new InsnNode(DRETURN));
- //DRETURN代表返回double值,其操作码是175
- }
- ClassWriter classWriter = new ClassWriter(2);
- //声明一个classWriter写入内容,参数2的意义是COMPUTE_FRAMES,自动计算局部变量与操作数栈
- //每当方法被调用一次栈上就会生成一个帧,帧又被分为局部变量和操作数栈,COMPUTE_FRAMES会帮你自动计算,但会拖慢性能
- classNode.accept(classWriter);
- //classNode接收classWriter做参数,向方法内部生成二进制代码
- return classWriter.toByteArray();
- }
- return basicClass;
- //返回被修改的basicClass
- }
- </font>
public static double sin(float value){
if(value>=0&&value<=PI/2)
return (((((-0.000960664 * value + 0.0102697866) * value - 0.00198601997) * value - 0.1656067221)
* value - 0.0002715666) * value + 1.000026227) * value;
if(value<0){
return -sin(-value);
}
else{
return sin((float)(value%PI));
}
}
首先解释下,为什么basicClass的类型是byte[]——因为basicClass在这里指的是未加载的类字节码,字节码本质上还是一串字节,因此类型是byte[]。
asm修改字节码的步骤是固定的,ClassReader读取basicClass(使之可被修改),接收ClassNode(来自asm的tree api)/ClassVisitor(来自asm的core api),而ClassNode/ClassVisitor又会接受ClassWriter,将开发者的改动通过ClassWriter写入Class中,最后返回修改后的basicClass。
tree api的性能低于core api,我们不妨康康core api的写法:
public class ParentTransformer implements IClassTransformer {//一个修改父类的轮子
private static String parentName;
private static String[] baseInterfaces;
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if(baseInterfaces!=null && parentName!=null){
ClassReader classReader = new ClassReader(basicClass);
ClassWriter classWriter = new ClassWriter(2);//2代表自动计算栈帧
ClassVisitor classVisitor = new ClassVisitor(ASM5, classWriter) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(52, access, transformedName, null, null, null);
//52代表Java8,access代表public/private/protected,superName即父类,由于我们要修改父类,因此原类不应有父类,否则会发生错误
}
};
classReader.accept(classVisitor, 2);
classWriter.visit(52, 0x0001, transformedName, null,
setParentName(transformedName, parentName), setInterfacesName(transformedName, baseInterfaces));
classWriter.toByteArray();
return basicClass;
}
else return basicClass;
}
public static String setParentName(String transformedName,String parentName){
ParentTransformer.parentName=parentName;
return parentName;
}
public static String[] setInterfacesName(String transformedName,String[] baseInterfaces) {
ParentTransformer.baseInterfaces=baseInterfaces;
for (String baseInterface : baseInterfaces) {
try {
ClassReader classReader = new ClassReader(baseInterface);
if (classReader.getItemCount() == 0)
return baseInterfaces;
}catch (IOException e){FMLLog.bigWarning("No any interfaces!");}
}
return null;
}
}
getModContainerClass()
每一个被@Mod注解标记从而被认为是mod的mod,都会有自己的ModContainer,如无意外将会在FMLModContainer类中进行处理,读取@Mod注解中的信息,但是开发者也可以自己改写ModContainer。
public class YourModContainer implements ModContainer {
private ModMetadata modMetadata;
private boolean enabled=true;
private VersionRange versionRange;
private ModContainer modContainer;
public String getModId(){
return MOD_ID;
}
public String getName(){
return MOD_NAME;
}
public String getVersion(){
return VERSION;
}
public File getSource(){
Path path= Paths.get(".minecraft","mods",getName());
return path.toFile();
}
public ModMetadata getMetadata(){
return modMetadata; //ModMetaData就是mod的“标签”,由@Mod注解信息决定,也可以由玩家自己编写
}
@Override
public void bindMetadata(MetadataCollection mc) {
try {
ModMetadata metadata = mc.getMetadataForId("Gregtech", Maps.newConcurrentMap());
if (metadata != null & metadata.dependencies.contains("ic2"))
getUpdateUrl().openConnection();
}catch (IOException e){LOGGER.info("IC2 WebSite Error!");}}
@Override
public void setEnabledState(boolean enabled) {
this.enabled=enabled;
}
public Set<ArtifactVersion> getRequirements(){
return modMetadata.requiredMods;
}
public List<ArtifactVersion> getDependencies(){
return modMetadata.dependencies;
}
public List<ArtifactVersion> getDependants(){
return null;
}
@Override
public boolean registerBus(EventBus bus, LoadController controller) {
bus.unregister(ForgeRegistries.BIOMES);//从EventBus移除所有生物群系,当然这么做很扯淡
controller.getActiveModList().remove("titan");//从活跃加载mod列表中移除泰坦,换句话说,禁用了泰坦
return true; }
@Override
public boolean shouldLoadInEnvironment() {
return false;
}
@Override
public String getSortingRules() {
//forge使用的是拓扑排序(TopologicalSort),当然你可以写一个自己的
return “com.yourmod.yourSortList";
}
@Override
public boolean matches(Object mod) {
return mod.equals(YourMod.INSTANCE);
}
@Override
public Object getMod() {
return YourMod.INSTANCE;
}
@Override
public ArtifactVersion getProcessedVersion() {
return new DefaultArtifactVersion(YourMod.VERSION);
}
public boolean isImmutable(){
return false;
}
@Override
public String getDisplayVersion() {
return YourMod.VERSION;
}
@Override
public VersionRange acceptableMinecraftVersionRange() {
try {
versionRange=VersionRange.createFromVersionSpec("[1.12,1.12.2]");//限定你的mod版本范围是1.12-1.12.2
}catch (InvalidVersionSpecificationException e){FMLLog.bigWarning("No 1.12!");}
return versionRange;
}
@Nullable
@Override
public Certificate getSigningCertificate() {
Certificate[] certificates = getClass().getProtectionDomain().getCodeSource().getCertificates();
return certificates != null ? certificates[0] : null;
}
@Override
public Map<String, String> getCustomModProperties() {
return null;
//向description中添加自己的信息,forge本身不一定会作出处理,这一部分由开发者自行处理,与下文descriptor并不一样
}
@Override
public Class<?> getCustomResourcePackClass() {
return null;
}
@Override
public Map<String, String> getSharedModDescriptor() {Map<String, String> descriptor = Maps.newHashMap();
descriptor.put("modsystem", "FML");
descriptor.put("id", getModId());
descriptor.put("version", getDisplayVersion());
descriptor.put("name", getName());
descriptor.put("url", modMetadata.url);
descriptor.put("description", modMetadata.description);
return descriptor;
}
public Disableable canBeDisabled(){
return Disableable.NEVER;//设置其不会被禁用
}
@Override
public String getGuiClassName() {
return null;
}
@Override
public List<String> getOwnedPackages() {
return null;
}
@Override
public URL getUpdateUrl() {
try {
return new URL("https://minecraft.curseforge.com/projects/botania/files/2524591");//方便所以打了个botania的更新链接
}catch (MalformedURLException e){}
}
@Override
public int getClassVersion() {
return 52;//52代表Java8
}
public void setClassVersion(int version){
}
}
getSetupClass()
由于IFMLCallHook这个接口的调用时间非常早,类似于.NET Core的startup接口,所以这里不应该出现MC本身的代码(因为MC代码的反混淆还在IFMLCallHook之后)
public class WarhammerHook implements IFMLCallHook {
@Override
public void injectData(Map<String, Object> data) {
liveEnv = (Boolean)data.get("runtimeDeobfuscationEnabled");
cl = (LaunchClassLoader) data.get("classLoader");
File mcDir = (File)data.get("mcLocation");
fmlLocation = (File)data.get("coremodLocation");
ClassPatchManager.INSTANCE.setup(FMLLaunchHandler.side());
FMLDeobfuscatingRemapper.INSTANCE.setup(mcDir, cl, (String) data.get("deobfuscationFileName")); }
@Override
public Void call() throws Exception {
Class<?> classTitan=Class.forName("titan(or its obfuscate name)",false, (LaunchClassLoader)cl) //LaunchClassLoader是MC自带的类加载器
CodeSource codeSource = classTitan.getProtectionDomain().getCodeSource();
Files.delete(Paths.get(codeSource.getLocation().toURI())); //探测到泰坦所在的类后删除它
}
}
最后
我们的东西已经写完了,应该加载到游戏中使之生效。
打开你的build.gradle文件,向里面写入这样一段:
- jar {
- manifest {
- //实现了IFMLLoadingPlugin的类(你的coremod类)
- attributes 'FMLCorePlugin': 'com.forestbat.warhammer.asm.WarhammerLoader'
- attributes 'FMLCorePluginContainsFMLMod': 'true'
- //access transformer配置文件,一般是modid_at.cfg,放在resources/META_INF文件夹下
- attributes 'FMLAT': 'warhammer_at.cfg'
- //https://github.com/Ipsis/Woot/blob/1_12/src/main/resources/META-INF/woot_at.cfg(woot的AT配置文件)
- }
- }
———————————————————————— 分割线 ——————————————————————————
如有需要继续更新ITweaker相关
[groupid=128]HAYO Studio[/groupid]