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

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

この編集を取り消せます。 下記の差分を確認して、本当に取り消していいか検証してください。よろしければ変更を保存して取り消しを完了してください。
最新版 編集中の文章
1行目: 1行目:
 +
 
{{前提MOD|reqmod="ForgeModLoader"}}
 
{{前提MOD|reqmod="ForgeModLoader"}}
  
ここでは、coremodsフォルダに入れるタイプのmod作成方法を紹介します。
+
ここでは、coremods用modのASM機能を使用するmod作成方法を紹介します。<br />
  
 
==概要==
 
==概要==
 
+
*ASMとは、Java動的ByteCode操作ライブラリの一つです。<br />
===Coremodとは===
+
通常コンパイル済みのClassファイルの内容は変更できません。<br />
通常 Minecraft.jar 内に上書きが必要な変更を施すような 前提MOD(API系)や既存書き換え系MODなどを、coremods フォルダに入れるだけでインストールできるようにする、FMLの機能です。
+
ですが、JavaにClassファイルを読み込む機能ClassLoaderを乗っ取り、ASMにより動的にClassファイルに変更を施すことで<br />
 
+
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読み込みの基点となります。
 
  
;tutorial/asm/TutorialModContainer.java
+
*※改変を施す部分のコードは、仮実装でありそのままでは動きません。<br />
:ModLoaderにおけるmod_XX.classのバージョン情報等のみを格納するものです。
+
ソースコメントにしたがって必要な実装を施して書き換えましょう。
:FMLでは情報を格納するのにアノテーションや mcmod.info ファイルを使うこともできます
 
:しかし、Coremodは読み込み方法が異なるので記載も別となっています。
 
  
;tutorial/asm/TutorialTransformer.java
+
*※パフォーマンスへの影響<br />
:Classの改変機能を実装します。
+
この機能によってロードされたクラスは、ロードは原則1回しか行われませんし<br />
 +
完了すれば通常どおりコンパイルされたclassファイルとなんら違いはありません。<br />
 +
施した改変内容以上のパフォーマンスへの影響はほぼ無いです。
  
;META-INF/MANIFEST.MF
+
==ソースの解説==
:ソースファイルではありませんが、FMLがcoremodである事を認識するのに必要です。
 
  
※改変を施す部分のコードは、仮実装でありそのままでは動きません。
+
作成するソース<br />
ソースコメントを参照し、必要な実装を施して書き換えましょう。
+
*TutorialCorePlugin.java<br />
 +
coremods読み込みの基点となります。
 +
*TutorialModContainer.java<br />
 +
ModLoaderにおけるmod_XX.classのバージョン情報等のみを格納するものです。
 +
*TutorialTransformer.java<br/>
 +
Classの改変機能を実装します。
 +
*META-INF/MANIFEST.MF
 +
coremod用jarにするために必要です。
  
 
===TutorialCorePluginクラス作成===
 
===TutorialCorePluginクラス作成===
 +
**CorePluginクラスを作成します。
  
# 任意の名前のクラス(ここではTutorialCorePlugin)を作成します。
+
mod_Tutorialソース
# 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等)
// ここでは便宜上 tutorial.asm パッケージとしています。
+
//asm : 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;
 
  
// TransformerExclusions: Transformerから除外するクラス名を設定するためのアノテーション
+
import cpw.mods.fml.relauncher.IFMLCallHook;
//
+
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
//  値は文字列の配列で、複数指定も可能です。
+
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;
//  指定した文字列と前方一致するクラス名は、後述のクラスの動的な変換処理から除外されます。
 
//  例えば、自身のクラスが変換されないように、自身のパッケージ以下を除外指定する、などが出来ます。
 
//  必須ではありません。必要に応じて設定してください。
 
//  本チュートリアルでは、参考として自身のパッケージを変換処理から除外しています。
 
//
 
