• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SamboyCoding / Cpp2IL / 27506648608

14 Jun 2026 05:31PM UTC coverage: 35.351% (+0.07%) from 35.28%
27506648608

push

github

SamboyCoding
Decompiler: Fix simplifier replacing incorrect constants, add methodInfo resolution

2443 of 8050 branches covered (30.35%)

Branch coverage included in aggregate %.

1 of 36 new or added lines in 6 files covered. (2.78%)

6 existing lines in 2 files now uncovered.

4851 of 12583 relevant lines covered (38.55%)

238886.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/Cpp2IL.Core/Analysis/MetadataResolver.cs
1
using System.Linq;
2
using Cpp2IL.Core.Extensions;
3
using Cpp2IL.Core.Graphs;
4
using Cpp2IL.Core.Il2CppApiFunctions;
5
using Cpp2IL.Core.ISIL;
6
using Cpp2IL.Core.Model.Contexts;
7
using Cpp2IL.Core.Utils;
8
using LibCpp2IL;
9

10
namespace Cpp2IL.Core.Analysis;
11

12
public static class MetadataResolver
13
{
14
    public static void ResolveAll(MethodAnalysisContext method)
15
    {
16
        ResolveCalls(method);
×
17
        ResolveGetter(method);
×
NEW
18
        ResolveMetadataUsages(method);
×
19
    }
×
20

21
    /// <summary>
22
    /// Resolves <c>Move local, [absoluteAddress]</c> loads of IL2CPP metadata-usage globals into a
23
    /// strongly-typed operand: a string literal, a <see cref="TypeAnalysisContext"/> (an Il2CppType*/
24
    /// Il2CppClass* usage) or, for a MethodInfo* usage, a <see cref="RuntimeMethodInfoAnalysisContext"/>
25
    /// naming the method it refers to (also used to type the local - see <see cref="LocalVariables"/>).
26
    /// </summary>
27
    private static void ResolveMetadataUsages(MethodAnalysisContext method)
28
    {
NEW
29
        var libContext = method.AppContext.LibCpp2IlContext;
×
30

UNCOV
31
        foreach (var instruction in method.ControlFlowGraph!.Instructions)
×
32
        {
33
            if (instruction.OpCode != OpCode.Move)
×
34
                continue;
35

36
            // Only an absolute-address load [addr] (no base/index/scale) can be a metadata-usage global.
NEW
37
            if (instruction.Operands[0] is not LocalVariable
×
NEW
38
                || instruction.Operands[1] is not MemoryOperand { Base: null, Index: null, Scale: 0 } memory)
×
39
                continue;
40

NEW
41
            var address = (ulong)memory.Addend;
×
42

43
            // String literal.
NEW
44
            var stringLiteral = libContext.GetLiteralByAddress(address);
×
NEW
45
            if (stringLiteral != null)
×
46
            {
NEW
47
                instruction.Operands[1] = stringLiteral;
×
NEW
48
                continue;
×
49
            }
50

51
            // Type metadata usage (Il2CppType* / Il2CppClass*).
NEW
52
            if (method.DeclaringType is { } declaringType)
×
53
            {
NEW
54
                var typeContext = libContext.GetTypeGlobalByAddress(address)?.ToContext(declaringType.DeclaringAssembly);
×
NEW
55
                if (typeContext != null)
×
56
                {
NEW
57
                    instruction.Operands[1] = typeContext;
×
58
                    continue;
×
59
                }
60
            }
61

62
            // Method metadata usage (MethodInfo*). On metadata v27+ GetMethodGlobalByAddress can return
63
            // any global, so confirm it is actually a method before resolving - the resolver's switch
64
            // throws on other usage kinds.
NEW
65
            var methodUsage = libContext.GetMethodGlobalByAddress(address);
×
NEW
66
            if (methodUsage?.Type is MetadataUsageType.MethodDef or MetadataUsageType.MethodRef
×
NEW
67
                && method.AppContext.ResolveContextForMethod(methodUsage) is { DeclaringType: { } methodDeclaringType } methodContext)
×
NEW
68
                instruction.Operands[1] = new RuntimeMethodInfoAnalysisContext(methodContext, methodDeclaringType.DeclaringAssembly);
×
69
        }
70
    }
×
71

72
    /// <summary>
73
    /// Replaces every <c>[base + addend]</c> memory operand whose base is a typed local with a
74
    /// <see cref="FieldReference"/> to the field at that offset. Returns whether any operand was
75
    /// resolved this pass, so the type/field fixpoint can detect convergence: as more bases become
76
    /// typed (a field load types its result, which is the base of the next load), more offsets
77
    /// resolve, so this is re-run until it stops finding new fields.
78
    /// </summary>
79
    public static bool ResolveFieldOffsets(MethodAnalysisContext method)
80
    {
81
        var changed = false;
×
82

83
        foreach (var instruction in method.ControlFlowGraph!.Instructions)
×
84
        {
85
            for (var i = 0; i < instruction.Operands.Count; i++)
×
86
            {
87
                var operand = instruction.Operands[i];
×
88

89
                if (operand is not MemoryOperand memory)
×
90
                    continue;
91

92
                // Has to be [base (local) + addend (field offset)]
93
                if (memory.Index != null || memory.Scale != 0)
×
94
                    continue;
95

96
                if (memory.Base is not LocalVariable local || local?.Type == null)
×
97
                    continue;
98

99
                var field = local.Type.Fields.FirstOrDefault(f => f.BackingData?.FieldOffset == memory.Addend);
×
100

101
                if (field == null) // TODO: Support nested fields (Field1.Field2.Field3)
×
102
                    continue;
103

104
                instruction.Operands[i] = new FieldReference(field, local, (int)memory.Addend);
×
105
                changed = true;
×
106
            }
107
        }
108

109
        return changed;
×
110
    }
111

112
    private static void ResolveCalls(MethodAnalysisContext method)
113
    {
114
        foreach (var block in method.ControlFlowGraph!.Blocks)
×
115
        {
116
            if (block.BlockType != BlockType.Call && block.BlockType != BlockType.TailCall)
×
117
                continue;
118

119
            var callInstruction = block.Instructions[^1];
×
120
            var dest = callInstruction.Operands[0];
×
121

122
            if (!dest.IsNumeric())
×
123
                continue;
124

125
            var target = (ulong)dest;
×
126

127
            var keyFunctionAddresses = method.AppContext.GetOrCreateKeyFunctionAddresses();
×
128

129
            if (keyFunctionAddresses.IsKeyFunctionAddress(target))
×
130
            {
131
                HandleKeyFunction(method.AppContext, callInstruction, target, keyFunctionAddresses);
×
132
                continue;
×
133
            }
134

135
            //Non-key function call. Try to find a single match
136
            if (!method.AppContext.MethodsByAddress.TryGetValue(target, out var targetMethods))
×
137
                continue;
138

139
            // Several methods can share one address (identical native code merged by the linker, or
140
            // generic sharing). Those are left as a numeric target here and disambiguated later by
141
            // receiver type in ResolveAmbiguousCalls, once types are known.
142
            if (targetMethods is not [{ } singleTargetMethod])
×
143
                continue;
144

145
            callInstruction.Operands[0] = singleTargetMethod;
×
146
        }
147

148
        method.ControlFlowGraph.MergeCallBlocks();
×
149
    }
×
150

151
    /// <summary>
152
    /// Resolves calls whose address maps to more than one method by matching the receiver's known
153
    /// type against the candidates' declaring types. Runs inside the type/field fixpoint and so
154
    /// re-fires as receivers become typed - a resolved call types its return value, which can type
155
    /// the receiver of a further call. Returns whether any call was resolved this pass.
156
    ///
157
    /// Conservative by design: it commits only when exactly one non-static candidate's declaring
158
    /// type matches the receiver's type. Anything still untyped or ambiguous is left for a later
159
    /// pass, or left unresolved - it never guesses.
160
    /// </summary>
161
    public static bool ResolveAmbiguousCalls(MethodAnalysisContext method)
162
    {
163
        var changed = false;
×
164

165
        foreach (var instruction in method.ControlFlowGraph!.Instructions)
×
166
        {
167
            if (!instruction.IsCall)
×
168
                continue;
169

170
            var target = instruction.Operands[0];
×
171

172
            // A resolved call's target is a method/key-function name; only unresolved ones are still numeric.
173
            if (!target.IsNumeric())
×
174
                continue;
175

176
            if (!method.AppContext.MethodsByAddress.TryGetValue((ulong)target, out var candidates) || candidates.Count < 2)
×
177
                continue;
178

179
            if (GetReceiver(instruction) is not { Type: { } receiverType })
×
180
                continue;
181

182
            MethodAnalysisContext? match = null;
×
183
            var ambiguous = false;
×
184

185
            foreach (var candidate in candidates)
×
186
            {
187
                if (candidate.IsStatic || !ReferenceEquals(candidate.DeclaringType, receiverType))
×
188
                    continue;
189

190
                if (match != null)
×
191
                {
192
                    ambiguous = true;
×
193
                    break;
×
194
                }
195

196
                match = candidate;
×
197
            }
198

199
            if (ambiguous || match == null)
×
200
                continue;
201

202
            instruction.Operands[0] = match;
×
203
            changed = true;
×
204
        }
205

206
        return changed;
×
207
    }
208

209
    // The receiver ('this') of a call is the first integer-slot argument: operand 1 for CallVoid
210
    // (after the target), operand 2 for Call (after the target and the return value).
211
    private static LocalVariable? GetReceiver(Instruction call)
212
    {
213
        var index = call.OpCode == OpCode.CallVoid ? 1 : 2;
×
214
        return index < call.Operands.Count ? call.Operands[index] as LocalVariable : null;
×
215
    }
216

217
    private static void HandleKeyFunction(ApplicationAnalysisContext appContext, Instruction instruction, ulong target, BaseKeyFunctionAddresses kFA)
218
    {
219
        var method = "";
×
220
        if (target == kFA.il2cpp_codegen_initialize_method || target == kFA.il2cpp_codegen_initialize_runtime_metadata)
×
221
        {
222
            if (appContext.MetadataVersion < 27)
×
223
            {
224
                method = nameof(kFA.il2cpp_codegen_initialize_method);
×
225
            }
226
            else
227
            {
228
                method = nameof(kFA.il2cpp_codegen_initialize_runtime_metadata);
×
229
            }
230
        }
231
        else
232
        {
233
            var pairs = kFA.Pairs.ToList();
×
234
            var key = pairs.FirstOrDefault(pair => pair.Value == target).Key;
×
235
            if (key == null)
×
236
                return;
×
237
            method = key;
×
238
        }
239

240
        if (method != "")
×
241
        {
242
            instruction.Operands[0] = method;
×
243
        }
244
    }
×
245

246
    // Because of il2cpp fields (like cctor_finished_or_no_cctor) [local @ reg+offset] sometimes can't be resolved, but this works for now
247
    private static void ResolveGetter(MethodAnalysisContext method)
248
    {
249
        if (!method.Name.StartsWith("get_"))
×
250
            return;
×
251

252
        // Default get: Return [this @ reg+offset]
253
        var instructions = method.ControlFlowGraph!.Instructions;
×
254
        if (instructions.Count == 1)
×
255
        {
256
            var instr = instructions[0];
×
257

258
            if (instr.OpCode != OpCode.Return
×
259
                || instr.Operands.Count < 1
×
260
                || instr.Operands[0] is not MemoryOperand memory
×
261
                || memory.Index != null || memory.Scale != 0
×
262
                || memory.Base is not LocalVariable local)
×
263
                return;
×
264

265
            var fieldName = $"<{method.Name[4..]}>k__BackingField";
×
266

267
            var field = method.DeclaringType!.Fields.Find(f => f.Name == fieldName);
×
268
            if (field == null)
×
269
                return;
×
270

271
            instr.Operands[0] = new FieldReference(field, local, (int)memory.Addend);
×
272
        }
273
    }
×
274
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc