提供: Minecraft Modding Wiki
移動先: 案内検索
(概要)
 
(11人の利用者による、間の44版が非表示)
1行目: 1行目:
 
 
{{前提MOD|reqmod="ForgeModLoader"}}
 
{{前提MOD|reqmod="ForgeModLoader"}}
  
ここでは、coremods用modのASM機能を使用するmod作成方法を紹介します。<br />
+
ここでは、coremodsフォルダに入れるタイプのmod作成方法を紹介します。
  
 
==概要==
 
==概要==
*ASMとは、Java動的ByteCode操作ライブラリの一つです。<br />
+
 
通常コンパイル済みのClassファイルの内容は変更できません。<br />
+
===Coremodとは===
ですが、JavaにClassファイルを読み込む機能ClassLoaderを乗っ取り、ASMにより動的にClassファイルに変更を施すことで<br />
+
通常 Minecraft.jar 内に上書きが必要な変更を施すような 前提MOD(API系)や既存書き換え系MODなどを、coremods フォルダに入れるだけでインストールできるようにする、FMLの機能です。
Minecraft.jar内(※Coremod部分以外のFMLコードも含む)および、mods内のzip等のClassファイルを直接上書きすることなく改変することを可能とします。<br />
+
 
 +
ただし、既存書き換えのMODをそのまま coremod にすることは出来ません。
 +
Mod作者自身が coremod として作成する必要があります。
 +
 
 +
===仕組み===
 +
FMLでは、Minecraftが起動した直後に処理を割り込ませ、現在のスレッドの ContextClassLoader に、net.minecraft.launchwrapper.LaunchClassLoader を設定しています。このクラスローダーでは、クラスロード時にバイトコードのクラスを編集するポイントが設けられており、Minecraftが実行中に読み込む殆どのクラス(※)を、ロード時に動的に改変する事が可能となっています。
 +
 
 +
※全てのクラスの変換が行えるわけではありません。IClassTransformer を通さないよう、変換対象から除外登録されている一部のパッケージ以下のクラスは、本来のクラスローダーであるシステムクラスローダーによりロードされてしまうため、動的な変換処理を行えません。またプラグイン側で TransformerExclusions アノテーションを用い除外設定されているパッケージやクラスも変換対象外となります。
 +
 
 +
詳細は RelaunchClassLoader の実装を確認するか、実際に試してみて判断ください。また、クラスローダーそのものの仕組みについては、Web検索で多くの情報を得る事が出来ますのでここでは割愛します。
 +
 
 +
当チュートリアルでは、主にこのクラス変換機能の実装方法を解説します。
 +
 
 +
この機能を用いることで、Minecraftの実行中に、初めてクラスがロードされた際に、クラスのバイトコードを置換、または部分的に書き換える事ができるようになります。動的に書き換えるため、Minecraft.jar 内(※Coremod部分以外のFMLコードも含む)および、mods 内の zip 等のClassファイルを、直接上書きして変更することなく改変することを可能とします。
 +
 
 +
====動的なクラス書き換えって、重くないの?====
 +
クラスのロードは、基本的に最初にクラスの参照が要求された際に、1回だけ行われます。一度ロードされたクラスは、通常どおりコンパイルされたclassファイルとなんら違いはありません。
 +
 
 +
つまり、施した改変内容以上のパフォーマンスへの影響は、ほぼ無いと考えて差し支えありません。
 +
 
 +
===ASMライブラリ===
 +
ASMライブラリとは、クラスのバイトコードに対し、動的に変更を施すことできるバイトコード操作ライブラリのことです。
 +
 
 +
FMLにバンドルされているので、別途ライブラリを添付することなく、FMLをインストールするだけで利用が可能です。
 +
 
 
本チュートリアルでは、最低限の機能のみの実装のみを紹介するため、ASMライブラリ等の使い方については別途検索ください。
 
本チュートリアルでは、最低限の機能のみの実装のみを紹介するため、ASMライブラリ等の使い方については別途検索ください。
  
なお、別にASMをかならず使う必要は無く、読み込むファイルを丸ごと差し替える事も出来るため<br />
+
==実装==
今までMinecraft.jarに投入するタイプのModをcoremods形式にしてインストールし易くするということも出来ます。
+
 
 +
今回作成するソースファイルは以下になります。
 +
 
 +
;tutorial/asm/TutorialCorePlugin.java
 +
:coremods読み込みの基点となります。
  
*※改変を施す部分のコードは、仮実装でありそのままでは動きません。<br />
+
;tutorial/asm/TutorialModContainer.java
ソースコメントにしたがって必要な実装を施して書き換えましょう。
+
:ModLoaderにおけるmod_XX.classのバージョン情報等のみを格納するものです。
 +
:FMLでは情報を格納するのにアノテーションや mcmod.info ファイルを使うこともできます
 +
:しかし、Coremodは読み込み方法が異なるので記載も別となっています。
  
*※パフォーマンスへの影響<br />
+
;tutorial/asm/TutorialTransformer.java
この機能によってロードされたクラスは、ロードは原則1回しか行われませんし<br />
+
:Classの改変機能を実装します。
完了すれば通常どおりコンパイルされたclassファイルとなんら違いはありません。<br />
 
施した改変内容以上のパフォーマンスへの影響はほぼ無いです。
 
  
==ソースの解説==
+
;META-INF/MANIFEST.MF
 +
:ソースファイルではありませんが、FMLがcoremodである事を認識するのに必要です。
  
作成するソース<br />
+
※改変を施す部分のコードは、仮実装でありそのままでは動きません。
TutorialCorePlugin.java:coremods読み込みの基点となります。<br />
+
ソースコメントを参照し、必要な実装を施して書き換えましょう。
TutorialModContainer.java:ModLoaderにおけるmod_XX.classのバージョン情報等のみを格納するものです。<br />
 
TutorialTransformer.java:Classの改変機能を実装します。<br />
 
META-INF/MANIFEST.MF:coremod用jarにするために必要です。
 
  
 
===TutorialCorePluginクラス作成===
 
===TutorialCorePluginクラス作成===
**CorePluginクラスを作成します。
 
  
mod_Tutorialソース
+
# 任意の名前のクラス(ここではTutorialCorePlugin)を作成します。
 +
# IFMLLoadingPlugin、<strike>IFMLCallHook</strike> を実装します。
 +
 
 +
net.minecraftforge.fml.relauncher.IFMLLoadingPlugin は、このクラス自体に実装する必要はありません。
 +
getSetupClass() メソッドで返される名前のクラスが、IFMLCallHookを実装している必要があります。
 +
なお、本チュートリアルでは、コールフックを使用していないため、getSetupClass()メソッドの戻り値は null としています。 
 +
 
 
<source lang="java">
 
<source lang="java">
package tutorial.asm;
+
// パッケージは、クラス(ファイル名)の衝突を回避するために、汎用的ではないユニークなパッケージ名を使用しましょう。
//tutorial : 独自のパッケージ名を付けられます。
+
// 例) 作者名、ドメイン など (一意性のあるものが好ましい)
//主に他の開発者とファイル名の衝突を避けるために利用します(tutorial.abc.asm等)
+
//  
//asm : ASM機能を使うクラスを配置する場合の慣例です。解りやすくする以外の意味はありません。
+
// ここでは便宜上 tutorial.asm パッケージとしています。
 +
// asm は ASM機能を使うクラスを配置する場合の慣例ですが、解りやすくする以外の意味はなく、必ずこうしないといけないわけではありません。
 +
package tutorial.asm;
  
 
import java.io.File;
 
import java.io.File;
 
import java.util.Map;
 
import java.util.Map;
  
 +
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
 +
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;
  
import cpw.mods.fml.relauncher.IFMLCallHook;
+
// TransformerExclusions: Transformerから除外するクラス名を設定するためのアノテーション
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
+
//
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;
+
//  値は文字列の配列で、複数指定も可能です。
 +
//  指定した文字列と前方一致するクラス名は、後述のクラスの動的な変換処理から除外されます。
 +
//  例えば、自身のクラスが変換されないように、自身のパッケージ以下を除外指定する、などが出来ます。
 +
//  必須ではありません。必要に応じて設定してください。
 +
//  本チュートリアルでは、参考として自身のパッケージを変換処理から除外しています。
 +
//
 +
// IFMLLoadingPlugin: Coremods としてロードするために必要なインタフェース
  
//TransformerExclusions.value:coremodsでロードする際に参照されるためパッケージ名と一致させてください。
+
@TransformerExclusions({"tutorial.asm"})
//IFMLLoadingPlugin:Coremodsの基礎インタフェース
+
public class TutorialCorePlugin implements IFMLLoadingPlugin
//IFMLCallHook:Coremods内で、coremod自身のパス等を取得する等の際に必要となります。
+
{
@TransformerExclusions(value={"tutorial.asm"})
+
    // coremod の jar ファイルのパス抽象表現を保持します。
public class TutorialCorePlugin implements IFMLLoadingPlugin, IFMLCallHook
+
    // Transformer 以外から呼ばれることは考慮しないため、デフォルトのアクセス指定子としています。
  
     //coremod自身のファイルパスの保存用
+
     static File location;
public static File location;
+
 
      
+
     // このプラグインが動作するために必要となるライブラリセットのクラス名の配列です。
     //今回は使用しません
+
     // 本チュートリアルでは使用しないため説明は割愛します。
@Override
+
    // インターフェイスの javadocや、FMLCorePlugin クラスの実装を参照してみてください。
public String[] getLibraryRequestClass()
+
 
{
+
    @Override
 +
    public String[] getLibraryRequestClass()
 +
    {
 
         return null;
 
         return null;
}
+
    }
  
     //Classの改変機能を実装したクラスの完全修飾名を返します。
+
     // Class の改変機能を実装したクラスの完全修飾名の配列を返します。
@Override
+
    // 本チュートリアルの変換処理クラスは TutorialTransformer のみなので、一つだけを配列に詰め返却しています。
public String[] getASMTransformerClass()
 
{
 
return new String[]{
 
"tutorial.asm.TutorialTransformer"};
 
}
 
  
     //coremodの名前やバージョン情報の格納クラスの完全修飾名を返します。
+
     @Override
@Override
+
    public String[] getASMTransformerClass()
public String getModContainerClass()
+
    {
{
+
        return new String[]{"tutorial.asm.TutorialTransformer"};
return "tutorial.asm.TutorialModContainer";
+
    }
}
+
 
 +
    // coremod の名前やバージョン情報を格納しているクラスの完全修飾名を返します。
  
     //coremods読み込みの基点クラスの完全修飾名を返します。
+
     @Override
@Override
+
    public String getModContainerClass()
public String getSetupClass()
+
    {
{
+
         return "tutorial.asm.TutorialModContainer";
         return "tutorial.asm.TutorialCorePlugin";
+
    }
}
+
 
 +
    // IFMLCallHook を実装しているクラス名を返す必要があります。
 +
    // 本チュートリアルでは、コールフックを用いないため、こちらの説明も割愛します。
 +
 
 +
    @Override
 +
    public String getSetupClass()
 +
    {
 +
        return null;
 +
    }
  
     //IFMLCallHookのメソッドです。
+
     // IFMLLoadingPlugin のメソッドです。(IFMLCallHook にも同じシグネチャーのメソッドがありますが、違います)
     //今回はCoremod自身のjarファイルパスを取得します。
+
     // 今回は coremod 自身の jar ファイルパスを取得しています。これは後述のトランスフォーマークラスで、
@Override
+
    // jarから置換用クラスを取得しているためで、そのような処理を行わないのであれば何も実装しなくても構いません。
public void injectData(Map<String, Object> data)
+
    //
{
+
    // なお、IFMLLoadingPlugin のメソッドとして呼ばれた際は、"mcLocation"、"coremodList"、"coremodLocation" の3つ、
if(data.containsKey("coremodLocation"))
+
    // IFMLCallHook のメソッドとして呼ばれた際は、"classLoader" がマップに設定されています。(FML#511現在)
location = (File) data.get("coremodLocation");
+
    //
}
+
    // 渡されるマップの中身は、net.minecraftforge.fml.relauncher.RelaunchLibraryManager の実装からも確認する事が出来ます。
  
@Override
+
    @Override
public Void call() throws Exception
+
    public void injectData(Map<String, Object> data)
{
+
    {
return null;
+
        if (data.containsKey("coremodLocation"))
}
+
        {
 +
            location = (File) data.get("coremodLocation");
 +
        }
 +
    }
 
  }
 
  }
 
</source>
 
</source>
105行目: 154行目:
 
===TutorialModContainerクラス作成===
 
===TutorialModContainerクラス作成===
  
**ModContainerクラスを作成します。
+
# 任意の名前のクラス(ここではTutorialModContainer)を作成します。
DummyModContainerクラスを継承して作成します。
+
# ModContainer を実装します。
 +
 
 +
Coremod 用のModでは、通常の mod読み込み処理より前にModContainerが要求されるため、MetaDataアノテーションや、mcmod.infoなど、MODのメタデータ設定処理を利用できません。
 +
 
 +