// IFMLLoadingPlugin: Coremods としてロードするために必要なインタフェース
 
  
@TransformerExclusions({"tutorial.asm"})
+
//TransformerExclusions.value:coremodsでロードする際に参照されるためパッケージ名と一致させてください。
public class TutorialCorePlugin implements IFMLLoadingPlugin
+
//IFMLLoadingPlugin:Coremodsの基礎インタフェース
{
+
//IFMLCallHook:Coremods内で、coremod自身のパス等を取得する等の際に必要となります。
    // coremod の jar ファイルのパス抽象表現を保持します。
+
@TransformerExclusions(value={"tutorial.asm"})
    // Transformer 以外から呼ばれることは考慮しないため、デフォルトのアクセス指定子としています。
+
public class TutorialCorePlugin implements IFMLLoadingPlugin, IFMLCallHook
  
     static File location;
+
     //coremod自身のファイルパスの保存用
 
+
public static File location;
     // このプラグインが動作するために必要となるライブラリセットのクラス名の配列です。
+
      
     // 本チュートリアルでは使用しないため説明は割愛します。
+
     //今回は使用しません
    // インターフェイスの javadocや、FMLCorePlugin クラスの実装を参照してみてください。
+
@Override
 
+
public String[] getLibraryRequestClass()
    @Override
+
{
    public String[] getLibraryRequestClass()
 
    {
 
 
         return null;
 
         return null;
    }
+
}
  
     // Class の改変機能を実装したクラスの完全修飾名の配列を返します。
+
     //Classの改変機能を実装したクラスの完全修飾名を返します。
    // 本チュートリアルの変換処理クラスは TutorialTransformer のみなので、一つだけを配列に詰め返却しています。
+
@Override
 +
public String[] getASMTransformerClass()
 +
{
 +
return new String[]{
 +
"tutorial.asm.TutorialTransformer"};
 +
}
  
     @Override
+
     //coremodの名前やバージョン情報の格納クラスの完全修飾名を返します。
    public String[] getASMTransformerClass()
+
@Override
    {
+
public String getModContainerClass()
        return new String[]{"tutorial.asm.TutorialTransformer"};
+
{
    }
+
return "tutorial.asm.TutorialModContainer";
 
+
}
    // coremod の名前やバージョン情報を格納しているクラスの完全修飾名を返します。
 
  
     @Override
+
     //coremods読み込みの基点クラスの完全修飾名を返します。
    public String getModContainerClass()
+
@Override
    {
+
public String getSetupClass()
         return "tutorial.asm.TutorialModContainer";
+
{
    }
+
         return "tutorial.asm.TutorialCorePlugin";
 
+
}
    // IFMLCallHook を実装しているクラス名を返す必要があります。
 
    // 本チュートリアルでは、コールフックを用いないため、こちらの説明も割愛します。
 
 
 
    @Override
 
    public String getSetupClass()
 
    {
 
        return null;
 
    }
 
  
     // IFMLLoadingPlugin のメソッドです。(IFMLCallHook にも同じシグネチャーのメソッドがありますが、違います)
+
     //IFMLCallHookのメソッドです。
     // 今回は coremod 自身の jar ファイルパスを取得しています。これは後述のトランスフォーマークラスで、
+
     //今回はCoremod自身のjarファイルパスを取得します。
    // jarから置換用クラスを取得しているためで、そのような処理を行わないのであれば何も実装しなくても構いません。
+
@Override
    //
+
public void injectData(Map<String, Object> data)
    // なお、IFMLLoadingPlugin のメソッドとして呼ばれた際は、"mcLocation"、"coremodList"、"coremodLocation" の3つ、
+
{
    // IFMLCallHook のメソッドとして呼ばれた際は、"classLoader" がマップに設定されています。(FML#511現在)
+
if(data.containsKey("coremodLocation"))
    //
+
location = (File) data.get("coremodLocation");
    // 渡されるマップの中身は、net.minecraftforge.fml.relauncher.RelaunchLibraryManager の実装からも確認する事が出来ます。
+
}
  
    @Override
+
@Override
    public void injectData(Map<String, Object> data)
+
public Void call() throws Exception
    {
+
{
        if (data.containsKey("coremodLocation"))
+
return null;
        {
+
}
            location = (File) data.get("coremodLocation");
 
        }
 
    }
 
 
  }
 
  }
 
</source>
 
</source>
154行目: 109行目:
 
===TutorialModContainerクラス作成===
 
===TutorialModContainerクラス作成===
  
# 任意の名前のクラス(ここではTutorialModContainer)を作成します。
+
**ModContainerクラスを作成します。
# ModContainer を実装します。
+
バージョン情報などみで良いため、DummyModContainerクラスを継承して作成します。
 +
FML形式のModではアノテーションを利用して、情報を記載できますが<br />
 +
coremods用途の場合、通常のアノテーションやmod_XX.class形式のmod読み込み処理より前に取得されるため<br />
 +
別の形式で記載することが必要になっています。
  
Coremod 用のModでは、通常の mod読み込み処理より前にModContainerが要求されるため、MetaDataアノテーションや、mcmod.infoなど、MODのメタデータ設定処理を利用できません。
+
<source lang="java">
  
