ILPostProcessorによる定数書き換え入門
「Applibot Advent Calendar 2023」 8日目の記事になります。
前日は @mori_yuki さんの「電子工作初心者が電子楽器作ってみた」という記事でした!
はじめに
本記事ではILPostProcessorを用いた定数書き換えについて紹介していきます。
筆者は最近ILPostProcessorを使い始めたが、自由に使いこなせるレベルではないので、お見苦しい点も多々あるかと思います。
温かい目で見守っていただけますと幸いです。
ILPostProcessorについて
ILPostProcessorは、Unityが生成するILに対して手を加えることができる仕組みです。
Unityのコンパイル後に処理が実行され、特定の値の書き換えやメソッドの書き換えなどを行うことができます。
実行環境
- OS: macOS 12.4
- Unity: 2022.3.11f1(SILICON)
書き換え予定クラスの用意
Settings
├── ProjectDefine.cs
├── Settings.asmdef
└── TemporaryClass.cs
ProjectDefine.cs
今回書き換え予定のクラス
TemporaryClass.cs
MonoBehaviourを継承した動作確認用クラス
SceneのGameObjectにアタッチ
Settings.asmdef
ProjectDefineを含むAssemblyを作成する
書き換え予定のクラス
namespace Settings
{
public class ProjectDefine
{
/// <summary>
/// 書き換えたい文字列
/// </summary>
public string Value { get; } = "Hello World";
}
}
ILPostProcessorの下準備
Mono.Cecilの導入
UnityのPackage Manager Windowより
- [+]を押下
- 「Add package from git URL ...」を押下
-
com.unity.nuget.mono-cecil
を入力
クラスの準備
ILPostProcessor
├── ConstantRewritingPostProcessor.cs
├── ILPostProcessorUtility.cs
├── PostProcessorAssemblyResolver.cs
└── Unity.Constant.Rewriting.CodeGen.asmdef
ILPostProcessorUtility.cs
PostProcessorAssemblyResolver.cs
こちらの2ファイルは参考記事1より拝借いたしました
ConstantRewritingPostProcessor.cs
書き換え処置を記載していくクラス
Unity.Constant.Rewriting.CodeGen.asmdef
Unity.XXXX.CodeGenの名前で作成したasmdef
asmdefの設定
AutoReferenced
falseに設定
Override References
trueに設定
AssemblyDifinitionReferences
書き換えたいAssemblyの参照を追加
Assembly References
Mono.Cecil周りのDLLを追加
- Mono.Cecil.dll
- Mono.Cecil.Mdb.dll
- Mono.Cecil.Pdb.dll
- Mono.Cecil.Rocks.dll
Platform
Editorのみ有効
ILを読む
SharpLabを用いて、ProjectDefine.csを調べる
ProjectDefine.csのIL
.assembly _
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = (
01 00 08 00 00 00 00 00
)
.custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = (
01 00 01 00 54 02 16 57 72 61 70 4e 6f 6e 45 78
63 65 70 74 69 6f 6e 54 68 72 6f 77 73 01
)
.custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = (
01 00 07 01 00 00 00 00
)
.permissionset reqmin = (
2e 01 80 8a 53 79 73 74 65 6d 2e 53 65 63 75 72
69 74 79 2e 50 65 72 6d 69 73 73 69 6f 6e 73 2e
53 65 63 75 72 69 74 79 50 65 72 6d 69 73 73 69
6f 6e 41 74 74 72 69 62 75 74 65 2c 20 53 79 73
74 65 6d 2e 52 75 6e 74 69 6d 65 2c 20 56 65 72
73 69 6f 6e 3d 37 2e 30 2e 30 2e 30 2c 20 43 75
6c 74 75 72 65 3d 6e 65 75 74 72 61 6c 2c 20 50
75 62 6c 69 63 4b 65 79 54 6f 6b 65 6e 3d 62 30
33 66 35 66 37 66 31 31 64 35 30 61 33 61 15 01
54 02 10 53 6b 69 70 56 65 72 69 66 69 63 61 74
69 6f 6e 01
)
.hash algorithm 0x00008004 // SHA1
.ver 0:0:0:0
}
.class private auto ansi '<Module>'
{
} // end of class <Module>
.class public auto ansi beforefieldinit Settings.ProjectDefine
extends [System.Runtime]System.Object
{
.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
01 00 01 00 00
)
.custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
01 00 00 00 00
)
// Fields
.field private initonly string '<Value>k__BackingField'
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
01 00 00 00 00 00 00 00
)
// Methods
.method public hidebysig specialname
instance string get_Value () cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x20a2
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld string Settings.ProjectDefine::'<Value>k__BackingField'
IL_0006: ret
} // end of method ProjectDefine::get_Value
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x20aa
// Code size 19 (0x13)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldstr "Hello World"
IL_0006: stfld string Settings.ProjectDefine::'<Value>k__BackingField'
IL_000b: ldarg.0
IL_000c: call instance void [System.Runtime]System.Object::.ctor()
IL_0011: nop
IL_0012: ret
} // end of method ProjectDefine::.ctor
// Properties
.property instance string Value()
{
.get instance string Settings.ProjectDefine::get_Value()
}
} // end of class Settings.ProjectDefine
.class private auto ansi sealed beforefieldinit Microsoft.CodeAnalysis.EmbeddedAttribute
extends [System.Runtime]System.Attribute
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
01 00 00 00
)
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2050
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method EmbeddedAttribute::.ctor
} // end of class Microsoft.CodeAnalysis.EmbeddedAttribute
.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableAttribute
extends [System.Runtime]System.Attribute
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [System.Runtime]System.AttributeUsageAttribute::.ctor(valuetype [System.Runtime]System.AttributeTargets) = (
01 00 84 6b 00 00 02 00 54 02 0d 41 6c 6c 6f 77
4d 75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
72 69 74 65 64 00
)
// Fields
.field public initonly uint8[] NullableFlags
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
uint8 ''
) cil managed
{
// Method begins at RVA 0x2059
// Code size 24 (0x18)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ldarg.0
IL_0008: ldc.i4.1
IL_0009: newarr [System.Runtime]System.Byte
IL_000e: dup
IL_000f: ldc.i4.0
IL_0010: ldarg.1
IL_0011: stelem.i1
IL_0012: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags
IL_0017: ret
} // end of method NullableAttribute::.ctor
.method public hidebysig specialname rtspecialname
instance void .ctor (
uint8[] ''
) cil managed
{
// Method begins at RVA 0x2072
// Code size 15 (0xf)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ldarg.0
IL_0008: ldarg.1
IL_0009: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags
IL_000e: ret
} // end of method NullableAttribute::.ctor
} // end of class System.Runtime.CompilerServices.NullableAttribute
.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableContextAttribute
extends [System.Runtime]System.Attribute
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [System.Runtime]System.AttributeUsageAttribute::.ctor(valuetype [System.Runtime]System.AttributeTargets) = (
01 00 4c 14 00 00 02 00 54 02 0d 41 6c 6c 6f 77
4d 75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
72 69 74 65 64 00
)
// Fields
.field public initonly uint8 Flag
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
uint8 ''
) cil managed
{
// Method begins at RVA 0x2082
// Code size 15 (0xf)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ldarg.0
IL_0008: ldarg.1
IL_0009: stfld uint8 System.Runtime.CompilerServices.NullableContextAttribute::Flag
IL_000e: ret
} // end of method NullableContextAttribute::.ctor
} // end of class System.Runtime.CompilerServices.NullableContextAttribute
.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.RefSafetyRulesAttribute
extends [System.Runtime]System.Attribute
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [System.Runtime]System.AttributeUsageAttribute::.ctor(valuetype [System.Runtime]System.AttributeTargets) = (
01 00 02 00 00 00 02 00 54 02 0d 41 6c 6c 6f 77
4d 75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
72 69 74 65 64 00
)
// Fields
.field public initonly int32 Version
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
int32 ''
) cil managed
{
// Method begins at RVA 0x2092
// Code size 15 (0xf)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ldarg.0
IL_0008: ldarg.1
IL_0009: stfld int32 System.Runtime.CompilerServices.RefSafetyRulesAttribute::Version
IL_000e: ret
} // end of method RefSafetyRulesAttribute::.ctor
} // end of class System.Runtime.CompilerServices.RefSafetyRulesAttribute
今回書き換えたいHello World
は.ctorメソッド内ldstrに存在するため、そちらを書き換える方針で進める
ILについての理解を深める
今回のILについて
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
publicなコンストラクタメソッド(.ctor
)を定義している
IL_0000: ldarg.0
0番目の引数(つまり、このコンストラクタメソッド自身のインスタンス)を評価スタックにロード
IL_0001: ldstr "Hello World"
文字列"Hello World"を評価スタックにロード
IL_0006: stfld string Settings.ProjectDefine::'<Value>k__BackingField'
評価スタックの最上位にある値(つまり、"Hello World")をフィールド'<Value>k__BackingField'
に保存
IL_000b: ldarg.0
再度、0番目の引数(つまり、このコンストラクタメソッド自身のインスタンス)を評価スタックにロード
IL_000c: call instance void [System.Runtime]System.Object::.ctor()
基底クラスSystem.Object
のコンストラクタを呼び出す
すべての.NETクラスが暗黙的にSystem.Object
を継承するための一般的なパターンである
IL_0011: nop
何もしない命令(no operation)
デバッガがブレークポイントを設定できるようにするためにしばしば使用される
IL_0012: ret
メソッドの終了と戻り値の返却を示す
.ctorについての理解を深める
.ctorとは、コンストラクターである
ProjectDefine.cs
ではコンストラクタが定義されていないが、C#のクラスでは、明示的にコンストラクターを書かなくても、暗黙的にコンストラクターが1つ作られる
プロパティ初期化を定義した際はコンストラクタで初期化されるっぽい
PostProcessorの作成
定数書き換えのPostProcessor
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil.Cil;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace Constant.Rewriting.CodeGen
{
public class ConstantRewritingPostProcessor : ILPostProcessor
{
public override ILPostProcessor GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly)
{
// Settings Assemblyのみを対象とする
return compiledAssembly.Name == "Settings";
}
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
if (!WillProcess(compiledAssembly))
{
return null;
}
using var assembly = ILPostProcessUtility.AssemblyDefinitionFor(compiledAssembly);
var diagnosticMessageList = new List<DiagnosticMessage>();
foreach (var type in assembly.MainModule.Types)
{
// Settings.ProjectDefine型を探す
if (type.FullName != "Settings.ProjectDefine")
{
continue;
}
// Settings.ProjectDefineのコンストラクタを取得する
var methodDefinition = type.Methods.FirstOrDefault((method) => method.Name == ".ctor");
if (methodDefinition == null)
{
diagnosticMessageList.Add(new DiagnosticMessage()
{
MessageData = ".ctorが見つかりませんでした。",
DiagnosticType = DiagnosticType.Warning
});
continue;
}
var postProcessor = methodDefinition.Body.GetILProcessor();
// ldstrを取得する
var ldstrInstruction = postProcessor.Body.Instructions.FirstOrDefault((instruction) => instruction.OpCode == OpCodes.Ldstr);
if (ldstrInstruction == null)
{
diagnosticMessageList.Add(new DiagnosticMessage()
{
MessageData = "ldstrが見つかりませんでした。",
DiagnosticType = DiagnosticType.Warning
});
continue;
}
// ldstrのオペランドを書き換える
ldstrInstruction.Operand = "Hello ILPostProcessor";
return ILPostProcessUtility.GetResult(assembly, diagnosticMessageList);
}
return new ILPostProcessResult(null, diagnosticMessageList);
}
}
}
ProjectDefine
のコンストラクタ.ctor
にてプロパティが初期化されているため、SettingsのAssemblyからこちらを検索
LdstrのオペランドをHello ILPostProcessor
に書き換えた
結果
実行時に書き変わっていることを確認
検証
ProjectDefine.cs
に複数の定義が存在する場合のILについて検証する
定義が2つ存在する場合の ProjectDefine
namespace Settings
{
public class ProjectDefine
{
/// <summary>
/// 書き換えなくてもいい文字列
/// </summary>
public string Value2 { get; } = "Hello Japan";
/// <summary>
/// 書き換えたい文字列
/// </summary>
public string Value { get; } = "Hello World";
}
}
ProjectDefine.csのIL
.assembly _
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = (
01 00 08 00 00 00 00 00
)
.custom instance void [System.Runtime]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = (
01 00 01 00 54 02 16 57 72 61 70 4e 6f 6e 45 78
63 65 70 74 69 6f 6e 54 68 72 6f 77 73 01
)
.custom instance void [System.Runtime]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggableAttribute/DebuggingModes) = (
01 00 07 01 00 00 00 00
)
.permissionset reqmin = (
2e 01 80 8a 53 79 73 74 65 6d 2e 53 65 63 75 72
69 74 79 2e 50 65 72 6d 69 73 73 69 6f 6e 73 2e
53 65 63 75 72 69 74 79 50 65 72 6d 69 73 73 69
6f 6e 41 74 74 72 69 62 75 74 65 2c 20 53 79 73
74 65 6d 2e 52 75 6e 74 69 6d 65 2c 20 56 65 72
73 69 6f 6e 3d 37 2e 30 2e 30 2e 30 2c 20 43 75
6c 74 75 72 65 3d 6e 65 75 74 72 61 6c 2c 20 50
75 62 6c 69 63 4b 65 79 54 6f 6b 65 6e 3d 62 30
33 66 35 66 37 66 31 31 64 35 30 61 33 61 15 01
54 02 10 53 6b 69 70 56 65 72 69 66 69 63 61 74
69 6f 6e 01
)
.hash algorithm 0x00008004 // SHA1
.ver 0:0:0:0
}
.class private auto ansi '<Module>'
{
} // end of class <Module>
.class public auto ansi beforefieldinit Settings.ProjectDefine
extends [System.Runtime]System.Object
{
.custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
01 00 01 00 00
)
.custom instance void System.Runtime.CompilerServices.NullableAttribute::.ctor(uint8) = (
01 00 00 00 00
)
// Fields
.field private initonly string '<Value2>k__BackingField'
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
01 00 00 00 00 00 00 00
)
.field private initonly string '<Value>k__BackingField'
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
01 00 00 00 00 00 00 00
)
// Methods
.method public hidebysig specialname
instance string get_Value2 () cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x20a2
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld string Settings.ProjectDefine::'<Value2>k__BackingField'
IL_0006: ret
} // end of method ProjectDefine::get_Value2
.method public hidebysig specialname
instance string get_Value () cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x20aa
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld string Settings.ProjectDefine::'<Value>k__BackingField'
IL_0006: ret
} // end of method ProjectDefine::get_Value
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x20b2
// Code size 30 (0x1e)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldstr "Hello Japan"
IL_0006: stfld string Settings.ProjectDefine::'<Value2>k__BackingField'
IL_000b: ldarg.0
IL_000c: ldstr "Hello World"
IL_0011: stfld string Settings.ProjectDefine::'<Value>k__BackingField'
IL_0016: ldarg.0
IL_0017: call instance void [System.Runtime]System.Object::.ctor()
IL_001c: nop
IL_001d: ret
} // end of method ProjectDefine::.ctor
// Properties
.property instance string Value2()
{
.get instance string Settings.ProjectDefine::get_Value2()
}
.property instance string Value()
{
.get instance string Settings.ProjectDefine::get_Value()
}
} // end of class Settings.ProjectDefine
.class private auto ansi sealed beforefieldinit Microsoft.CodeAnalysis.EmbeddedAttribute
extends [System.Runtime]System.Attribute
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
01 00 00 00
)
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x2050
// Code size 8 (0x8)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method EmbeddedAttribute::.ctor
} // end of class Microsoft.CodeAnalysis.EmbeddedAttribute
.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableAttribute
extends [System.Runtime]System.Attribute
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [System.Runtime]System.AttributeUsageAttribute::.ctor(valuetype [System.Runtime]System.AttributeTargets) = (
01 00 84 6b 00 00 02 00 54 02 0d 41 6c 6c 6f 77
4d 75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
72 69 74 65 64 00
)
// Fields
.field public initonly uint8[] NullableFlags
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
uint8 ''
) cil managed
{
// Method begins at RVA 0x2059
// Code size 24 (0x18)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ldarg.0
IL_0008: ldc.i4.1
IL_0009: newarr [System.Runtime]System.Byte
IL_000e: dup
IL_000f: ldc.i4.0
IL_0010: ldarg.1
IL_0011: stelem.i1
IL_0012: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags
IL_0017: ret
} // end of method NullableAttribute::.ctor
.method public hidebysig specialname rtspecialname
instance void .ctor (
uint8[] ''
) cil managed
{
// Method begins at RVA 0x2072
// Code size 15 (0xf)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ldarg.0
IL_0008: ldarg.1
IL_0009: stfld uint8[] System.Runtime.CompilerServices.NullableAttribute::NullableFlags
IL_000e: ret
} // end of method NullableAttribute::.ctor
} // end of class System.Runtime.CompilerServices.NullableAttribute
.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.NullableContextAttribute
extends [System.Runtime]System.Attribute
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [System.Runtime]System.AttributeUsageAttribute::.ctor(valuetype [System.Runtime]System.AttributeTargets) = (
01 00 4c 14 00 00 02 00 54 02 0d 41 6c 6c 6f 77
4d 75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
72 69 74 65 64 00
)
// Fields
.field public initonly uint8 Flag
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
uint8 ''
) cil managed
{
// Method begins at RVA 0x2082
// Code size 15 (0xf)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ldarg.0
IL_0008: ldarg.1
IL_0009: stfld uint8 System.Runtime.CompilerServices.NullableContextAttribute::Flag
IL_000e: ret
} // end of method NullableContextAttribute::.ctor
} // end of class System.Runtime.CompilerServices.NullableContextAttribute
.class private auto ansi sealed beforefieldinit System.Runtime.CompilerServices.RefSafetyRulesAttribute
extends [System.Runtime]System.Attribute
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void Microsoft.CodeAnalysis.EmbeddedAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [System.Runtime]System.AttributeUsageAttribute::.ctor(valuetype [System.Runtime]System.AttributeTargets) = (
01 00 02 00 00 00 02 00 54 02 0d 41 6c 6c 6f 77
4d 75 6c 74 69 70 6c 65 00 54 02 09 49 6e 68 65
72 69 74 65 64 00
)
// Fields
.field public initonly int32 Version
// Methods
.method public hidebysig specialname rtspecialname
instance void .ctor (
int32 ''
) cil managed
{
// Method begins at RVA 0x2092
// Code size 15 (0xf)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call instance void [System.Runtime]System.Attribute::.ctor()
IL_0006: nop
IL_0007: ldarg.0
IL_0008: ldarg.1
IL_0009: stfld int32 System.Runtime.CompilerServices.RefSafetyRulesAttribute::Version
IL_000e: ret
} // end of method RefSafetyRulesAttribute::.ctor
} // end of class System.Runtime.CompilerServices.RefSafetyRulesAttribute
ldstrが2つ存在するため、上述のPostProcessorではValue
, Value2
のうち最初に検索されたldstrのみが書き変わる
そこで、stfldを判定に使用する方法を考える
PostProcessorの作成 その2
定数書き換えのPostProcessor
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil.Cil;
using Settings;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace Constant.Rewriting.CodeGen
{
public class ConstantRewritingPostProcessor : ILPostProcessor
{
public override ILPostProcessor GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly)
{
// Settings Assemblyのみを対象とする
return compiledAssembly.Name == "Settings";
}
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
if (!WillProcess(compiledAssembly))
{
return null;
}
using var assembly = ILPostProcessUtility.AssemblyDefinitionFor(compiledAssembly);
var diagnosticMessageList = new List<DiagnosticMessage>();
foreach (var type in assembly.MainModule.Types)
{
// Settings.ProjectDefine型を探す
if (type.FullName != "Settings.ProjectDefine")
{
continue;
}
// Settings.ProjectDefineのコンストラクタを取得する
var methodDefinition = type.Methods.FirstOrDefault((method) => method.Name == ".ctor");
if (methodDefinition == null)
{
diagnosticMessageList.Add(new DiagnosticMessage()
{
MessageData = ".ctorが見つかりませんでした。",
DiagnosticType = DiagnosticType.Warning
});
continue;
}
var postProcessor = methodDefinition.Body.GetILProcessor();
for (int i = 0; i < postProcessor.Body.Instructions.Count; i++)
{
var instruction = postProcessor.Body.Instructions[i];
// 命令のオペコードがStfldでない場合はスキップ
if (instruction.OpCode != OpCodes.Stfld)
{
continue;
}
// 命令のオペランドにValueが含まれていない場合はスキップ
if (!instruction.Operand.ToString().Contains($"<{nameof(ProjectDefine.Value)}>k__BackingField"))
{
continue;
}
// 含まれている場合、一つ前の命令を取得し、オペコードがLdstrでない場合はスキップ
var prevInstruction = postProcessor.Body.Instructions[i - 1];
if (prevInstruction.OpCode != OpCodes.Ldstr)
{
continue;
}
// 書き換え
prevInstruction.Operand = "Hello ILPostProcessor";
}
return ILPostProcessUtility.GetResult(assembly, diagnosticMessageList);
}
return new ILPostProcessResult(null, diagnosticMessageList);
}
}
}
stfldに<Value>k__BackingField
が含まれているかを判定に使用し、含まれている場合1つ前の命令のオペランドを変更するようにした
結果
書き換えたい箇所のみ書き換えられていることを確認
方針
書き換えたい値にAttributeをつける方針を取ってみる
アトリビュートクラスの用意
Settings
├── ProjectDefine.cs
├── RewritingAttribute.cs
├── Settings.asmdef
└── TemporaryClass.cs
RewritingAttribute.cs
を追加
書き換えたい箇所指定アトリビュートクラス
using System;
namespace Settings
{
[AttributeUsage(AttributeTargets.Property)]
public class RewritingAttribute : System.Attribute
{
}
}
アトリビュートをつけたProjectDefine.cs
using System;
namespace Settings
{
public class ProjectDefine
{
/// <summary>
/// 書き換えなくてもいい文字列
/// </summary>
public string Value2 { get; } = "Hello Japan";
/// <summary>
/// 書き換えたい文字列
/// </summary>
[Rewriting]
public string Value { get; } = "Hello World";
}
}
アトリビュートをつけたプロパティのみ書き換えられるように修正する
PostProcessorの作成 その3
定数書き換えのPostProcessor
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Settings;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace Constant.Rewriting.CodeGen
{
public class ConstantRewritingPostProcessor : ILPostProcessor
{
public override ILPostProcessor GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly)
{
// Settings Assemblyのみを対象とする
return compiledAssembly.Name == "Settings";
}
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
if (!WillProcess(compiledAssembly))
{
return null;
}
using var assembly = ILPostProcessUtility.AssemblyDefinitionFor(compiledAssembly);
var diagnosticMessageList = new List<DiagnosticMessage>();
foreach (var type in assembly.MainModule.Types)
{
foreach (var property in type.Properties)
{
if (!property.HasCustomAttributes)
{
continue;
}
var attribute = property.CustomAttributes.FirstOrDefault(attr => attr.AttributeType.FullName == typeof(RewritingAttribute).FullName);
if (attribute == null)
{
continue;
}
var constructors = type.GetConstructors();
foreach (var method in constructors)
{
var postProcessor = method.Body.GetILProcessor();
var instruction = postProcessor.Body.Instructions.FirstOrDefault(
inst => inst.Operand != null && inst.Operand.ToString().Contains($"<{property.Name}>k__BackingField")
);
if (instruction == null)
{
continue;
}
var prevInstruction = instruction.Previous;
if (prevInstruction.OpCode != OpCodes.Ldstr)
{
continue;
}
// 書き換え
prevInstruction.Operand = "Hello ILPostProcessor";
}
return ILPostProcessUtility.GetResult(assembly, diagnosticMessageList);
}
}
return new ILPostProcessResult(null, diagnosticMessageList);
}
}
}
アトリビュートがついているプロパティと同じ名前のBackingFieldがコンストラクタ内に存在するか確認し、存在する場合は1つ前の命令を取得するように変更した
アトリビュートからプロパティ名を取得するようにしたため、ProjectDefine
のValue
以外もアトリビュートをつければ書き換えられるようにした
結果
アトリビュートつけた箇所のみ書き換えられていることを確認
終わりに
本記事ではILPostProcessorで定数を書き換える方法について紹介しました。
今回実装した内容はGithub上で公開しておりますので、併せてご確認ください。
以上、「Applibot Advent Calendar 2023」 8日目の記事でした。
明日は@daikidev111さんです!
Discussion