summaryrefslogtreecommitdiff
path: root/StardewInjector/CecilUtils.cs
blob: acdf51984a276a81ac45d85c766ded1b5f166d36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace StardewInjector
{
    public struct ScannerState
    {
        public ILProcessor ILProcessor;
        public Instruction Instruction;

        public ScannerState(ILProcessor ilProc, Instruction ins)
        {
            ILProcessor = ilProc;
            Instruction = ins;
        }

        public ScannerState Previous(Func<Instruction, bool> until = null)
        {
            if (until != null)
            {
                Instruction cur = this.Instruction;
                do
                {
                    cur = cur.Previous;
                } while (!until(cur));
                return new ScannerState(this.ILProcessor, cur);
            }
            return new ScannerState(this.ILProcessor, Instruction.Previous);
        }

        public ScannerState Next(Func<Instruction, bool> until = null)
        {
            if (until != null)
            {
                Instruction cur = this.Instruction;
                do
                {
                    cur = cur.Next;
                } while (!until(cur));
                return new ScannerState(this.ILProcessor, cur);
            }
            return new ScannerState(this.ILProcessor, Instruction.Next);
        }

        public ScannerState Last()
        {
            var instructions = this.ILProcessor.Body.Instructions;
            return new ScannerState(this.ILProcessor, instructions[instructions.Count - 1]);
        }

        public ScannerState First()
        {
            var instructions = this.ILProcessor.Body.Instructions;
            return new ScannerState(this.ILProcessor, instructions[0]);
        }

        public ScannerState ReplaceCreate(OpCode opcode)
        {
            Instruction ins = this.ILProcessor.Create(opcode);
            this.ILProcessor.Replace(this.Instruction, ins);
            return new ScannerState(this.ILProcessor, ins);
        }

        public ScannerState ReplaceCreate(OpCode opcode, object arg)
        {
            Instruction ins = this.ILProcessor.Create(opcode, arg as dynamic);
            this.ILProcessor.Replace(this.Instruction, ins);
            return new ScannerState(this.ILProcessor, ins);
        }

        public ScannerState CreateBefore(OpCode opcode)
        {
            Instruction ins = this.ILProcessor.Create(opcode);
            this.ILProcessor.InsertBefore(this.Instruction, ins);
            return new ScannerState(this.ILProcessor, ins);
        }

        public ScannerState CreateBefore(OpCode opcode, object arg)
        {
            Instruction ins = this.ILProcessor.Create(opcode, arg as dynamic);
            this.ILProcessor.InsertBefore(this.Instruction, ins);
            return new ScannerState(this.ILProcessor, ins);
        }

        public ScannerState CreateAfter(OpCode opcode)
        {
            Instruction ins = this.ILProcessor.Create(opcode);
            this.ILProcessor.InsertAfter(this.Instruction, ins);
            return new ScannerState(this.ILProcessor, ins);
        }

        public ScannerState CreateAfter(OpCode opcode, object arg)
        {
            Instruction ins = this.ILProcessor.Create(opcode, arg as dynamic);
            this.ILProcessor.InsertAfter(this.Instruction, ins);
            return new ScannerState(this.ILProcessor, ins);
        }
    }

    public static class CecilUtils
    {
        public static ScannerState Scanner(this MethodDefinition me)
        {
            return new ScannerState(me.Body.GetILProcessor(), me.Body.Instructions[0]);
        }

        public static ScannerState FindSetField(this MethodDefinition me, string fieldName)
        {
            var instruction = me.Body.Instructions
                .FirstOrDefault(i => i.OpCode == OpCodes.Stsfld && (i.Operand as FieldDefinition).Name == fieldName);
            return new ScannerState(me.Body.GetILProcessor(), instruction);
        }

        public static ScannerState FindLoadField(this MethodDefinition me, string fieldName)
        {
            var instruction = me.Body.Instructions
                .FirstOrDefault(i => {
                    if (i.OpCode != OpCodes.Ldfld && i.OpCode != OpCodes.Ldsfld)
                        return false;
                    if (i.Operand is FieldDefinition && (i.Operand as FieldDefinition).Name == fieldName)
                        return true;
                    if (i.Operand is FieldReference && (i.Operand as FieldReference).Name == fieldName)
                        return true;
                    return false;
                });
            return new ScannerState(me.Body.GetILProcessor(), instruction);
        }

        public static ScannerState FindLoadConstant(this MethodDefinition me, int val)
        {
            var instruction = me.Body.Instructions
                .FirstOrDefault(i => i.OpCode == OpCodes.Ldc_I4 && (int)i.Operand == val);
            return new ScannerState(me.Body.GetILProcessor(), instruction);
        }

        public static ScannerState FindLoadConstant(this MethodDefinition me, float val)
        {
            var instruction = me.Body.Instructions
                .FirstOrDefault(i => i.OpCode == OpCodes.Ldc_R4 && (float)i.Operand == val);
            return new ScannerState(me.Body.GetILProcessor(), instruction);
        }

        public static MethodDefinition FindMethod(this ModuleDefinition me, string name)
        {
            var nameSplit = name.Split(new string[] { "::" }, StringSplitOptions.RemoveEmptyEntries);
            if (nameSplit.Length < 2)
                throw new ArgumentException("Invalid method full name", "name");

            var currentType = me.Types.FirstOrDefault(t => t.FullName == nameSplit[0]);
            if (currentType == null)
                return null;

            return currentType.Methods.FirstOrDefault(m => m.Name == nameSplit[1]);
        }

        public static FieldDefinition FindField(this ModuleDefinition me, string name)
        {
            var nameSplit = name.Split(new string[] { "::" }, StringSplitOptions.RemoveEmptyEntries);
            if (nameSplit.Length < 2)
                throw new ArgumentException("Invalid field full name", "name");

            var currentType = me.Types.FirstOrDefault(t => t.FullName == nameSplit[0]);
            if (currentType == null)
                return null;

            return currentType.Fields.FirstOrDefault(m => m.Name == nameSplit[1]);
        }
    }
}