そのため、別の形式で記載することが必要になっています。
 +
 
 +
ID、名前、バージョン情報を変更できれば良いため、ここでは DummyModContainer クラスを継承して作成しています。
  
 
<source lang="java">
 
<source lang="java">
 
+
package tutorial.asm;
package tutorial.asm;
 
  
 
import java.util.Arrays;
 
import java.util.Arrays;
118行目: 172行目:
 
import com.google.common.eventbus.Subscribe;
 
import com.google.common.eventbus.Subscribe;
  
import cpw.mods.fml.common.DummyModContainer;
+
import net.minecraftforge.fml.common.DummyModContainer;
import cpw.mods.fml.common.LoadController;
+
import net.minecraftforge.fml.common.LoadController;
import cpw.mods.fml.common.ModMetadata;
+
import net.minecraftforge.fml.common.ModMetadata;
import cpw.mods.fml.common.event.FMLInitializationEvent;
+
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.versioning.ArtifactVersion;
+
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
 +
 
 +
// 必ずしも DummyModContainer を継承している必要はありません。
 +
// net.minecraftforge.fml.common.ModContainer さえ実装していれば、どんなクラスでも構いません。
 +
 
 
public class TutorialModContainer extends DummyModContainer
 
public class TutorialModContainer extends DummyModContainer
 
{
 
{
public TutorialModContainer()
+
    public TutorialModContainer()
 
     {
 
     {
 
         super(new ModMetadata());
 
         super(new ModMetadata());
        getMetadata();
 
    }
 
 
@Override
 
public List<ArtifactVersion> getDependencies()
 
{
 
return super.getDependencies();
 
}
 
  
@Override
+
        // 他のModと区別するための一意なIDやmodの名前など、MODのメタデータを設定します。
public ModMetadata getMetadata()
+
        ModMetadata meta = getMetadata();
{
 
//modの名前や、他のModと区別するための一意なID情報などを指定します。
 
ModMetadata meta = super.getMetadata();
 
  
 
         meta.modId      = "transformertutorial";
 
         meta.modId      = "transformertutorial";
150行目: 197行目:
 
         meta.url        = "";
 
         meta.url        = "";
 
         meta.credits    = "";
 
         meta.credits    = "";
 
+
         this.setEnabledState(true);
         return meta;
+
    }
}
+
    @Override
 
+
    public boolean registerBus(EventBus bus, LoadController controller)
@Override
 
public boolean registerBus(EventBus bus, LoadController controller)
 
{
 
bus.register(this);
 
return true;
 
}
 
 
 
@Subscribe
 
    public void init(FMLInitializationEvent event)
 
 
     {
 
     {
 +
bus.register(this);
 +
return true;
 
     }
 
     }
 
}
 
}
170行目: 210行目:
 
===TutorialTransformerクラス作成===
 
===TutorialTransformerクラス作成===
  
**Transformerクラスを作成します。
+
# 任意の名前のクラス(ここではTutorialTransformer)を作成します。
IClassTransformerインタフェースを実装します。
+
# IClassTransformer インタフェースを実装します。
  
 
<source lang="java">
 
<source lang="java">
package flammpfeil.everybodysnametag.asm;
+
package tutorial.asm;
 
 
import java.util.List;
 
  
 +
import java.io.IOException;
 
import java.io.InputStream;
 
import java.io.InputStream;
 
import java.util.List;
 
import java.util.List;
183行目: 222行目:
 
import java.util.zip.ZipFile;
 
import java.util.zip.ZipFile;
  
import org.objectweb.asm.ClassReader;
 
import org.objectweb.asm.ClassWriter;
 
import org.objectweb.asm.Opcodes;
 
import org.objectweb.asm.tree.ClassNode;
 
import org.objectweb.asm.tree.InsnList;
 
import org.objectweb.asm.tree.MethodInsnNode;
 
import org.objectweb.asm.tree.MethodNode;
 
import org.objectweb.asm.tree.VarInsnNode;
 
  
import cpw.mods.fml.relauncher.FMLRelauncher;
+
import net.minecraftforge.fml.relauncher.FMLRelauncher;
import cpw.mods.fml.relauncher.IClassTransformer;
+
import net.minecraftforge.fml.relauncher.IClassTransformer;
  
