Minecraft Modding

コードが書きたい。

MinecraftForgeでJavassistを使いたい

目次

 

概要

Mod開発時に既存クラスを変更したいなと調べているうちにForge側で方法が用意されていることを知ったが、ASMを利用する方法でちんぷんかんぷんだったのでJavassistを使いたいなとやってみました。

当初公開していたコードが全く!javassist使っていないことに気付いてコードを修正しました。参考にならないコードを載せてしまってすみません。

 

今回のコード

build.gradleの一部抜粋

dependencies {
    //compile group: 'org.javassist', name: 'javassist', version: '3.15.0-GA'
    // 上記のバージョンだとJava8で落ちてしまうようで最新のものにしました
    compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'
}
//manifestファイルの書き込みをビルドに追加する。これにより、coremodの読み込みが可能になる。
//なお、テスト起動時にはこのオプションが効かないためVMオプションに以下の引数を追加する。
//-Dfml.coreMods.load=coremodexample.asm.CoreModExamplePlugin
jar {
    manifest {
        //coremodのパスを指定する。
        attributes FMLCorePlugin: 'coremodexample.asm.CoreModExamplePlugin'
        //今回はAluminiummodが別で入っているためそれも読み込む。
        attributes FMLCorePluginContainsFMLMod : 'true'
    }
}

 

CoreModExamplePlugin.java

package coremodexample.asm;

import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;

import java.util.Map;

/**
 * クラス書き換え用のコアクラス。
 * Modアノテーションを付けているクラスとは別に必要なことに注意。
 */
@IFMLLoadingPlugin.TransformerExclusions({"coremodexample.asm"})
public class CoreModExamplePlugin implements IFMLLoadingPlugin {
    //書き換え機能を実装したクラス一覧を渡す関数。書き方はパッケージ名+クラス名。
    @Override
    public String[] getASMTransformerClass() {
        return new String[]{"coremodexample.asm.CoreModExampleTransformer"};
    }

    //あとは今回は使わない為適当に。
    @Override
    public String getSetupClass() {
        return null;
    }

    @Override
    public void injectData(Map<String, Object> data) {
    }

    @Override
    public String getAccessTransformerClass() {
        return null;
    }

    @Override
    public String getModContainerClass() {
        return null;
    }
}

 

CoreModExampleTransformer

package coremodexample.asm;

import javassist.ClassPool;
import net.minecraft.launchwrapper.IClassTransformer;

public class CoreModExampleTransformer implements IClassTransformer {
    @Override
    public byte[] transform(final String name, final String transformedName, byte[] baseClass) {
        // javassist系のクラスを除外しないと循環してしまう
        if(name.matches("javassist.*")) { return baseClass; }

        // 全てのブロックのコアクラスだけ処理する
        if(!name.equals("net.minecraft.block.Block")) { return baseClass; }

        // 読み込むクラス全て?に対して実行される為出力が量産される
        System.out.println("CoreModExample transform.");
        System.out.println("name :" + name);
        System.out.println("transformedName :" + transformedName);

        try {
            return this.patchClass(name, transformedName, baseClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return baseClass;
    }

    /*
    修正前
    private byte[] patchClass(String name, String transformedName, byte[] baseClass) {
        ClassPool cp = ClassPool.getDefault();

        return baseClass;
    }
    */
    
    /**
     * クラス書き換える。
     * @param name 絶対パスのクラス名
     * @param transformedName 分からない
     * @param baseClass クラスのバイナリ
     * @return 書き換え後クラスのバイナリ
     */
    private byte[] patchClass(String name, String transformedName, byte[] baseClass) throws NotFoundException, IOException, CannotCompileException {
        ClassPool classPool = ClassPool.getDefault();

        // バイトコードからクラス定義を読み込む
        InputStream inputStream = new ByteArrayInputStream(baseClass);
        CtClass ctClass = classPool.makeClass(inputStream);

        // フィールドの追加
        ctClass.addField(CtField.make("private static int counter = 0;", ctClass));

        // コンストラクタの読み込み
        CtConstructor ctConstructor = ctClass.getDeclaredConstructor(new CtClass[] {
                classPool.get("net.minecraft.block.material.Material"),
                classPool.get("net.minecraft.block.material.MapColor")
        });

        // コンストラクタの実装の文末に処理を追加する
        // 本当はブロック名を表示したかったが、コンストラクタで名称を読んでいないので出来なかった
        ctConstructor.insertAfter("System.out.println(\"This Counter : \" + counter);");
        ctConstructor.insertAfter("counter++;");

        return ctClass.toBytecode();
    }
}

 

課題
  • JarをModsに入れても読み込まれない

 

終わりに

久方ぶりの投稿でご無沙汰していました。Mod開発の中で新しいブロックを追加するよりも既存の機能をちょっと書き換えた方が自然なんじゃないかと思うことがあってやり方を調べていました。Javaのリフレクションの機能からASM、Javassistと調べて今回のようになりました。

最初はASMでやろうと思ったのですが、バイトコードがどうとかさっぱり頭に入らず鬱々しながら英語の公式サイトを眺めていてたまらずJavassistに逃避しました。

あまりにも投稿していなかったので死滅するかと思いましたが投稿出来てほっこりする思いです。はー楽しかった。