そのため、別の形式で記載することが必要になっています。
+
package tutorial.asm;
 
 
ID、名前、バージョン情報を変更できれば良いため、ここでは DummyModContainer クラスを継承して作成しています。
 
 
 
<source lang="java">
 
package tutorial.asm;
 
  
 
import java.util.Arrays;
 
import java.util.Arrays;
172行目: 125行目:
 
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 を継承している必要はありません。
 
// 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();
 +
}
  
        // 他のModと区別するための一意なIDやmodの名前など、MODのメタデータを設定します。
+
@Override
        ModMetadata meta = getMetadata();
+
public ModMetadata getMetadata()
 +
{
 +
//modの名前や、他のModと区別するための一意なID情報などを指定します。
 +
ModMetadata meta = super.getMetadata();
  
 
         meta.modId      = "transformertutorial";
 
         meta.modId      = "transformertutorial";
197行目: 157行目:
 
         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;
 
 
     }
 
     }
 
}
 
}
210行目: 177行目:
 
===TutorialTransformerクラス作成===
 
===TutorialTransformerクラス作成===
  
# 任意の名前のクラス(ここではTutorialTransformer)を作成します。
+
**Transformerクラスを作成します。
# IClassTransformer インタフェースを実装します。
+
IClassTransformerインタフェースを実装します。
  
 
<source lang="java">
 
<source lang="java">
package tutorial.asm;
+
package flammpfeil.everybodysnametag.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;
222行目: 190行目:
 
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 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によるバイトコード定数にアクセスするのに便利です。
 +
public class TutorialTransformer implements IClassTransformer , Opcodes
 