//Opcodes : インプリメントすると、ASMによるバイトコード定数にアクセスするのに便利です。
+
public class TutorialTransformer implements IClassTransformer
public class TutorialTransformer implements IClassTransformer , Opcodes
 
 
{
 
{
 +
    // 改変対象のクラスの完全修飾名です。
 +
    // 後述でMinecraft.jar内の難読化されるファイルを対象とする場合の簡易な取得方法を紹介します。
 +
    private static final String TARGET_CLASS_NAME = "net.minecraft.src.TargetClass";
  
     //クラスがロードされる際に呼び出されるメソッドです。
+
     // クラスがロードされる際に呼び出されるメソッドです。
    @Override
+
     public byte[] transform(final String name, final String transformedName, byte[] baseClass) {
     public byte[] transform(String name, byte[] bytes)
 
    {
 
        try
 
        {
 
          //改変対象のクラスの完全修飾名です。
 
          //後述でMinecraft.jar内の難読化されるファイルを対象とする場合の簡易な取得方法を紹介します。
 
          String targetClassName = "net.minecraft.src.target";
 
         
 
          //FMLRelauncher.side() : Client/Serverどちらか一方のを対象とする場合の判定などに使用できます。
 
          //今回は"CLIENT"と比較し、Client側のファイルを対象としている例です。
 
         
 
            //name : 現在ロードされようとしているクラス名が格納されています。
 
            if(FMLRelauncher.side().equals("CLIENT") && name.equals(targetClassName))
 
            {
 
  
              //--------------------------------------------------------------
+
        // FMLRelauncher.side() : Client/Server どちらか一方のを対象とする場合や、
              //クラスファイル丸ごと差し替える場合
+
        // 一つのMODで Client/Sever 両方に対応したMODで、この値を判定して処理を変える事ができます。
              //--------------------------------------------------------------
+
        // 今回は"CLIENT"と比較し、Client側のファイルを対象としている例です。
             
+
        // Client側専用のMODとして公開するのであれば、判定は必須ではありません。
              /*
 
             
 
              ZipFile zf = null;
 
              InputStream zi = null;
 
              try{
 
              zf = new ZipFile(TutorialCorePlugin.location);
 
             
 
              //差し替え後のファイルです。coremodのjar内のパスを指定します。
 
              ZipEntry ze = zf.getEntry("target.class");
 
             
 
              if(ze != null){
 
                zi = zf.getInputStream(ze);
 
                bytes = new byte[(int) ze.getSize()];
 
                zi.read(bytes);
 
              }
 
              }finally{
 
              if(zi != null)
 
                zi.close();
 
              if(zf != null)
 
                zf.close();
 
              }
 
             
 
              */
 
             
 
              //--------------------------------------------------------------
 
              //ASMを使用し、既存のクラスファイルに改変を施す場合。
 
              //今回のサンプルでは下記の想定で記述しています。
 
              //EntityLiving.classのdoRenderLivingの先頭に
 
              //tutorial/test.classのpassTestRender(EntityLiving,double,double,double)メソッドの呼び出しを追加する
 
              //--------------------------------------------------------------
 
             
 
              /*
 
                ClassNode cnode = new ClassNode();
 
                ClassReader reader = new ClassReader(bytes); //ASMで、bytesに格納されたクラスファイルを解析します。
 
                reader.accept(cnode, 0);
 
  
                MethodNode mnode = null;
+
        // name : 現在ロードされようとしているクラス名が格納されています。
                //改変対象のメソッド名です
+
        if (/*!FMLRelauncher.side().equals("CLIENT") || */!transformedName.equals(TARGET_CLASS_NAME))
                String targetMethodName = "doRenderLiving" ;
+
        {
                //改変対象メソッドの戻り値型および、引数型をあらわします
+
            // 処理対象外なので何もしない
                String targetMethoddesc = "(Lnet/minecraft/entity/EntityLiving;DDDFF)V";
+
            return bytes;
 +
        }
  
                //対象のメソッドを検索取得します。
+
        try
                for(MethodNode curMnode : (List<MethodNode>) cnode.methods)
+
        {
                    if(targetMethodName.equals(curMnode.name) && targetMethoddesc.equals(curMnode.desc)){
+
            // bytesに元のクラスの生情報が入っているので、ASMなどで処理し返す。
                        mnode = curMnode;
+
            return doSomething(bytes);
                        break;
 
                    }
 
                   
 
                if(mnode != null){
 
                    InsnList overrideList = new InsnList();
 
                   
 
                    //メソッドコールを、バイトコードであらわした例です。
 
                    overrideList.add(new VarInsnNode(ALOAD, 1));
 
                    overrideList.add(new VarInsnNode(DLOAD, 2));
 
                    overrideList.add(new VarInsnNode(DLOAD, 4));
 
                    overrideList.add(new VarInsnNode(DLOAD, 6));
 
                    overrideList.add(new MethodInsnNode(INVOKESTATIC, "tutorial/test", "passTestRender", "(LEntityLiving;DDD)V"));
 
 
 
                    //mnode.instructions.get(1)で、対象のメソッドの先頭を取得
 
                    //mnode.instructions.insertで、指定した位置にバイトコードを挿入します。
 
                    mnode.instructions.insert(mnode.instructions.get(1), overrideList);
 
 
 
                    //改変したクラスファイルをバイト列に書き出します
 
                    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
 
                    cnode.accept(cw);
 
                    bytes = cw.toByteArray();
 
                }
 
                */
 
            }
 
 
         }
 
         }
         catch(Exception e)
+
         catch (Exception e)
 
         {
 
         {
             throw new RuntimeException("failed : TutorialTransformer loading",e);
+
             throw new RuntimeException("failed : TutorialTransformer loading", e);
 
         }
 
         }
 
        return bytes;
 
 
     }
 
     }
 
}
 
}
 +
</source>
  
