Modding
MODを自作したい人向けの内容です。
作れるだけ作っておくので後で誰かきれいにまとめてね(投げやり)。
これ見てもよく分からない場合は英語版wikiかフォーラム見ることをオススメします。
ディレクトリまわり
SteamWorkshopのMOD格納場所
Steam Workshopから導入したMODは大抵の場合、
Cドライブ直下にインストールしているなら
C:\Program Files (x86)\Steam\steamapps\workshop\content\294100
Cドライブ以外の場合は
ドライブ名:\SteamLibrary\steamapps\workshop\content\294100
に入ってると思います。
またMacでは
/User/ユーザー名/Library/Application Support/Steam/steamapps/workshop/content/294100
(隠しファイルを表示する設定にすれば辿り着けます)
に入ってるはずです
自作MODの格納場所
Rimworldをインストールしたフォルダ内にModsというフォルダがあるので
その中に作ったMODを入れて下さい。
ディレクトリ的には以下となります。
C:\Program Files (x86)\Steam\steamapps\common\RimWorld\Mods
Cドライブ以外の場合は
ドライブ名:\SteamLibrary\steamapps\common\RimWorld\Mods
Macでは
/User/ユーザー名/Library/Application Support/Steam/steamapps/common/RimWorld/RimWorld.app/Mods
※Modsフォルダ内にCoreフォルダがありますがこちらには入れないで下さい。
補足
MODを導入していれば294100フォルダ内に色々な数字フォルダが追加されます。
この数字がWorkshopにアップロードしたMODの登録番号となります。
※例えば294100はRimworldのアプリ番号(ストアページのURL末尾番号と同じ)です。なので
steamcommunity.com/sharedfiles/filedetails/?id=××××
この××××に入るのが294100フォルダ内の番号フォルダ名を入れてあげると
そのMODのWorkshopページに飛ぶことが出来ます。
※他人が作ったMODを解析したい場合はこの方法で探すといいです。
それ以外でMODの作り方を模索する場合はRimWorld\Mods\Core内(特にDefs)を見てみると良いです。
MODの構成
MODのフォルダ構成は以下のようになります。
絶対に必要なものは赤字にて記述。
YourModName ├About │├About.xml │└Preview.png ├Assemblies │└ProjectName.dll ├Defs │└(..) ├Languages │└English │ └Strings │ └NameBanks ├Sounds ├Source │└ProjectName │ └ProjectName.sln └Textures
一連のフォルダをModsフォルダに入れることでMODとして認識されます。
※厳密には明確な決まりはありませんがこう分けておくと他者から分かりやすいようです。
但しTexPath等のパスまわりの話が出てくると一概にはこうではないのかも?
- YourModName
- これは自分が作ったフォルダ名を記述しておけば良いです。
Workshopへアップロードした後は上項の数字フォルダに名前が置き換わります。
- About
- ここに自分が作ったMODの名称、説明文、バージョン表記などの説明が入ります。
- Assembly/とSource/
- C#でプログラミングを行う場合のみ使用します。
Sourceフォルダは(ライセンスによっては必要になりますが)配布するときにはなくても構いません。
- Defs/
- ここにXMLで記述したソースコードが入ります。
自作のアイテム等を追加する場合はほぼこのフォルダは必須です。
- Languages/
- 言語を追加する場合はここに入れます。
- Sounds/とTextures/
- 音声や画像を付けたい場合はここに入れます。
自作MODのアップロード方法
- 自作MODをModsフォルダに突っ込み、バグ無く動作することを確認する
- メイン画面のオプションボタンから開発者モードにチェックを付ける
- MODから自作MODを選択し、Upload on Steam Workshopを押す
- 説明に従ってアップロードを実行する
- Workshopの内容と公開設定を設定し完了
XMLのみでのModの作成
作例:髪型追加MODを作る
必要なもの
MODフォルダ
- About
- About.xml
- Preview.png
- Defs
- 適当な名前.xml
- Textures
- Hairs
- ○○○○_back.png
- ○○○○_front.png
- ○○○○_side.png
- Hairs
About.xml
<?xml version="1.0" encoding="utf-8"?> <ModMetaData> <name>MOD名称</name> <author>作者名</author> <url>WorkshopのURLなど</url> <targetVersion>バージョン表記</targetVersion> <description>MODの説明文</description> </ModMetaData>
- name
- MODの名前を入れて下さい。
- author
- MOD作者名を入れて下さい。
- url
- フォーラムかワークショップのURLを入れるのが一般的です。
- targetVersion
- 動作させるRimworldのバージョンを記入して下さい。
A16で動作させるのであれば現行の0.16.1393と書いておけばいいでしょう。
このバージョン表記が使用バージョンと合ってないとMODを動かせません。
逆にいえば作者の更新が止まっていてもこのバージョンを書き換えれば動くMODもあります。 - description
- MODの説明文を入れて下さい。
Preview.png
637*358のPNGファイルを入れて下さい。
適当な名前.xml
<?xml version="1.0" encoding="utf-8"?> <DefPackage-HairDef> <HairDef> <label>髪型の名前</label> <hairGender>男性用か女性用か共用か</hairGender> <texPath>Hairs/○○○○</texPath> <defName>defの名前</defName> <hairTags> <li>ヘアータグ</li> </hairTags> </HairDef> </DefPackage-HairDef>
- label
- 実際に表示される髪型の名前となります。
- hairGender
- この髪型が使える性別を記入します。
男性用ならMale、女性用ならFemale、共用ならAnyと記入。
※頭文字は必ず大文字で書いて下さい。
- defName
- labelで付けた名前と同じでも良いです。
但し複数追加する場合は名前に被ると不味いっぽい?
- hairTags
- 素生によって選ばれる模様?
いくつか種類がありますが判らなければ、Urbanと記入します。
他のタグにはPunk、Rural、Tribalなどがあります。
※頭文字は必ず大文字で書いて下さい。
○○○○_back_front_side.png
一つの髪型を追加するのに三枚の画像が必要です。
120*120(ビット深さ32bit)のPNG形式で作成して下さい。
- ○○○○_back.png …後ろ向き姿時の髪型
- ○○○○_front.png …前向き姿時の髪型
- ○○○○_side.png …横向き時の髪型
texPathは必ず○○○○で揃えて下さい。
C#を用いたModの作成
開発環境
C#でModを作成する際にオススメのIDE(統合開発環境)になります
IDE | 対応OS | 詳細 | ダウンロードページ |
---|---|---|---|
Visual Studio | Windows/Mac | 有償版、無償版、トライアル版がある。おそらく最も有名なIDE 小規模なチームや個人ではVisual Studio 2015を無料で利用できます Mac用には無料のPreview版がリリースされています | Download(Win) Download(Mac) |
SharpDevelop | Windows | C#用に作られたフリーのIDEです | Download |
Xamarin | Mac | 無償ですがDLに登録が求められます Macでのみの動作になります Visual Studio for Macと全く同じ機能になので好みの方をご利用ください | Download |
MonoDevelop | Windows/Mac/Linux | カスタマイズ可能なUIを備えたフリーのクロスプラットフォームIDE リンク先のWindows/Mac版はXamarin Studioなので Xamarinの登録が嫌な方はこちらを直接入れても良いかもしれません | Download |
開発の準備
新規Modを作る際は.NETのライブラリとして作成するか有志によってつくられたテンプレートを利用するかのどちらかです
ここではライブラリとして作成する方法を記載します
有志によってつくられたテンプレートは英語ですがLudeon公式フォーラムにありますのでこちらのリンクから参照してください
[Tutorial] (Alpha 15c) How to make a .dll-mod (Power Generation)
- プロジェクトの作成
- 前項の開発環境にあげたIDEなどC#を扱えるIDEを起動する。
- メニューバーにあるファイル(File)→新規(New)→ソリューション(Solution)を開く。
- C#や.NETにあるライブラリ(Library)かクラスライブラリ(Class Library)を選択する。(ポータブル(portable)は選択しない)
- Modの名前をつける
- 場所(location)にSourceディレクトリを指定する。
YourModName/Source
- 好みでソリューションのディレクトリを作成する(Create a directory for solution)/ソリューションのディレクトリ内にプロジェクトを作成する(Create a project within the solution directory)を無効にする
- Assembly-CSharp.dllとUnityEngine.dllを追加する
- 参照(References)をダブルクリックで開く
- .NET Assembly Browserのタブで"Browse..."をクリック
- Windowsでは、Steam版でCドライブ直下にインストールしているなら
C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin_Data\Managed
Cドライブ以外の場合はドライブ名:\SteamLibrary\steamapps\common\RimWorld\RimWorldWin_Data\Managed
Macでは/User/ユーザー名/Library/Application Support/Steam/steamapps/common/RimWorld/ RimWorld.app/Contents/Resources/Data/Managed
にあるAssembly-CSharp.dll UnityEngine.dll
を追加してOKを押す - 追加した.dllを右クリックして"Local copy"を無効にする
- ターゲットフレームワークを.NET 3.5に変更する
- プロジェクトをダブルクリックしてオプションを開きターゲットフレームワーク(Target framework)を.NET 3.5にする
- 1つのファイルのみをビルドする
- プロジェクトのオプションを開きDebug infomationをなし(None)にする
- アセンブリフォルダへ出力する
- プロジェクトのオプションを開き出力(Output)のディレクトリを..\..\Assemblies\(YourModName/Assemblies)に変更する
注意: Monoフレームワークを利用してるユーザー
.NET 4.xではRimWorldのModは使えないため.NET 3.5があるMonoが必要
しかし.NET 3.xはMonoのかなり古いバージョンから探す必要があるのでModding以外に.NET 3.5を要するものがないのであれば.csprojファイルをテキストエディタで開いてTargetFrameworkVersionをv3.5に変更し、Assembly-CSharp.dllと同じディレクトリにある
System.Core.dll System.Xml.Linq.dll
などを追加することで作れるようになります
また、プロジェクトのオプションにあるビルド(Build)→一般(General)のUse MSBuild build engine (recommended for this project type)を無効にしてください
HarmonyによるMOD開発
Harmonyとは
C#での開発時に、メソッド等の置き換えや、追加での実行を簡単に行えるようにするためのライブラリです。
基本的なパッチの当て方
基本的には、オリジナルのメソッドを指定し、そのメソッドの処理前後、またはメソッド内のコードを入れ替えることで変更を加えます。
パッチの方法にはクラスやメソッドに属性を指定する方法と、手動でメソッドを指定する方法があります。
パッチの種類には、Prefix、Postfix、Transpilerなどがあります。
他にもPrepare、Cleanupなどのオプションがありますが、良く分かっていないので何方か記事を作成していただけると幸いです。
- Prefix
Prefixでは、オリジナルのメソッドの処理の前に処理を割り込みます。
using System; using Verse; using HarmonyLib; using System.Reflection; namespace HarmonyTest { [StaticConstructorOnStartup] public static class Main { public static Main() { //他のMODと重複しない名称でHarmonyのインスタンスを作成します。 var harmony = new Harmony("com.harmony.rimworld.example"); //該当Assemblyに含まれるHarmonyPatch属性を持つクラスのパッチを一括実行します。 harmony.PatchAll(Assembly.GetExecutingAssembly()); } } //HarmonyPatch属性を使ってオリジナルのクラス、メソッド、メソッドの引数(オプション)を指定します。 [HarmonyPatch(typeof(WindowStack))] [HarmonyPatch("Add")] [HarmonyPatch(new Type[] { typeof(Window) })] public static class Harmony_WindowStack { //HarmonyPrefix属性を指定すると、オリジナルのメソッドの処理前にメソッドを処理します。 [HarmonyPrefix] public static void Prefix(Window window) { Log.Warning("Window: " + window); } } }
また、割り込みを行うメソッドの戻り値にboolを指定すると、オリジナルのメソッド(またはそれ以降のパッチメソッド)を処理しない様に出来ます。
この機能により、オリジナルのメソッドが実行されない、または別のMODのメソッドが処理されないことで、競合が起きる場合があります。
using System; using Verse; using HarmonyLib; using System.Reflection; namespace HarmonyTest { [StaticConstructorOnStartup] public static class Main { public static Main() { Harmony harmony = new Harmony("com.harmony.rimworld.example"); harmony.PatchAll(Assembly.GetExecutingAssembly()); } } [HarmonyPatch(typeof(WindowStack))] [HarmonyPatch("Add")] [HarmonyPatch(new Type[] { typeof(Window) })] public static class Harmony_WindowStack { [HarmonyPrefix] public static bool Prefix(Window window) { Log.Warning("Window: " + window); //trueを返すと処理を継続します。falseを返すと、それ以降のメソッドを処理しません。 return True; } } }
- Postfix
Postfixでは、オリジナルのメソッドの後に処理を追加します。
using System; using Verse; using HarmonyLib; using System.Reflection; namespace HarmonyTest { [StaticConstructorOnStartup] public static class Main { static Main() { Harmony harmony = new Harmony("com.harmony.rimworld.example"); harmony.PatchAll(Assembly.GetExecutingAssembly()); } } [HarmonyPatch(typeof(WindowStack))] [HarmonyPatch("Add")] [HarmonyPatch(new Type[] { typeof(Window) })] public static class Harmony_WindowStack { //HarmonyPostfix属性を指定すると、オリジナルのメソッドの処理後にパッチメソッドを処理します。 [HarmonyPostfix] public static void Postfix(Window window) { Log.Warning("Window: " + window); } } }
- Transpiler
Transpilerでは、オリジナルのメソッドのIL(C#の中間言語)に対して変更を加えることが出来ます。
例えば、特定のメソッド内でのみ一部のメソッドを別のメソッドに置き換えたり、フィールドを変更したりすることが出来ます。
using System; using System.Collections.Generic; using Verse; using HarmonyLib; using System.Reflection; using System.Reflection.Emit; namespace HarmonyTest { public static class ClassA { public static int TestOriginal(int prm) { return TestCalc(prm); } public static int TestOther(int prm) { return TestCalc(prm); } public static int TestCalc(int prm) { return prm * 2; } } [StaticConstructorOnStartup] public static class Main { static Main() { Harmony harmony = new Harmony("com.harmony.rimworld.example"); harmony.PatchAll(Assembly.GetExecutingAssembly()); Log.Message(ClassA.TestOriginal(2).ToString()); //TestOriginal内のTestCalcをTestCalcPに置き換えているので、1になります。 Log.Message(ClassA.TestOther(2).ToString()); //TestOther内ではTestCalcがそのまま実行されるので、4になります。 } } [HarmonyPatch(typeof(ClassA))] [HarmonyPatch("TestOriginal")] public static class Harmony_ClassA { //HarmonyTranspiler属性を指定すると、メソッドのILコードの編集を行うことが出来ます。 //引数はオリジナルのILコード、戻値は編集後のILコードです。 [HarmonyTranspiler] public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> codes = new List<CodeInstruction>(instructions); //変更したいメソッドを指定 MethodInfo method = AccessTools.Method(typeof(ClassA), "TestCalc"); //変更したいメソッドの位置を検索 int pos = codes.FindIndex(c => c.opcode == OpCodes.Callvirt && c.operand != null && (MethodInfo)c.operand == method); //メソッドをTestCalcPに置き換え //※引数も変更する場合、引数の変更処理も追加する必要がある codes[pos] = new CodeInstruction(OpCodes.Callvirt, AccessTools.Method(typeof(Harmony_ClassA), "TestCalcP")); //ILコードを返す foreach (CodeInstruction code in codes) { yield return code; } } public static int TestCalcP(int prm) { return prm / 2; } } }
しかし、他のMOD内の処理を変更しないので逆に競合の原因になることもあります。
また、元のメソッドをそのまま書き換えるため、理論上はC#で可能な変更であれば何でも出来ます。
例えば、オリジナルのメソッド内に定義されているパラメータを変更したりする場合、Prefixではコードを全てコピーする必要があり、非効率です。
Transpilerであればパラメータのコードを書き換えるだけで実行変更することが出来ます。
ただし、ILは中間言語である以上可読性が低いことに注意して下さい。
その他のメリットとして、他作者のMODにTranspilerで変更を加えると言ったことも出来ます。- Tips:
ILの確認はVisual Studio付属のildasm.exeなどで確認出来ます。詳細は検索すれば詳細な解説サイトが出てくるのでそちらを確認して下さい。
実際にはやりたいことのコードを書いてILをildasm.exeで確認、それを改変して実装という流れが現実的かと思います。
Transpilerの実装時にはOpCodesの書き間違い等に注意して下さい。
- Tips:
- 各パッチの処理の順序
各メソッドをPrefix1、Prefix2、Original、Postfix1、Postfix2、Transpiler1、Transpiler2とすると、各メソッドは以下の様な順序で実行されます。
//コンストラクタ内など OriginalのIL = Transpiler1(OriginalのIL); OriginalのIL = Transpiler2(OriginalのIL); //Transpiler1と同じ部分にパッチを当てるか、矛盾する処理内容だと競合します。 //各処理 flg = Prefix1(); //戻り値がboolの場合、Trueなら継続。 if(flg) flg = Prefix2(); //優先順位が低いPrefixパッチは無視される場合がある。 if(flg) Original(); Postfix1(); //Postfixは全て必ず実行 Postfix2();
手動パッチ(MethodInfoを利用したパッチ)
以上は属性を利用したパッチの当て方でしたが、メソッド情報を取得して手動でパッチを当てることも出来ます。
using System; using Verse; using HarmonyLib; using System.Reflection; namespace HarmonyTest { [StaticConstructorOnStartup] public static class Main { public static Main() { var harmony = HarmonyInstance.Create("com.harmony.rimworld.example"); //AccessToolでオリジナルのメソッド、変更先のメソッドを取得します。 var original = AccessTools.Method(typeof(WindowStack), "Add"); var prefix = new HarmonyMethod((typeof(Harmony_WindowStack), "Prefix")); //パッチ対象を指定して実行します。Prefixは第2引数、Postfixは第3、Transpilerは第4引数です。 harmony.Patch(original,prefix,null,null); } } public static class Harmony_WindowStack { public static void Prefix(Window window) { Log.Warning("Window: " + window); } } }
- 動的パッチ
手動でパッチを当てるメリットは動的にパッチ対象を変更出来ることです。
例えば、コードをどこかに保存しておけばTranspilerで変更したコードをMODの設定によって元に戻したり、
設定によってコードの内容を変更したり出来ます。
- MODロード状況の確認
動的にパッチを当てる最大のメリットは、特定のMOD(Assembly)がロードされている場合に対応出来ることです。
AccessToolsなどを使えば、dllを参照に入れる必要はなく、エラーも吐きません。
test.dllが読み込まれている場合だけパッチを行いたい場合。
test.dll内にnamespace Test、Class TestClassが定義されているとする。using System; using Verse; using HarmonyLib; using System.Reflection; namespace HarmonyTest { [StaticConstructorOnStartup] public static class Main { public static Main() { var harmony = HarmonyInstance.Create("com.harmony.rimworld.example"); //TypeByNameにはフルネーム(namespace名.Class名)を指定する var type = AccessTools.TypeByName("Test.TestClass"); if(type != null) { var original = AccessTools.Method(typeof(WindowStack), "Add"); var prefix = new HarmonyMethod((typeof(Harmony_WindowStack), "Prefix")); harmony.Patch(original,prefix,null,null); } } } public static class Harmony_WindowStack { public static void Prefix(Window window) { Log.Warning("Window: " + window); } } }
で該当のDefが存在するか確認すれば大体対応出来るかと思います。また、LoadedModManager.RunningModsに稼働中のMODの情報が入っている様なので、ここに該当のMOD名が入っていれば確認可能ではないかと思います。(未検証)
MOD名が分かっている場合、ModLister.HasActiveModWithNameを使えば容易に確認出来ます。
- Tips:
Assemblyのロード状況の確認だけであれば、AccessToolsを使わずにAssembly.Load(アセンブリ名)でも行えます。
また、クラスが入れ子になっている場合ドットではなくプラスで繋ぎます。
例
namespace TestのClass TestClass内にClass TestChildが定義されている場合。
var type = AccessTools.TypeByName("Test.TestClass+TestChild");
なお、ドットが含まれるnamespaceの場合はそのままドットを2回以上使って指定します。
- Tips:
- アクセス制限のあるクラスへのパッチ
対象のクラスにアクセス制限(internalやprivate指定)がある場合、属性ではクラスが指定出来ません。
その場合もAccessTools.TypeByNameを使えばパッチを当てることが出来ます。
test.dll内のTestClass.Add(int)メソッドにパッチを行いたい場合。
test.dll内にnamespace Test、internal Class TestClassが定義されているとする。using System; using Verse; using HarmonyLib; using System.Reflection; namespace HarmonyTest { [StaticConstructorOnStartup] public static class Main { public static Main() { var harmony = HarmonyInstance.Create("com.harmony.rimworld.example"); //TypeByNameであれば、アクセス制限を無視してTypeを読み込むことが出来る。 var type = AccessTools.TypeByName("Test.TestClass"); if(type != null) { var original = AccessTools.Method(type, "Add"); var prefix = new HarmonyMethod((typeof(Harmony_Test), "Prefix")); harmony.Patch(original,prefix,null,null); } } } public static class Harmony_Test { public static void Prefix(int val) { //処理 } } }
引数の指定
Prefix,Postfixで使用するメソッドには、元のメソッドと同じ引数の他、特殊な引数を指定することが出来ます。
指定時は_(アンダーバー)2個または3個を付けます。
- __instance
元のメソッドのクラスのインスタンスを指定できます。
型は元のメソッドのクラスを指定します。 - __result
元のメソッドの戻り値を取得します。(Prefixの場合、デフォルト値を取得します) - __stat
PrefixメソッドからPostfixメソッドに値を受け渡す際に使用します。 - ___フィールド
元のインスタンス(__instanceで指定する物と同じ)のプライベートフィールドにアクセス出来ます。
優先順位(オプション)
オプションとして、パッチを当てる優先順位を設定することが出来ます。
- HarmonyPriority属性
パッチを当てるメソッドにこの属性を設定すると、整数値によって処理順序が変わります。
[HarmonyPrefix] [HarmonyPriority(int.Max)] //整数値を指定、高い方が先に処理。 public static void Prefix(Window window) { Log.Warning("Window: " + window); }
- HarmonyBefore属性
パッチを当てるメソッドにこの属性を設定すると、指定したアセンブリ(Harmonyのインスタンスに指定する名前)より先に処理されます。
//別MOD内 Harmony harmony = new Harmony("anotherMOD")
//作成MOD内 [HarmonyPrefix] [HarmonyBefore("anotherMOD"))] //別のMODよりも先に処理したい場合に指定。 public static void Prefix(Window window) { Log.Warning("Window: " + window); }
- HarmonyAfter属性
パッチを当てるメソッドにこの属性を設定すると、指定したアセンブリ(Harmonyのインスタンスに指定する名前)より後に処理されます。
使い方はHamonyBeforeと同様です。
- 手動パッチの場合
HarmonyMethodクラスにpriority、before、afterというフィールドがあるのでそこに指定するか、コンストラクタで指定します。
効果は上記の各属性と同じです。
競合の防止
基本的な競合の防止策としては
- bool型Prefixを使用しない
戻り値にbool型のPrefixパッチを当て、Falseを返すと優先度が低い他のPrefixパッチやOriginalのメソッドを飛ばします。
必然的に飛ばされたPrefixパッチやOriginalのメソッドにTranspilerを当てている場合競合の原因になるので、なるべく使わない様にしましょう。 - Transpilerを使用し、不要な箇所にパッチが当たるのを防ぐ
PrefixやPostfixだと、Originalのメソッドを呼び出す全てのメソッドに影響が出ます。
他のMODのメソッドで想定外の処理をしていたりすると競合しますので、Transpilerを使用して特定のメソッドに限定することで競合を防ぐことが出来ます。
また、Prefixでパッチを当てた上で特定のCompが無い場合飛ばすような処理だと、オブジェクトが増えた際に動作が遅くなる原因にもなります。
一方で他のMODのメソッド内にも影響を与えたい場合、Transpilerでは該当のMODに対して変更を加えないため、競合の原因になる場合もあります。
どちらを使用するのが適切かは処理内容から判断して下さい。
FAQ
- 特定のMOD導入時だけパッチを当てたい
手動パッチ - MODロード状況の確認を参照
- internalやprivateのクラスにパッチが当てられない
手動パッチ - アクセス制限のあるクラスへのパッチを参照
エラーと原因
- Error in static constructor of ○○: System.TypeInitializationException: ~~ cannot be patched. Reason: Invalid IL code in (wrapper dynamic-method)
IL codeが間違っている。OpCodes等に間違いが無いか確認
- Error in static constructor of ○○: System.TypeInitializationException: ~~ System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentException: opcode
IL codeが間違っている。Operandの型やキャスト、またはOpCodesとOperandの組み合わせが合っているか確認
floatを指定すべきところでキャストなし整数を指定したりしても発生する。(Operandを自動でキャストしてくれない)
コメント
最新の10件を表示しています。コメントページを参照