提供: Minecraft Modding Wiki
移動先: 案内検索

警告: ログインしていません。編集を行うと、あなたの IP アドレスが公開されます。ログインまたはアカウントを作成すれば、あなたの編集はその利用者名とともに表示されるほか、その他の利点もあります。

この編集を取り消せます。 下記の差分を確認して、本当に取り消していいか検証してください。よろしければ変更を保存して取り消しを完了してください。
最新版 編集中の文章
12行目: 12行目:
  
 
===仕組み===
 
===仕組み===
FMLでは、Minecraftが起動した直後に処理を割り込ませ、現在のスレッドの ContextClassLoader に、net.minecraft.launchwrapper.LaunchClassLoader を設定しています。このクラスローダーでは、クラスロード時にバイトコードのクラスを編集するポイントが設けられており、Minecraftが実行中に読み込む殆どのクラス(※)を、ロード時に動的に改変する事が可能となっています。
+
FMLでは、Minecraftが起動した直後に処理を割り込ませ、現在のスレッドの ContextClassLoader に、cpw.mods.fml.relauncher.RelaunchClassLoader を設定しています。このクラスローダーでは、クラスロード時にバイトコードのクラスを編集するポイントが設けられており、Minecraftが実行中に読み込む殆どのクラス(※)を、ロード時に動的に改変する事が可能となっています。
  
 
※全てのクラスの変換が行えるわけではありません。IClassTransformer を通さないよう、変換対象から除外登録されている一部のパッケージ以下のクラスは、本来のクラスローダーであるシステムクラスローダーによりロードされてしまうため、動的な変換処理を行えません。またプラグイン側で TransformerExclusions アノテーションを用い除外設定されているパッケージやクラスも変換対象外となります。
 
※全てのクラスの変換が行えるわけではありません。IClassTransformer を通さないよう、変換対象から除外登録されている一部のパッケージ以下のクラスは、本来のクラスローダーであるシステムクラスローダーによりロードされてしまうため、動的な変換処理を行えません。またプラグイン側で TransformerExclusions アノテーションを用い除外設定されているパッケージやクラスも変換対象外となります。
60行目: 60行目:
 
# IFMLLoadingPlugin、<strike>IFMLCallHook</strike> を実装します。
 
# IFMLLoadingPlugin、<strike>IFMLCallHook</strike> を実装します。
  
net.minecraftforge.fml.relauncher.IFMLLoadingPlugin は、このクラス自体に実装する必要はありません。
+
cpw.mods.fml.relauncher.IFMLCallHook は、このクラス自体に実装する必要はありません。
 
getSetupClass() メソッドで返される名前のクラスが、IFMLCallHookを実装している必要があります。
 
getSetupClass() メソッドで返される名前のクラスが、IFMLCallHookを実装している必要があります。
なお、本チュートリアルでは、コールフックを使用していないため、getSetupClass()メソッドの戻り値は null としています。
+
なお、本チュートリアルでは、コールフックを使用していないため、getSetupClass()メソッドの戻り値は null としています。
  
 
<source lang="java">
 
<source lang="java">
75行目: 75行目:
 
import java.util.Map;
 
import java.util.Map;
  
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
+
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;
+
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;
  
 
// TransformerExclusions: Transformerから除外するクラス名を設定するためのアノテーション
 
// TransformerExclusions: Transformerから除外するクラス名を設定するためのアノテーション
139行目: 139行目:
 
     // IFMLCallHook のメソッドとして呼ばれた際は、"classLoader" がマップに設定されています。(FML#511現在)
 
     // IFMLCallHook のメソッドとして呼ばれた際は、"classLoader" がマップに設定されています。(FML#511現在)
 
     //  
 
     //  
     // 渡されるマップの中身は、net.minecraftforge.fml.relauncher.RelaunchLibraryManager の実装からも確認する事が出来ます。
+
     // 渡されるマップの中身は、cpw.mods.fml.relauncher.RelaunchLibraryManager の実装からも確認する事が出来ます。
  
 
     @Override
 
     @Override
172行目: 172行目:
 
import com.google.common.eventbus.Subscribe;
 
import com.google.common.eventbus.Subscribe;
  
import net.minecraftforge.fml.common.DummyModContainer;
+
import cpw.mods.fml.common.DummyModContainer;
import net.minecraftforge.fml.common.LoadController;
+
import cpw.mods.fml.common.LoadController;
import net.minecraftforge.fml.common.ModMetadata;
+
import cpw.mods.fml.common.ModMetadata;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
+
import cpw.mods.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
+
import cpw.mods.fml.common.versioning.ArtifactVersion;
  
 
// 必ずしも DummyModContainer を継承している必要はありません。
 
// 必ずしも DummyModContainer を継承している必要はありません。
// net.minecraftforge.fml.common.ModContainer さえ実装していれば、どんなクラスでも構いません。
+
// cpw.mods.fml.common.ModContainer さえ実装していれば、どんなクラスでも構いません。
  
 
public class TutorialModContainer extends DummyModContainer
 