</source>
+
※1 詳しくは[http://asm.ow2.org/doc/faq.html#Q7 ここ]を参照
  
 
===META-INF/MANIFEST.MFファイルの作成===
 
===META-INF/MANIFEST.MFファイルの作成===
  
META-INF/MANIFEST.MFファイルを作成します。
+
# META-INF/MANIFEST.MFファイルを作成します。
TutorialCorePluginのクラス名を指定します。
+
# FMLCorePlugin節にTutorialCorePlugin の完全修飾クラスの名(パッケージ名を含む名前)を指定します。
 
 
 
<source lang="java">
 
<source lang="java">
 
Manifest-Version: 1.0
 
Manifest-Version: 1.0
312行目: 271行目:
 
</source>
 
</source>
  
==jarパッケージにまとめる==
+
上記の場合、jarに通常のFML(@Mod)クラスやmcmod.infoを含めてもロードされません。
前述で作成したclassを形式にまとめます。
+
通常のFMLクラスを含めたい場合はFMLCorePluginContainsFMLMod節を追加します。値はなんでもいいです。
coremods用のmodではjar形式でなければなりません。
+
この場合、FMLでロードしたいクラス(@Modが付加されたクラス)はTransformerクラス(このチュートリアルではasm)等とpackageが異なる必要があります。
 +
<source lang="java">
 +
FMLCorePluginContainsFMLMod: *
 +
</source>
 +
 
 +
==jarパッケージへのまとめ方==
 +
前述で作成したclassをjar形式にまとめます。<br />
 +
coremods用のmodではjar形式でなければ読み込まれません。
  
 
今回の例では下記のようなファイル構成となります。
 
今回の例では下記のようなファイル構成となります。
*tutorial.jar
+
* tutorial.jar
**META-INF
+
** META-INF
***MANIFEST.MF
+
*** MANIFEST.MF
**tutorial
+
** tutorial
***asm
+
*** asm
****TutorialCorePlugin.class
+
**** TutorialCorePlugin.class
****TutorialModContainer.class
+
**** TutorialModContainer.class
****TutorialTransformer.class
+
**** TutorialTransformer.class
**target.class (丸ごと差し替え例用)
+
** ModifiedTargetClass.class (丸ごと差し替える場合に用いるクラス(難読化済み))
 
    
 
    
通常のmodのように、zipで圧縮し、jarに拡張子を変更すれば完成です。
+
通常の mod のように、zip で圧縮し、拡張子を .zip から .jar に変更すれば完成です。
 +
 
 +
※jarコマンドを用いて作成することも可能ですが、今回は割愛します。
 +
 
 +
※1.8から
 +
*ワークスペース(Gradlew.batがあるフォルダ)
 +
** src
 +
*** main
 +
**** java
 +
***** tutorial
 +
******* asm
 +
******** TutorialCorePlugin.class
 +
******** TutorialModContainer.class
 +
******** TutorialTransformer.class
 +
**** resources
 +
***** ModifiedTargetClass.class (丸ごと差し替える場合に用いるクラス(難読化済み))
 +
ForgeGradleが勝手に1.7のようにやってくれます。
 +
 
 +
==テスト、デバッグ方法について==
 +
<del>coremod 形式の mod は、Eclipse でのデバッグで動作させるのは手間がかかります。</del>
 +
<del>機能の実装部分については、書き換え元のクラスを、直接改変して試したほうが簡単でしょう。</del>
 +
 
 +
<del>coremodとしての動作確認は、実際の動作環境へ放り込んで行ってください。</del>
 +
 
 +
通常 coremods フォルダーにある場合のみ coremod として読み込まれるため、作成した coremod にただクラスパスを通すだけでは読み込まれません。しかし、FMLには環境変数 fml.coreMods.load を用いた coremod の読み込み機能が用意されています。これを用いることで、任意のプラグインを coremod として読み込ませる事が可能です。
 +
 
 +
なお、環境変数に指定するのは割と不便なので、JVM起動時の引数としてVMに環境変数を追加して指定するほうが便利です。
 +
 
 +
やり方はとても簡単で、デバッグ実行する際のJVMの引数に、以下を追加します。
 +
 
 +
<source lang="dos">-Dfml.coreMods.load=完全修飾クラス名CSV</source>
 +
※-Dオプションについての詳細は検索してください。
 +
 
 +
引数にはカンマ区切りで複数のクラス名を指定可能です。なお、両端トリムはされないため余計なスペースなどを含まないよう注意してください。(Ex: =com.example.mod.PluginAAA, com.example.mod.pluginBBB ; カンマの後にスペースを挟んでしまっている点が間違い。)
  
==動作確認==
+
またこの際、coremod のクラスのある場所へのクラスパスを追加するのも忘れないでください。
coremod形式のmodは、基本的にはEclipseでのデバッグでは読み込みが行われません。
 
機能の実装部分については、元ファイルを直接改変するなどしましょう。
 
  
coremodとしての動作確認は、実際の動作環境へ放り込んで行うことになります。
+
* Eclipseでの設定手順(MCP付属のワークスペースを用いており、既にClientのデバッグ構成があるものとします)
 +
# デバッグの構成を開きます。
 +
# Clientのデバッグ構成を選択し、引数タブを選択します。
 +
# VM引数に、上記の引数を追加します。
 +
# 必要に応じてプラグインへのクラスパスを追加します。
 +
 
 +
サーバーへのcoremod追加も手順は同じです。
 +
 
 +
正しくcoremodとして読み込まれた場合、コンソールに
 +
<source lang="java">yyyy-MM-dd hh:mm:ss [INFO] [ForgeModLoader] Found a command line coremod : プラグインの完全修飾名</source>
 +
が出力されます。(標準エラーを表示している場合)
  
 
==難読化への対抗手段の解説==
 
==難読化への対抗手段の解説==
*準備<br />
+
===Minecraft1.5.0以降===
改変したいClassを通常通り書き換え。recompile>reobfuscateしてしまいます。
+
;改善された改変対象の比較検索方法
 +
:IClassTransformer.transformメソッドに引数"transformedName"が追加されました。
 +
:これは、易読化されたクラス名が渡されるため、開発時と同様のクラス名で比較することができるようになりました。
  
*丸ごと差し替える場合<br />
+
:また、Method名やMethodDescについても同様に、易読化する手段が提供されています。
reobfに出力されたクラスファイル名を見てTutorialTransformerを適宜書き換えましょう。
+
:Method名の場合
 +
:FMLDeobfuscatingRemapper.INSTANCE.mapMethodName(class名,Method名,MethodDesc)
 +
:MethodDescの場合
 +
:FMLDeobfuscatingRemapper.INSTANCE.mapMethodDesc(MethodDesc)
 +
:※渡すのは、どちらも難読化されているもの 逆に難読化する方法については未調査です。
  
※置き変えてしまうので、同一のクラスを置き買えるものとは競合してしまいます。
+
;ASMライブラリを用いたクラスの部分改変への改善
 +
:開発環境と同様とまでは行きませんが、deobf済みのclass名、method名、methodDescを用いて追加しても動作するようになりました。
 +
:このため、Eclipse 等の ByteCodeOutline プラグインなどで得たコードをあるていどならば、ほぼそのまま利用できるようになりました。
  
*ASMによる改変を行う場合<br />
+
===Minecraft1.4.7まで===
reobfに出力されたクラスファイルと、改変前のクラスファイルを<br />
+
;準備
JBVD等の適当なByteCodeViewerで開き、2つを比較して改変された部分を特定します。<br />
+
: 改変したいClassを書き換える。
それを元にASMのコードに置き変えTutorialTransformerを適宜かきかえましょう。
+
: recompile > reobfuscate し、難読化後のクラスファイルを作成する。
  
サンプルのようなメソッドのフック処理程度であれば<br />
+
;クラスを丸ごと差し替える場合
Eclipse等のByteCodeOutlineプラグインなどで、改変部分のコードを直接ASM形式のコードに変換して<br />
+
:reobfに出力されたクラスファイル名を見てTutorialTransformerを適宜書き換えましょう。
難読化される部分を書きかえる程度で、そのまま使える場合もあります。
 
  
※ByteCode操作のばあい、同一クラスにに複数の改変を施すことができるため、競合させずにそれぞれの改変を施すこともできます。
+
:※置き変えてしまうので、同一のクラスを置き買えるものとは競合してしまいます。
  
*雑記<br />
+
;ASMライブラリを用いたクラスの部分改変を行う場合
 +
:reobf/ に出力されたクラスファイルと、改変前のクラスファイルを、JBVD等の適当な ByteCodeViewer で開き、2つを比較して改変された部分を特定します。
 +
:それを元にASMのコードに置き変え、TutorialTransformer を適宜書き換えましょう。
 +
 
 +
:サンプルのようなメソッドのフック処理程度であれば、Eclipse 等の ByteCodeOutline プラグインなどで、改変部分のコードを直接ASM形式のコードに変換し、:難読化される部分を書きかえる程度の修正で、そのまま使える場合もあります。
 +
 
 +
:※ ByteCode操作の場合、同一クラスにに複数の改変を施すことができるため、競合させずにそれぞれの改変を施すこともできます。
 +
 
 +
==雑記==
 
メソッドコールの追加は今回の例のとおりですが<br />
 
メソッドコールの追加は今回の例のとおりですが<br />
 
別な処理に置き変えるなど、Javaでできることはほぼなんでも出来ますが<br />
 
別な処理に置き変えるなど、Javaでできることはほぼなんでも出来ますが<br />
ASMライブラリの使用方法を別途調べる必要があります。(記述時点で、日本語解説はあまり多くありません。)
+
ASMライブラリの使用方法を別途調べる必要があります。(記述時点で、日本語解説はあまり多くありません。)
 +
大まかな解説についてはこのWikiにも解説があります(→[[ASM利用]])。
 +
 
 +
ASMライブラリの既知の不具合
 +
・関数外のstaticフィールドの初期化を行うコードがあるとClassWriterがNullPointerExceptionを引き起こします。
 +
 
 +
こめ
  
 
<comments />
 
<comments />
 +
----
 +
* MANIFEST.MF 内の FMLCorePluginContainsFMLMod について追記しました。 --[[特別:投稿記録/118.22.179.98|118.22.179.98]] 2014年2月7日 (金) 08:58 (JST)
 +
----
 +
* 1.6 では FMLRelauncher.side() &gt;&gt; FMLLaunchHandler.side() と変更されているようです。パッケージもかなり移動しているようなので追記が必要かも知れません。 --[[特別:投稿記録/122.251.192.117|122.251.192.117]] 2013年7月8日 (月) 00:44 (JST)
 +
----
 +
* とてもいいチュートリアルなので、体裁などを修正させて頂きました。問題があるようでしたら差し戻してください。 --[[特別:投稿記録/219.125.180.254|219.125.180.254]] 2013年1月7日 (月) 22:24 (JST)
 
[[Category:その他]]
 
[[Category:その他]]

2018年10月17日 (水) 03:44時点における最新版

この記事は"ForgeModLoader"を前提MODとしています。

ここでは、coremodsフォルダに入れるタイプのmod作成方法を紹介します。

概要[編集]

Coremodとは[編集]

通常 Minecraft.jar 内に上書きが必要な変更を施すような 前提MOD(API系)や既存書き換え系MODなどを、coremods フォルダに入れるだけでインストールできるようにする、FMLの機能です。

ただし、既存書き換えのMODをそのまま coremod にすることは出来ません。 Mod作者自身が coremod として作成する必要があります。

仕組み[編集]

FMLでは、Minecraftが起動した直後に処理を割り込ませ、現在のスレッドの ContextClassLoader に、net.minecraft.launchwrapper.LaunchClassLoader を設定しています。このクラスローダーでは、クラスロード時にバイトコードのクラスを編集するポイントが設けられており、Minecraftが実行中に読み込む殆どのクラス(※)を、ロード時に動的に改変する事が可能となっています。

※全てのクラスの変換が行えるわけではありません。IClassTransformer を通さないよう、変換対象から除外登録されている一部のパッケージ以下のクラスは、本来のクラスローダーであるシステムクラスローダーによりロードされてしまうため、動的な変換処理を行えません。またプラグイン側で TransformerExclusions アノテーションを用い除外設定されているパッケージやクラスも変換対象外となります。

詳細は RelaunchClassLoader の実装を確認するか、実際に試してみて判断ください。また、クラスローダーそのものの仕組みについては、Web検索で多くの情報を得る事が出来ますのでここでは割愛します。

当チュートリアルでは、主にこのクラス変換機能の実装方法を解説します。

この機能を用いることで、Minecraftの実行中に、初めてクラスがロードされた際に、クラスのバイトコードを置換、または部分的に書き換える事ができるようになります。動的に書き換えるため、Minecraft.jar 内(※Coremod部分以外のFMLコードも含む)および、mods 内の zip 等のClassファイルを、直接上書きして変更することなく改変することを可能とします。

動的なクラス書き換えって、重くないの?[編集]

クラスのロードは、基本的に最初にクラスの参照が要求された際に、1回だけ行われます。一度ロードされたクラスは、通常どおりコンパイルされたclassファイルとなんら違いはありません。

つまり、施した改変内容以上のパフォーマンスへの影響は、ほぼ無いと考えて差し支えありません。

ASMライブラリ[編集]

ASMライブラリとは、クラスのバイトコードに対し、動的に変更を施すことできるバイトコード操作ライブラリのことです。

FMLにバンドルされているので、別途ライブラリを添付することなく、FMLをインストールするだけで利用が可能です。

本チュートリアルでは、最低限の機能のみの実装のみを紹介するため、ASMライブラリ等の使い方については別途検索ください。

実装[編集]

今回作成するソースファイルは以下になります。

tutorial/asm/TutorialCorePlugin.java
coremods読み込みの基点となります。
tutorial/asm/TutorialModContainer.java
ModLoaderにおけるmod_XX.classのバージョン情報等のみを格納するものです。
FMLでは情報を格納するのにアノテーションや mcmod.info ファイルを使うこともできます
しかし、Coremodは読み込み方法が異なるので記載も別となっています。
tutorial/asm/TutorialTransformer.java
Classの改変機能を実装します。
META-INF/MANIFEST.MF
ソースファイルではありませんが、FMLがcoremodである事を認識するのに必要です。

※改変を施す部分のコードは、仮実装でありそのままでは動きません。 ソースコメントを参照し、必要な実装を施して書き換えましょう。

TutorialCorePluginクラス作成[編集]

  1. 任意の名前のクラス(ここではTutorialCorePlugin)を作成します。
  2. IFMLLoadingPlugin、IFMLCallHook を実装します。

net.minecraftforge.fml.relauncher.IFMLLoadingPlugin は、このクラス自体に実装する必要はありません。 getSetupClass() メソッドで返される名前のクラスが、IFMLCallHookを実装している必要があります。 なお、本チュートリアルでは、コールフックを使用していないため、getSetupClass()メソッドの戻り値は null としています。

// パッケージは、クラス(ファイル名)の衝突を回避するために、汎用的ではないユニークなパッケージ名を使用しましょう。
// 例) 作者名、ドメイン など (一意性のあるものが好ましい)
// 
// ここでは便宜上 tutorial.asm パッケージとしています。
// asm は ASM機能を使うクラスを配置する場合の慣例ですが、解りやすくする以外の意味はなく、必ずこうしないといけないわけではありません。
package tutorial.asm;

import java.io.File;
import java.util.Map;

import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;

// TransformerExclusions: Transformerから除外するクラス名を設定するためのアノテーション
// 
//  値は文字列の配列で、複数指定も可能です。
//  指定した文字列と前方一致するクラス名は、後述のクラスの動的な変換処理から除外されます。
//  例えば、自身のクラスが変換されないように、自身のパッケージ以下を除外指定する、などが出来ます。
//  必須ではありません。必要に応じて設定してください。
//  本チュートリアルでは、参考として自身のパッケージを変換処理から除外しています。
// 
// IFMLLoadingPlugin: Coremods としてロードするために必要なインタフェース

@TransformerExclusions({"tutorial.asm"})
public class TutorialCorePlugin implements IFMLLoadingPlugin
{
    // coremod の jar ファイルのパス抽象表現を保持します。
    // Transformer 以外から呼ばれることは考慮しないため、デフォルトのアクセス指定子としています。

    static File location;

    // このプラグインが動作するために必要となるライブラリセットのクラス名の配列です。
    // 本チュートリアルでは使用しないため説明は割愛します。
    // インターフェイスの javadocや、FMLCorePlugin クラスの実装を参照してみてください。

    @Override
    public String[] getLibraryRequestClass()
    {
        return null;
    }

    // Class の改変機能を実装したクラスの完全修飾名の配列を返します。
    // 本チュートリアルの変換処理クラスは TutorialTransformer のみなので、一つだけを配列に詰め返却しています。

    @Override
    public String[] getASMTransformerClass()
    {
        return new String[]{"tutorial.asm.TutorialTransformer"};
    }

    // coremod の名前やバージョン情報を格納しているクラスの完全修飾名を返します。

    @Override
    public String getModContainerClass()
    {
        return "tutorial.asm.TutorialModContainer";
    }

    // IFMLCallHook を実装しているクラス名を返す必要があります。
    // 本チュートリアルでは、コールフックを用いないため、こちらの説明も割愛します。

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

    // IFMLLoadingPlugin のメソッドです。(IFMLCallHook にも同じシグネチャーのメソッドがありますが、違います)
    // 今回は coremod 自身の jar ファイルパスを取得しています。これは後述のトランスフォーマークラスで、
    // jarから置換用クラスを取得しているためで、そのような処理を行わないのであれば何も実装しなくても構いません。
    // 
    // なお、IFMLLoadingPlugin のメソッドとして呼ばれた際は、"mcLocation"、"coremodList"、"coremodLocation" の3つ、
    // IFMLCallHook のメソッドとして呼ばれた際は、"classLoader" がマップに設定されています。(FML#511現在)
    // 
    // 渡されるマップの中身は、net.minecraftforge.fml.relauncher.RelaunchLibraryManager の実装からも確認する事が出来ます。

    @Override
    public void injectData(Map<String, Object> data)
    {
        if (data.containsKey("coremodLocation"))
        {
            location = (File) data.get("coremodLocation");
        }
    }
 }

TutorialModContainerクラス作成[編集]

  1. 任意の名前のクラス(ここではTutorialModContainer)を作成します。
  2. ModContainer を実装します。

Coremod 用のModでは、通常の mod読み込み処理より前にModContainerが要求されるため、MetaDataアノテーションや、mcmod.infoなど、MODのメタデータ設定処理を利用できません。

そのため、別の形式で記載することが必要になっています。

ID、名前、バージョン情報を変更できれば良いため、ここでは DummyModContainer クラスを継承して作成しています。

package tutorial.asm;

import java.util.Arrays;
import java.util.List;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

import net.minecraftforge.fml.common.DummyModContainer;
import net.minecraftforge.fml.common.LoadController;
import net.minecraftforge.fml.common.ModMetadata;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;

// 必ずしも DummyModContainer を継承している必要はありません。
// net.minecraftforge.fml.common.ModContainer さえ実装していれば、どんなクラスでも構いません。

public class TutorialModContainer extends DummyModContainer
{
    public TutorialModContainer()
    {
        super(new ModMetadata());

        // 他のModと区別するための一意なIDやmodの名前など、MODのメタデータを設定します。
        ModMetadata meta = getMetadata();

        meta.modId       = "transformertutorial";
        meta.name        = "TransformerTutorial";
        meta.version     = "1.0.0";
        meta.authorList  = Arrays.asList("Author");
        meta.description = "";
        meta.url         = "";
        meta.credits     = "";
        this.setEnabledState(true);
    }
    @Override
    public boolean registerBus(EventBus bus, LoadController controller)
    {
	bus.register(this);
	return true;
    }
}

TutorialTransformerクラス作成[編集]

  1. 任意の名前のクラス(ここではTutorialTransformer)を作成します。
  2. IClassTransformer インタフェースを実装します。
package tutorial.asm;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;


import net.minecraftforge.fml.relauncher.FMLRelauncher;
import net.minecraftforge.fml.relauncher.IClassTransformer;

public class TutorialTransformer implements IClassTransformer
{
    // 改変対象のクラスの完全修飾名です。
    // 後述でMinecraft.jar内の難読化されるファイルを対象とする場合の簡易な取得方法を紹介します。
    private static final String TARGET_CLASS_NAME = "net.minecraft.src.TargetClass";

    // クラスがロードされる際に呼び出されるメソッドです。
    public byte[] transform(final String name, final String transformedName, byte[] baseClass) {

        // FMLRelauncher.side() : Client/Server どちらか一方のを対象とする場合や、
        // 一つのMODで Client/Sever 両方に対応したMODで、この値を判定して処理を変える事ができます。
        // 今回は"CLIENT"と比較し、Client側のファイルを対象としている例です。
        // Client側専用のMODとして公開するのであれば、判定は必須ではありません。

        // name : 現在ロードされようとしているクラス名が格納されています。
        if (/*!FMLRelauncher.side().equals("CLIENT") || */!transformedName.equals(TARGET_CLASS_NAME))
        {
            // 処理対象外なので何もしない
            return bytes;
        }

        try
        {
            // bytesに元のクラスの生情報が入っているので、ASMなどで処理し返す。
            return doSomething(bytes);
        }
        catch (Exception e)
        {
            throw new RuntimeException("failed : TutorialTransformer loading", e);
        }
    }
}

※1 詳しくはここを参照

META-INF/MANIFEST.MFファイルの作成[編集]

  1. META-INF/MANIFEST.MFファイルを作成します。
  2. FMLCorePlugin節にTutorialCorePlugin の完全修飾クラスの名(パッケージ名を含む名前)を指定します。
Manifest-Version: 1.0
FMLCorePlugin: tutorial.asm.TutorialCorePlugin

上記の場合、jarに通常のFML(@Mod)クラスやmcmod.infoを含めてもロードされません。 通常のFMLクラスを含めたい場合はFMLCorePluginContainsFMLMod節を追加します。値はなんでもいいです。 この場合、FMLでロードしたいクラス(@Modが付加されたクラス)はTransformerクラス(このチュートリアルではasm)等とpackageが異なる必要があります。

FMLCorePluginContainsFMLMod: *

jarパッケージへのまとめ方[編集]

前述で作成したclassをjar形式にまとめます。
coremods用のmodではjar形式でなければ読み込まれません。

今回の例では下記のようなファイル構成となります。

  • tutorial.jar
    • META-INF
      • MANIFEST.MF
    • tutorial
      • asm
        • TutorialCorePlugin.class
        • TutorialModContainer.class
        • TutorialTransformer.class
    • ModifiedTargetClass.class (丸ごと差し替える場合に用いるクラス(難読化済み))

通常の mod のように、zip で圧縮し、拡張子を .zip から .jar に変更すれば完成です。

※jarコマンドを用いて作成することも可能ですが、今回は割愛します。

※1.8から

  • ワークスペース(Gradlew.batがあるフォルダ)
    • src
      • main
        • java
          • tutorial
              • asm
                • TutorialCorePlugin.class
                • TutorialModContainer.class
                • TutorialTransformer.class
        • resources
          • ModifiedTargetClass.class (丸ごと差し替える場合に用いるクラス(難読化済み))

ForgeGradleが勝手に1.7のようにやってくれます。

テスト、デバッグ方法について[編集]

coremod 形式の mod は、Eclipse でのデバッグで動作させるのは手間がかかります。 機能の実装部分については、書き換え元のクラスを、直接改変して試したほうが簡単でしょう。

coremodとしての動作確認は、実際の動作環境へ放り込んで行ってください。

通常 coremods フォルダーにある場合のみ coremod として読み込まれるため、作成した coremod にただクラスパスを通すだけでは読み込まれません。しかし、FMLには環境変数 fml.coreMods.load を用いた coremod の読み込み機能が用意されています。これを用いることで、任意のプラグインを coremod として読み込ませる事が可能です。

なお、環境変数に指定するのは割と不便なので、JVM起動時の引数としてVMに環境変数を追加して指定するほうが便利です。

やり方はとても簡単で、デバッグ実行する際のJVMの引数に、以下を追加します。

-Dfml.coreMods.load=完全修飾クラス名CSV

※-Dオプションについての詳細は検索してください。

引数にはカンマ区切りで複数のクラス名を指定可能です。なお、両端トリムはされないため余計なスペースなどを含まないよう注意してください。(Ex: =com.example.mod.PluginAAA, com.example.mod.pluginBBB ; カンマの後にスペースを挟んでしまっている点が間違い。)

またこの際、coremod のクラスのある場所へのクラスパスを追加するのも忘れないでください。

  • Eclipseでの設定手順(MCP付属のワークスペースを用いており、既にClientのデバッグ構成があるものとします)
  1. デバッグの構成を開きます。
  2. Clientのデバッグ構成を選択し、引数タブを選択します。
  3. VM引数に、上記の引数を追加します。
  4. 必要に応じてプラグインへのクラスパスを追加します。

サーバーへのcoremod追加も手順は同じです。

正しくcoremodとして読み込まれた場合、コンソールに

yyyy-MM-dd hh:mm:ss [INFO] [ForgeModLoader] Found a command line coremod : プラグインの完全修飾名

が出力されます。(標準エラーを表示している場合)

難読化への対抗手段の解説[編集]

Minecraft1.5.0以降[編集]

改善された改変対象の比較検索方法
IClassTransformer.transformメソッドに引数"transformedName"が追加されました。
これは、易読化されたクラス名が渡されるため、開発時と同様のクラス名で比較することができるようになりました。
また、Method名やMethodDescについても同様に、易読化する手段が提供されています。
Method名の場合
FMLDeobfuscatingRemapper.INSTANCE.mapMethodName(class名,Method名,MethodDesc)
MethodDescの場合
FMLDeobfuscatingRemapper.INSTANCE.mapMethodDesc(MethodDesc)
※渡すのは、どちらも難読化されているもの 逆に難読化する方法については未調査です。
ASMライブラリを用いたクラスの部分改変への改善
開発環境と同様とまでは行きませんが、deobf済みのclass名、method名、methodDescを用いて追加しても動作するようになりました。
このため、Eclipse 等の ByteCodeOutline プラグインなどで得たコードをあるていどならば、ほぼそのまま利用できるようになりました。

Minecraft1.4.7まで[編集]

準備
改変したいClassを書き換える。
recompile > reobfuscate し、難読化後のクラスファイルを作成する。
クラスを丸ごと差し替える場合
reobfに出力されたクラスファイル名を見てTutorialTransformerを適宜書き換えましょう。
※置き変えてしまうので、同一のクラスを置き買えるものとは競合してしまいます。
ASMライブラリを用いたクラスの部分改変を行う場合
reobf/ に出力されたクラスファイルと、改変前のクラスファイルを、JBVD等の適当な ByteCodeViewer で開き、2つを比較して改変された部分を特定します。
それを元にASMのコードに置き変え、TutorialTransformer を適宜書き換えましょう。
サンプルのようなメソッドのフック処理程度であれば、Eclipse 等の ByteCodeOutline プラグインなどで、改変部分のコードを直接ASM形式のコードに変換し、:難読化される部分を書きかえる程度の修正で、そのまま使える場合もあります。
※ ByteCode操作の場合、同一クラスにに複数の改変を施すことができるため、競合させずにそれぞれの改変を施すこともできます。

雑記[編集]

メソッドコールの追加は今回の例のとおりですが
別な処理に置き変えるなど、Javaでできることはほぼなんでも出来ますが
ASMライブラリの使用方法を別途調べる必要があります。(記述時点で、日本語解説はあまり多くありません。) 大まかな解説についてはこのWikiにも解説があります(→ASM利用)。

ASMライブラリの既知の不具合 ・関数外のstaticフィールドの初期化を行うコードがあるとClassWriterがNullPointerExceptionを引き起こします。

こめ


自分のコメントを追加
Minecraft Modding Wikiはすべてのコメントを歓迎します。匿名で投稿したくない場合は、アカウント作成またはログインしてください。無料です。


  • MANIFEST.MF 内の FMLCorePluginContainsFMLMod について追記しました。 --118.22.179.98 2014年2月7日 (金) 08:58 (JST)

  • 1.6 では FMLRelauncher.side() >> FMLLaunchHandler.side() と変更されているようです。パッケージもかなり移動しているようなので追記が必要かも知れません。 --122.251.192.117 2013年7月8日 (月) 00:44 (JST)

  • とてもいいチュートリアルなので、体裁などを修正させて頂きました。問題があるようでしたら差し戻してください。 --219.125.180.254 2013年1月7日 (月) 22:24 (JST)