{
 
{
    // 改変対象のクラスの完全修飾名です。
 
    // 後述でMinecraft.jar内の難読化されるファイルを対象とする場合の簡易な取得方法を紹介します。
 
    private static final String TARGET_CLASS_NAME = "net.minecraft.src.TargetClass";
 
  
     // クラスがロードされる際に呼び出されるメソッドです。
+
     //クラスがロードされる際に呼び出されるメソッドです。
     public byte[] transform(final String name, final String transformedName, byte[] baseClass) {
+
    @Override
 +
     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))
 +
            {
 +
 
 +
              //--------------------------------------------------------------
 +
              //クラスファイル丸ごと差し替える場合
 +
              //--------------------------------------------------------------
 +
             
 +
              /*
 +
             
 +
              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);
  
        // FMLRelauncher.side() : Client/Server どちらか一方のを対象とする場合や、
+
                MethodNode mnode = null;
        // 一つのMODで Client/Sever 両方に対応したMODで、この値を判定して処理を変える事ができます。
+
                //改変対象のメソッド名です
        // 今回は"CLIENT"と比較し、Client側のファイルを対象としている例です。
+
                String targetMethodName = "doRenderLiving" ;
        // Client側専用のMODとして公開するのであれば、判定は必須ではありません。
+
                //改変対象メソッドの戻り値型および、引数型をあらわします
 +
                String targetMethoddesc = "(Lnet/minecraft/entity/EntityLiving;DDDFF)V";
  
        // name : 現在ロードされようとしているクラス名が格納されています。
+
                //対象のメソッドを検索取得します。
        if (/*!FMLRelauncher.side().equals("CLIENT") || */!transformedName.equals(TARGET_CLASS_NAME))
+
                for(MethodNode curMnode : (List<MethodNode>) cnode.methods)
        {
+
                    if(targetMethodName.equals(curMnode.name) && targetMethoddesc.equals(curMnode.desc)){
            // 処理対象外なので何もしない
+
                        mnode = curMnode;
            return 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);
  
        try
+
                    //改変したクラスファイルをバイト列に書き出します
        {
+
                    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
            // bytesに元のクラスの生情報が入っているので、ASMなどで処理し返す。
+
                    cnode.accept(cw);
            return doSomething(bytes);
+
                    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ファイルを作成します。
 +
TutorialCorePluginのクラス名を指定します。
  
# META-INF/MANIFEST.MFファイルを作成します。
 
# FMLCorePlugin節にTutorialCorePlugin の完全修飾クラスの名(パッケージ名を含む名前)を指定します。
 
 
<source lang="java">
 
<source lang="java">
 
Manifest-Version: 1.0
 
Manifest-Version: 1.0
271行目: 319行目:
 
</source>
 
</source>
  
上記の場合、jarに通常のFML(@Mod)クラスやmcmod.infoを含めてもロードされません。
+
==jarパッケージにまとめる==
通常のFMLクラスを含めたい場合はFMLCorePluginContainsFMLMod節を追加します。値はなんでもいいです。
+
前述で作成したclassを形式にまとめます。
この場合、FMLでロードしたいクラス(@Modが付加されたクラス)はTransformerクラス(このチュートリアルではasm)等とpackageが異なる必要があります。
+
coremods用のmodではjar形式でなければなりません。
<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
** ModifiedTargetClass.class (丸ごと差し替える場合に用いるクラス(難読化済み))
+
**target.class (丸ごと差し替え例用)
 
    
 
    
通常の mod のように、zip で圧縮し、拡張子を .zip から .jar に変更すれば完成です。
+
通常のmodのように、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でのデバッグでは読み込みが行われません。
 +
機能の実装部分については、元ファイルを直接改変するなどしましょう。
  
* Eclipseでの設定手順(MCP付属のワークスペースを用いており、既にClientのデバッグ構成があるものとします)
+
coremodとしての動作確認は、実際の動作環境へ放り込んで行うことになります。
# デバッグの構成を開きます。
 
# Clientのデバッグ構成を選択し、引数タブを選択します。
 
# VM引数に、上記の引数を追加します。
 
# 必要に応じてプラグインへのクラスパスを追加します。
 
 
 
サーバーへのcoremod追加も手順は同じです。
 
 
 
正しくcoremodとして読み込まれた場合、コンソールに
 
<source lang="java">yyyy-MM-dd hh:mm:ss [INFO] [ForgeModLoader] Found a command line coremod : プラグインの完全修飾名</source>
 
が出力されます。(標準エラーを表示している場合)
 
  
 
==難読化への対抗手段の解説==
 
==難読化への対抗手段の解説==
===Minecraft1.5.0以降===
+
*準備<br />
;改善された改変対象の比較検索方法
+
改変したいClassを通常通り書き換え。recompile>reobfuscateしてしまいます。
:IClassTransformer.transformメソッドに引数"transformedName"が追加されました。
 
:これは、易読化されたクラス名が渡されるため、開発時と同様のクラス名で比較することができるようになりました。
 
  
:また、Method名やMethodDescについても同様に、易読化する手段が提供されています。
+
*丸ごと差し替える場合<br />
:Method名の場合
+
reobfに出力されたクラスファイル名を見てTutorialTransformerを適宜書き換えましょう。
:FMLDeobfuscatingRemapper.INSTANCE.mapMethodName(class名,Method名,MethodDesc)
 
:MethodDescの場合
 
:FMLDeobfuscatingRemapper.INSTANCE.mapMethodDesc(MethodDesc)
 
:※渡すのは、どちらも難読化されているもの 逆に難読化する方法については未調査です。
 
  
;ASMライブラリを用いたクラスの部分改変への改善
+
※置き変えてしまうので、同一のクラスを置き買えるものとは競合してしまいます。
:開発環境と同様とまでは行きませんが、deobf済みのclass名、method名、methodDescを用いて追加しても動作するようになりました。
 
:このため、Eclipse 等の ByteCodeOutline プラグインなどで得たコードをあるていどならば、ほぼそのまま利用できるようになりました。
 
  
===Minecraft1.4.7まで===
+
*ASMによる改変を行う場合<br />
;準備
+
reobfに出力されたクラスファイルと、改変前のクラスファイルを<br />
: 改変したいClassを書き換える。
+
JBVD等の適当なByteCodeViewerで開き、2つを比較して改変された部分を特定します。<br />
: recompile > reobfuscate し、難読化後のクラスファイルを作成する。
+
それを元にASMのコードに置き変えTutorialTransformerを適宜かきかえましょう。
  
;クラスを丸ごと差し替える場合
+
サンプルのようなメソッドのフック処理程度であれば<br />
:reobfに出力されたクラスファイル名を見てTutorialTransformerを適宜書き換えましょう。
+
Eclipse等のByteCodeOutlineプラグインなどで、改変部分のコードを直接ASM形式のコードに変換して<br />
 +
難読化される部分を書きかえる程度で、そのまま使える場合もあります。
  
:※置き変えてしまうので、同一のクラスを置き買えるものとは競合してしまいます。
+
※ByteCode操作のばあい、同一クラスにに複数の改変を施すことができるため、競合させずにそれぞれの改変を施すこともできます。
  
;ASMライブラリを用いたクラスの部分改変を行う場合
+
*雑記<br />
: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:その他]]

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

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

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

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