提供: Minecraft Modding Wiki
この編集を取り消せます。
下記の差分を確認して、本当に取り消していいか検証してください。よろしければ変更を保存して取り消しを完了してください。
最新版 | 編集中の文章 | ||
1行目: | 1行目: | ||
+ | |||
{{前提MOD|reqmod="ForgeModLoader"}} | {{前提MOD|reqmod="ForgeModLoader"}} | ||
− | ここでは、coremodsフォルダに入れるタイプのmod作成方法を紹介します。 | + | ここでは、coremodsフォルダに入れるタイプのmod作成方法を紹介します。<br /> |
==概要== | ==概要== | ||
− | + | *coremodとは<br /> | |
− | + | 通常 Minecraft.jar 内に上書きが必要な変更を施すような mod(前提Modなど等)を<br /> | |
− | 通常 Minecraft.jar 内に上書きが必要な変更を施すような | + | coremods フォルダに入れるだけでインストールできるようにする機能です。 |
− | |||
ただし、既存書き換えのMODをそのまま coremod にすることは出来ません。 | ただし、既存書き換えのMODをそのまま coremod にすることは出来ません。 | ||
Mod作者自身が coremod として作成する必要があります。 | Mod作者自身が coremod として作成する必要があります。 | ||
− | + | *Classファイルの動的な変更<br /> | |
− | + | 通常コンパイル済みのClassファイルの内容は変更できません。<br /> | |
− | + | ですがCoremodsでは、JavaにClassファイルを読み込む機能ClassLoaderを乗っ取る機能があります。<br /> | |
− | + | 読み込む際に動的に変更を施す(後述のASMを使ったり、丸ごと別のクラスを読み込ませる)ことで<br /> | |
− | + | Minecraft.jar内(※Coremod部分以外のFMLコードも含む)および、mods内のzip等のClassファイルを直接上書きすることなく改変することを可能とします。 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
+ | *ASMライブラリ<br /> | ||
+ | ASMとは動的にClassファイルに変更を施すことできるバイトコード操作ライブラリのことです。<br /> | ||
+ | FMLをインストールするとバンドルされているので、別途ライブラリを添付することなく、そのまま利用が可能です。<br /> | ||
本チュートリアルでは、最低限の機能のみの実装のみを紹介するため、ASMライブラリ等の使い方については別途検索ください。 | 本チュートリアルでは、最低限の機能のみの実装のみを紹介するため、ASMライブラリ等の使い方については別途検索ください。 | ||
− | + | ※改変を施す部分のコードは、仮実装でありそのままでは動きません。<br /> | |
− | + | ソースコメントにしたがって必要な実装を施して書き換えましょう。 | |
− | |||
− | |||
− | |||
− | |||
− | + | ※パフォーマンスへの影響<br /> | |
− | + | この機能によってのロードは原則1回しか行われません<br /> | |
− | + | ロードされたクラスは、通常どおりコンパイルされたclassファイルとなんら違いはありません。<br /> | |
− | + | 施した改変内容以上のパフォーマンスへの影響はほぼ無いです。 | |
− | + | ==ソースの解説== | |
− | |||
− | + | 作成するソース<br /> | |
− | + | *TutorialCorePlugin.java<br /> | |
− | + | coremods読み込みの基点となります。 | |
− | + | *TutorialModContainer.java<br /> | |
− | + | ModLoaderにおけるmod_XX.classのバージョン情報等のみを格納するものです。<br /> | |
+ | FMLでは情報を格納するのにアノテーションを使うこともできますが<br /> | ||
+ | Coremodは読み込み方法が異なるので記載も別となっています。 | ||
+ | *TutorialTransformer.java<br/> | ||
+ | Classの改変機能を実装します。 | ||
+ | *META-INF/MANIFEST.MF | ||
+ | FMLがcoremodである事を認識するのに必要です。 | ||
===TutorialCorePluginクラス作成=== | ===TutorialCorePluginクラス作成=== | ||
+ | **CorePluginクラスを作成します。 | ||
− | + | mod_Tutorialソース | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<source lang="java"> | <source lang="java"> | ||
// パッケージは、クラス(ファイル名)の衝突を回避するために、汎用的ではないユニークなパッケージ名を使用しましょう。 | // パッケージは、クラス(ファイル名)の衝突を回避するために、汎用的ではないユニークなパッケージ名を使用しましょう。 | ||
75行目: | 59行目: | ||
import java.util.Map; | import java.util.Map; | ||
− | import | + | import cpw.mods.fml.relauncher.IFMLCallHook; |
− | import | + | import cpw.mods.fml.relauncher.IFMLLoadingPlugin; |
+ | import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions; | ||
− | // TransformerExclusions: | + | // TransformerExclusions.value: coremods でロードする際に参照されるため、パッケージ名と一致させてください。 |
− | // | + | // IFMLLoadingPlugin: Coremods の基礎インタフェース |
− | // | + | // IFMLCallHook: Coremods 内で、coremod 自身のパス等を取得する等の際に必要となります。 |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | @TransformerExclusions({"tutorial.asm"}) | + | @TransformerExclusions(value={"tutorial.asm"}) |
− | public class TutorialCorePlugin implements IFMLLoadingPlugin | + | public class TutorialCorePlugin implements IFMLLoadingPlugin, IFMLCallHook |
{ | { | ||
− | // coremod | + | // coremod 自身のファイルパスの保存用 |
− | + | public static File location; | |
− | + | ||
− | static File location; | + | // 今回は使用しません |
− | |||
− | |||
− | |||
− | // | ||
− | |||
@Override | @Override | ||
public String[] getLibraryRequestClass() | public String[] getLibraryRequestClass() | ||
106行目: | 80行目: | ||
} | } | ||
− | // Class | + | // Class の改変機能を実装したクラスの完全修飾名を返します。 |
− | |||
− | |||
@Override | @Override | ||
public String[] getASMTransformerClass() | public String[] getASMTransformerClass() | ||
115行目: | 87行目: | ||
} | } | ||
− | // coremod | + | // coremod の名前やバージョン情報の格納クラスの完全修飾名を返します。 |
− | |||
@Override | @Override | ||
public String getModContainerClass() | public String getModContainerClass() | ||
123行目: | 94行目: | ||
} | } | ||
− | // | + | // coremod 読み込みの基点クラスの完全修飾名を返します。 |
− | |||
− | |||
@Override | @Override | ||
public String getSetupClass() | public String getSetupClass() | ||
{ | { | ||
− | return | + | return "tutorial.asm.TutorialCorePlugin"; |
} | } | ||
− | // | + | // IFMLCallHook のメソッドです。 |
− | // 今回は coremod 自身の jar | + | // 今回は coremod 自身の jar ファイルパスを取得します。 |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
@Override | @Override | ||
public void injectData(Map<String, Object> data) | public void injectData(Map<String, Object> data) | ||
148行目: | 110行目: | ||
location = (File) data.get("coremodLocation"); | location = (File) data.get("coremodLocation"); | ||
} | } | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public Void call() throws Exception | ||
+ | { | ||
+ | return null; | ||
} | } | ||
} | } | ||
154行目: | 122行目: | ||
===TutorialModContainerクラス作成=== | ===TutorialModContainerクラス作成=== | ||
− | + | **ModContainerクラスを作成します。 | |
− | + | バージョン情報のみで良いため、DummyModContainerクラスを継承して作成しています。 | |
− | + | FML形式のModではアノテーションを利用して、情報を記載できますが<br /> | |
− | + | coremods用途の場合、通常のアノテーションやmod_XX.class形式のmod読み込み処理より前に取得されるため<br /> | |
− | + | 別の形式で記載することが必要になっています。 | |
− | |||
− | |||
− | |||
<source lang="java"> | <source lang="java"> | ||
172行目: | 137行目: | ||
import com.google.common.eventbus.Subscribe; | import com.google.common.eventbus.Subscribe; | ||
− | import | + | import cpw.mods.fml.common.DummyModContainer; |
− | import | + | import cpw.mods.fml.common.LoadController; |
− | import | + | import cpw.mods.fml.common.ModMetadata; |
− | import | + | import cpw.mods.fml.common.event.FMLInitializationEvent; |
− | import | + | import cpw.mods.fml.common.versioning.ArtifactVersion; |
// 必ずしも DummyModContainer を継承している必要はありません。 | // 必ずしも DummyModContainer を継承している必要はありません。 | ||
− | // | + | // cpw.mods.fml.common.ModContainer さえ実装していれば、どんなクラスでも構いません。 |
public class TutorialModContainer extends DummyModContainer | public class TutorialModContainer extends DummyModContainer | ||
186行目: | 151行目: | ||
{ | { | ||
super(new ModMetadata()); | super(new ModMetadata()); | ||
+ | getMetadata(); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public List<ArtifactVersion> getDependencies() | ||
+ | { | ||
+ | return super.getDependencies(); | ||
+ | } | ||
− | // | + | @Override |
− | ModMetadata meta = getMetadata(); | + | public ModMetadata getMetadata() |
+ | { | ||
+ | // modの名前や、他のModと区別するための一意なID情報などを指定します。 | ||
+ | ModMetadata meta = super.getMetadata(); | ||
meta.modId = "transformertutorial"; | meta.modId = "transformertutorial"; | ||
197行目: | 173行目: | ||
meta.url = ""; | meta.url = ""; | ||
meta.credits = ""; | meta.credits = ""; | ||
− | + | ||
+ | return meta; | ||
} | } | ||
+ | |||
@Override | @Override | ||
public boolean registerBus(EventBus bus, LoadController controller) | public boolean registerBus(EventBus bus, LoadController controller) | ||
{ | { | ||
− | + | bus.register(this); | |
− | + | return true; | |
+ | } | ||
+ | |||
+ | @Subscribe | ||
+ | public void init(FMLInitializationEvent event) | ||
+ | { | ||
} | } | ||
} | } | ||
210行目: | 193行目: | ||
===TutorialTransformerクラス作成=== | ===TutorialTransformerクラス作成=== | ||
− | + | **Transformerクラスを作成します。 | |
− | + | IClassTransformerインタフェースを実装します。 | |
<source lang="java"> | <source lang="java"> | ||
222行目: | 205行目: | ||
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 | + | import cpw.mods.fml.relauncher.FMLRelauncher; |
− | import | + | import cpw.mods.fml.relauncher.IClassTransformer; |
− | public class TutorialTransformer implements IClassTransformer | + | // Opcodes : インプリメントすると、ASMによるバイトコード定数にアクセスするのに便利です。 |
+ | // 必須ではありません。不用な場合は implements から削除してください。 | ||
+ | |||
+ | public class TutorialTransformer implements IClassTransformer, Opcodes | ||
{ | { | ||
// 改変対象のクラスの完全修飾名です。 | // 改変対象のクラスの完全修飾名です。 | ||
233行目: | 227行目: | ||
// クラスがロードされる際に呼び出されるメソッドです。 | // クラスがロードされる際に呼び出されるメソッドです。 | ||
− | public byte[] transform( | + | @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行目: | 236行目: | ||
// name : 現在ロードされようとしているクラス名が格納されています。 | // name : 現在ロードされようとしているクラス名が格納されています。 | ||
− | if ( | + | if (!FMLRelauncher.side().equals("CLIENT") || !name.equals(TARGET_CLASS_NAME)) |
{ | { | ||
// 処理対象外なので何もしない | // 処理対象外なので何もしない | ||
249行目: | 244行目: | ||
try | try | ||
{ | { | ||
− | // | + | // -------------------------------------------------------------- |
− | return | + | // クラスファイル丸ごと差し替える場合 |
+ | // -------------------------------------------------------------- | ||
+ | // return replaceClass(bytes); | ||
+ | |||
+ | // -------------------------------------------------------------- | ||
+ | // ASMを使用し、既存のクラスファイルに改変を施す場合。 | ||
+ | // -------------------------------------------------------------- | ||
+ | // return hookDoRenderLivingMethod(bytes); | ||
+ | |||
} | } | ||
catch (Exception e) | catch (Exception e) | ||
257行目: | 260行目: | ||
} | } | ||
} | } | ||
− | |||
− | |||
− | + | // 下記の想定で実装されています。 | |
+ | // 対象クラスの 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> | ||
− | + | ===META-INF/MANIFEST.MFファイルの作成=== | |
− | + | META-INF/MANIFEST.MFファイルを作成します。 | |
+ | TutorialCorePluginのクラス名を指定します。 | ||
− | + | <source lang="java"> | |
− | + | Manifest-Version: 1.0 | |
− | + | FMLCorePlugin: tutorial.asm.TutorialCorePlugin | |
− | + | </source> | |
− | |||
− | + | ==jarパッケージにまとめる== | |
+ | 前述で作成したclassをjar形式にまとめます。<br /> | ||
+ | coremods用のmodではjar形式でなければ読み込まれません。 | ||
− | + | 今回の例では下記のようなファイル構成となります。 | |
− | + | *tutorial.jar | |
− | + | **META-INF | |
+ | ***MANIFEST.MF | ||
+ | **tutorial | ||
+ | ***asm | ||
+ | ****TutorialCorePlugin.class | ||
+ | ****TutorialModContainer.class | ||
+ | ****TutorialTransformer.class | ||
+ | **target.class (丸ごと差し替え例用) | ||
+ | |||
+ | 通常のmodのように、zipで圧縮し、jarに拡張子を変更すれば完成です。 | ||
− | == | + | ==動作確認== |
− | + | coremod形式のmodは、基本的にはEclipseでのデバッグでは読み込みが行われません。 | |
− | + | 機能の実装部分については、元ファイルを直接改変するなどしましょう。 | |
− | |||
− | |||
− | + | coremodとしての動作確認は、実際の動作環境へ放り込んで行うことになります。 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | ==難読化への対抗手段の解説== | |
− | + | *準備<br /> | |
− | + | 改変したいClassを通常通り書き換え。recompile>reobfuscateしてしまいます。 | |
− | + | *丸ごと差し替える場合<br /> | |
− | + | reobfに出力されたクラスファイル名を見てTutorialTransformerを適宜書き換えましょう。 | |
− | |||
− | |||
− | + | ※置き変えてしまうので、同一のクラスを置き買えるものとは競合してしまいます。 | |
− | |||
− | + | *ASMによる改変を行う場合<br /> | |
+ | reobfに出力されたクラスファイルと、改変前のクラスファイルを<br /> | ||
+ | JBVD等の適当なByteCodeViewerで開き、2つを比較して改変された部分を特定します。<br /> | ||
+ | それを元にASMのコードに置き変えTutorialTransformerを適宜かきかえましょう。 | ||
− | + | サンプルのようなメソッドのフック処理程度であれば<br /> | |
− | + | Eclipse等のByteCodeOutlineプラグインなどで、改変部分のコードを直接ASM形式のコードに変換して<br /> | |
− | + | 難読化される部分を書きかえる程度で、そのまま使える場合もあります。 | |
− | + | ※ByteCode操作のばあい、同一クラスにに複数の改変を施すことができるため、競合させずにそれぞれの改変を施すこともできます。 | |
− | + | *雑記<br /> | |
− | |||
− | |||
メソッドコールの追加は今回の例のとおりですが<br /> | メソッドコールの追加は今回の例のとおりですが<br /> | ||
別な処理に置き変えるなど、Javaでできることはほぼなんでも出来ますが<br /> | 別な処理に置き変えるなど、Javaでできることはほぼなんでも出来ますが<br /> | ||
ASMライブラリの使用方法を別途調べる必要があります。(記述時点で、日本語解説はあまり多くありません。) | ASMライブラリの使用方法を別途調べる必要があります。(記述時点で、日本語解説はあまり多くありません。) | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<comments /> | <comments /> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
[[Category:その他]] | [[Category:その他]] |