public class TutorialModContainer extends DummyModContainer
222行目: 222行目:
 
import java.util.zip.ZipFile;
 
import java.util.zip.ZipFile;
  
 +
import org.objectweb.asm.*;
 +
import org.objectweb.asm.tree.*;
 +
import static org.objectweb.asm.Opcodes.*;
 +
import static org.objectweb.asm.tree.AbstractInsnNode.*;
  
import net.minecraftforge.fml.relauncher.FMLRelauncher;
+
import cpw.mods.fml.relauncher.FMLRelauncher;
import net.minecraftforge.fml.relauncher.IClassTransformer;
+
import cpw.mods.fml.relauncher.IClassTransformer;
  
public class TutorialTransformer implements IClassTransformer
+
// Opcodes : インプリメントすると、ASMによるバイトコード定数にアクセスするのに便利です。
 +
// 必須ではありません。不用な場合は implements から削除してください。
 +
 
 +
public class TutorialTransformer implements IClassTransformer, Opcodes
 
{
 
{
 
     // 改変対象のクラスの完全修飾名です。
 
     // 改変対象のクラスの完全修飾名です。
233行目: 240行目:
  
 
     // クラスがロードされる際に呼び出されるメソッドです。
 
     // クラスがロードされる際に呼び出されるメソッドです。
     public byte[] transform(final String name, final String transformedName, byte[] baseClass) {
+
    @Override
 
+
     public byte[] transform(String name, byte[] bytes)
 +
    {
 
         // FMLRelauncher.side() : Client/Server どちらか一方のを対象とする場合や、
 
         // FMLRelauncher.side() : Client/Server どちらか一方のを対象とする場合や、
 
         // 一つのMODで Client/Sever 両方に対応したMODで、この値を判定して処理を変える事ができます。
 
         // 一つのMODで Client/Sever 両方に対応したMODで、この値を判定して処理を変える事ができます。
241行目: 249行目:
  
 
         // name : 現在ロードされようとしているクラス名が格納されています。
 
         // name : 現在ロードされようとしているクラス名が格納されています。
         if (/*!FMLRelauncher.side().equals("CLIENT") || */!transformedName.equals(TARGET_CLASS_NAME))
+
         if (!FMLRelauncher.side().equals("CLIENT") || !name.equals(TARGET_CLASS_NAME))
 
         {
 
         {
 
             // 処理対象外なので何もしない
 
             // 処理対象外なので何もしない
249行目: 257行目:
 
         try
 
         try
 
         {
 
         {
             // bytesに元のクラスの生情報が入っているので、ASMなどで処理し返す。
+
             // --------------------------------------------------------------
             return doSomething(bytes);
+
             // クラスファイル丸ごと差し替える場合
 +
            // --------------------------------------------------------------
 +
            // return replaceClass(bytes);
 +
 
 +
            // --------------------------------------------------------------
 +
            // ASMを使用し、既存のクラスファイルに改変を施す場合。
 +
            // --------------------------------------------------------------
 +
            // return hookDoRenderLivingMethod(bytes);
 +
 
 
         }
 
         }
 
         catch (Exception e)
 
         catch (Exception e)
256行目: 272行目:
 
             throw new RuntimeException("failed : TutorialTransformer loading", e);
 
             throw new RuntimeException("failed : TutorialTransformer loading", e);
 
         }
 
         }
 +
    }
 +
 +
    // 下記の想定で実装されています。
 +
    // 対象クラスの bytes を ModifiedTargetClass.class ファイルに置き換える
 +
    private byte[] replaceClass(byte[] bytes) throws IOException
 +
    {
 +
        ZipFile zf = null;
 +
        InputStream zi = null;
 +
 +
        try
 +
        {
 +
            zf = new ZipFile(TutorialCorePlugin.location);
 +
 +
            // 差し替え後のファイルです。coremodのjar内のパスを指定します。
 +
            ZipEntry ze = zf.getEntry("ModifiedTargetClass.class");
 +
 +
            if (ze != null)
 +
            {
 +
                zi = zf.getInputStream(ze);
 +
                bytes = new byte[(int) ze.getSize()];
 +
                zi.read(bytes);
 +
            }
 +
 +
            return bytes;
 +
        }
 +
        finally
 +
        {
 +
            if (zi != null)
 +
            {
 +
                zi.close();
 +
            }
 +
 +
            if (zf != null)
 +
            {
 +
                zf.close();
 +
            }
 +
        }
 +
    }
 +
 +
    // 下記の想定で実装されています。
 +
    // EntityLiving.class の doRenderLiving の先頭に
 +
    // tutorial/test.class の passTestRender(EntityLiving, double, double, double)メソッドの呼び出しを追加する。
 +
    private byte[] hookDoRenderLivingMethod(byte[] bytes)
 +
    {
 +
        // ASMで、bytesに格納されたクラスファイルを解析します。
 +
        ClassNode cnode = new ClassNode();
 +
        ClassReader reader = new ClassReader(bytes);
 +
        reader.accept(cnode, 0);
 +
 +
        // 改変対象のメソッド名です
 +
        String targetMethodName = "doRenderLiving";
 +
 +
        // 改変対象メソッドの戻り値型および、引数型をあらわします
 +
        String targetMethoddesc = "(Lnet/minecraft/entity/EntityLiving;DDDFF)V";
 +
 +
        // 対象のメソッドを検索取得します。
 +
        MethodNode mnode = null;
 +
        for (MethodNode curMnode : (List<MethodNode>) cnode.methods)
 +
        {
 +
            if (targetMethodName.equals(curMnode.name) && targetMethoddesc.equals(curMnode.desc))
 +
            {
 +
                mnode = curMnode;
 +
                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();
 +
        }
 +
 +
        return bytes;
 
     }
 
     }
 
}
 
}
 
</source>
 
</source>
 
※1 詳しくは[http://asm.ow2.org/doc/faq.html#Q7 ここ]を参照
 
  
 
===META-INF/MANIFEST.MFファイルの作成===
 
===META-INF/MANIFEST.MFファイルの作成===
272行目: 375行目:
  
 
上記の場合、jarに通常のFML(@Mod)クラスやmcmod.infoを含めてもロードされません。
 
上記の場合、jarに通常のFML(@Mod)クラスやmcmod.infoを含めてもロードされません。
通常のFMLクラスを含めたい場合はFMLCorePluginContainsFMLMod節を追加します。値はなんでもいいです。
+
通常のFMLクラスを含めたい場合はFMLCorePluginContainsFMLMod節を追加し、FMLにロードさせたい @Mod.Instanceのある 完全修飾クラス名を指定します。
この場合、FMLでロードしたいクラス(@Modが付加されたクラス)はTransformerクラス(このチュートリアルではasm)等とpackageが異なる必要があります。
+
ただし、Transformerクラス(このチュートリアルではasm)等とはpackageを別にする必要があります。
 
<source lang="java">
 
<source lang="java">
FMLCorePluginContainsFMLMod: *
+
FMLCorePluginContainsFMLMod: tutorial.fml.TutorialFMLMod
 
</source>
 
</source>
  
291行目: 394行目:
 
**** TutorialModContainer.class
 
**** TutorialModContainer.class
 
**** TutorialTransformer.class
 
**** TutorialTransformer.class
** ModifiedTargetClass.class (丸ごと差し替える場合に用いるクラス(難読化済み))
+
** ModifiedTargetClass.class (丸ごと差し替える場合に用いるクラス)
 
    
 
    
 
通常の mod のように、zip で圧縮し、拡張子を .zip から .jar に変更すれば完成です。
 
通常の mod のように、zip で圧縮し、拡張子を .zip から .jar に変更すれば完成です。
  
 
※jarコマンドを用いて作成することも可能ですが、今回は割愛します。
 
※jarコマンドを用いて作成することも可能ですが、今回は割愛します。
 
※1.8から
 
*ワークスペース(Gradlew.batがあるフォルダ)
 
** src
 
*** main
 
**** java
 
***** tutorial
 
******* asm
 
******** TutorialCorePlugin.class
 
******** TutorialModContainer.class
 
******** TutorialTransformer.class
 
**** resources
 
***** ModifiedTargetClass.class (丸ごと差し替える場合に用いるクラス(難読化済み))
 
ForgeGradleが勝手に1.7のようにやってくれます。
 
  
 
==テスト、デバッグ方法について==
 
==テスト、デバッグ方法について==
381行目: 470行目:
 
別な処理に置き変えるなど、Javaでできることはほぼなんでも出来ますが<br />
 
別な処理に置き変えるなど、Javaでできることはほぼなんでも出来ますが<br />
 
ASMライブラリの使用方法を別途調べる必要があります。(記述時点で、日本語解説はあまり多くありません。)
 
ASMライブラリの使用方法を別途調べる必要があります。(記述時点で、日本語解説はあまり多くありません。)
大まかな解説についてはこのWikiにも解説があります(→[[ASM利用]])。
 
  
 
ASMライブラリの既知の不具合
 
ASMライブラリの既知の不具合
 
・関数外のstaticフィールドの初期化を行うコードがあるとClassWriterがNullPointerExceptionを引き起こします。
 
・関数外のstaticフィールドの初期化を行うコードがあるとClassWriterがNullPointerExceptionを引き起こします。
 
こめ
 
  
 
<comments />
 
<comments />

Minecraft Modding Wikiへの投稿はすべて、他の投稿者によって編集、変更、除去される場合があります。 自分が書いたものが他の人に容赦なく編集されるのを望まない場合は、ここに投稿しないでください。
また、投稿するのは、自分で書いたものか、パブリック ドメインまたはそれに類するフリーな資料からの複製であることを約束してください(詳細はMinecraft Modding Wiki:著作権を参照)。 著作権保護されている作品は、許諾なしに投稿しないでください!

このページを編集するには、下記の確認用の質問に回答してください (詳細):

取り消し 編集の仕方 (新しいウィンドウで開きます)

このページで使用されているテンプレート: