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

SamboyCoding / Cpp2IL / 30178263727

25 Jul 2026 10:48PM UTC coverage: 36.302% (-0.8%) from 37.144%
30178263727

push

github

SamboyCoding
Decompiler: Recover static fields, throw helpers, and rgctx, fix bool flags, fix this param in IL, fix float handling somewhat

2890 of 9069 branches covered (31.87%)

Branch coverage included in aggregate %.

28 of 319 new or added lines in 17 files covered. (8.78%)

10 existing lines in 6 files now uncovered.

5291 of 13467 relevant lines covered (39.29%)

164158.62 hits per line

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

56.18
/Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics.CodeAnalysis;
4
using System.Linq;
5
using System.Reflection;
6
using Cpp2IL.Core.Analysis;
7
using Cpp2IL.Core.Graphs;
8
using Cpp2IL.Core.ISIL;
9
using Cpp2IL.Core.Logging;
10
using Cpp2IL.Core.Utils;
11
using LibCpp2IL;
12
using LibCpp2IL.Metadata;
13
using StableNameDotNet.Providers;
14

15
namespace Cpp2IL.Core.Model.Contexts;
16

17
/// <summary>
18
/// Represents one method within the application. Can be analyzed to attempt to reconstruct the function body.
19
/// </summary>
20
public class MethodAnalysisContext : HasGenericParameters, IMethodInfoProvider
21
{
22
    /// <summary>
23
    /// The underlying metadata for the method.
24
    ///
25
    /// Nullable iff this is a subclass.
26
    /// </summary>
27
    public readonly Il2CppMethodDefinition? Definition;
28

29
    /// <summary>
30
    /// The analysis context for the declaring type of this method.
31
    /// </summary>
32
    public readonly TypeAnalysisContext? DeclaringType;
33

34
    /// <summary>
35
    /// The address of this method as defined in the underlying metadata.
36
    /// </summary>
37
    public virtual ulong UnderlyingPointer => Definition?.MethodPointer ?? throw new("Subclasses of MethodAnalysisContext should override UnderlyingPointer");
1,423,436!
38

39
    public ulong Rva => UnderlyingPointer == 0 ? 0 : AppContext.Binary.GetRva(UnderlyingPointer);
×
40

41
    /// <summary>
42
    /// The raw method body as machine code in the active instruction set.
43
    /// </summary>
44
    public BinarySlice RawBytes = BinarySlice.Empty;
1,257,762✔
45

46
    /// <summary>
47
    /// The first-stage-analyzed Instruction-Set-Independent Language Instructions.
48
    /// </summary>
49
    public List<Instruction>? ConvertedIsil;
50

51
    /// <summary>
52
    /// All ISIL local variables.
53
    /// </summary>
54
    public List<LocalVariable> Locals = [];
1,257,762✔
55

56
    /// <summary>
57
    /// Operands used as parameters.
58
    /// </summary>
59
    public List<object> ParameterOperands = [];
1,257,762✔
60

61
    /// <summary>
62
    /// The control flow graph for this method, if one is built.
63
    /// </summary>
64
    public ISILControlFlowGraph? ControlFlowGraph;
65

66
    /// <summary>
67
    /// Dominance info for the control flow graph.
68
    /// </summary>
69
    public DominatorInfo? DominatorInfo;
70

71
    public List<string> AnalysisWarnings = [];
1,257,762✔
72

73
    public static int MaxMethodSizeBytes = 18000; // 18KB
×
74

75
    public List<ParameterAnalysisContext> Parameters = [];
1,257,762✔
76

77
    public List<LocalVariable> ParameterLocals = [];
1,257,762✔
78

79
    /// <summary>
80
    /// Does this method return void?
81
    /// </summary>
82
    public bool IsVoid => ReturnType == AppContext.SystemTypes.SystemVoidType;
4✔
83

84
    public bool IsStatic => (Attributes & MethodAttributes.Static) != 0;
162,159✔
85

86
    public bool IsVirtual => (Attributes & MethodAttributes.Virtual) != 0;
43,304✔
87

88
    public bool IsAbstract => (Attributes & MethodAttributes.Abstract) != 0;
42,972✔
89

90
    public bool IsNewSlot => (Attributes & MethodAttributes.NewSlot) != 0;
43,304✔
91

92
    public bool IsFinal => (Attributes & MethodAttributes.Final) != 0;
42,972✔
93

94
    protected override int CustomAttributeIndex => Definition?.customAttributeIndex ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeIndex if they have custom attributes");
487,368!
95

96
    public override AssemblyAnalysisContext CustomAttributeAssembly => DeclaringType?.DeclaringAssembly ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeAssembly if they have custom attributes");
1,692,752!
97

98
    public override string DefaultName => Definition?.Name ?? throw new("Subclasses of MethodAnalysisContext should override DefaultName");
432,185!
99

100
    public string FullName => DeclaringType == null ? Name : $"{DeclaringType.FullName}::{Name}";
58!
101

102
    public string FullNameWithSignature => $"{ReturnType.FullName} {FullName}({string.Join(", ", Parameters.Select(p => p.HumanReadableSignature))})";
×
103

104
    public virtual MethodAttributes DefaultAttributes => Definition?.Attributes ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultAttributes)}");
603,080!
105

106
    public virtual MethodAttributes? OverrideAttributes { get; set; }
603,082✔
107

108
    public MethodAttributes Attributes
109
    {
110
        get => OverrideAttributes ?? DefaultAttributes;
603,082!
111
        set => OverrideAttributes = value;
×
112
    }
113

114
    public virtual MethodImplAttributes DefaultImplAttributes => Definition?.MethodImplAttributes ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultImplAttributes)}");
87,030!
115

116
    public virtual MethodImplAttributes? OverrideImplAttributes { get; set; }
87,030✔
117

118
    public MethodImplAttributes ImplAttributes
119
    {
120
        get => OverrideImplAttributes ?? DefaultImplAttributes;
87,030!
121
        set => OverrideImplAttributes = value;
×
122
    }
123

124
    public MethodAttributes Visibility
125
    {
126
        get
127
        {
128
            return Attributes & MethodAttributes.MemberAccessMask;
94,311✔
129
        }
130
        set
131
        {
132
            Attributes = (Attributes & ~MethodAttributes.MemberAccessMask) | (value & MethodAttributes.MemberAccessMask);
×
133
        }
×
134
    }
135

136
    private List<GenericParameterTypeAnalysisContext>? _genericParameters;
137
    public override List<GenericParameterTypeAnalysisContext> GenericParameters
138
    {
139
        get
140
        {
141
            // Lazy load the generic parameters
142
            _genericParameters ??= Definition?.GenericContainer?.GenericParameters.Select(p => new GenericParameterTypeAnalysisContext(p, this)).ToList() ?? [];
718,234!
143
            return _genericParameters;
708,239✔
144
        }
145
    }
146

147
    private ushort Slot => Definition?.slot ?? ushort.MaxValue;
109,573!
148

149
    public virtual TypeAnalysisContext DefaultReturnType => AppContext.ResolveIl2CppType(Definition?.RawReturnType) ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultReturnType)}");
652,541!
150

151
    public TypeAnalysisContext? OverrideReturnType { get; set; }
652,545✔
152

153
    //TODO Support custom attributes on return types (v31 feature)
154
    public TypeAnalysisContext ReturnType
155
    {
156
        get => OverrideReturnType ?? DefaultReturnType;
652,545!
157
        set => OverrideReturnType = value;
×
158
    }
159

160
    public MethodAnalysisContext? BaseMethod
161
    {
162
        get
163
        {
164
            if (Definition == null)
336!
165
                return null;
×
166

167
            var vtable = DeclaringType?.Definition?.VTable;
336!
168
            if (vtable == null)
336!
169
                return null;
×
170

171
            for (var i = 0; i < vtable.Length; ++i)
770✔
172
            {
173
                var vtableEntry = vtable[i];
51✔
174
                if (vtableEntry is null or { Type: not MetadataUsageType.MethodDef } || vtableEntry.AsMethod() != Definition)
51!
175
                    continue;
176

177
                if (IsInterfaceSlot(this, i))
6✔
178
                {
179
                    continue;
180
                }
181

182
                var baseType = DeclaringType?.DefaultBaseType;
2!
183
                while (baseType is not null)
3✔
184
                {
185
                    if (TryGetMethodForSlot(baseType, i, out var method))
3✔
186
                    {
187
                        return method;
2✔
188
                    }
189
                    baseType = baseType.DefaultBaseType;
1✔
190
                }
191
            }
192
            return null;
334✔
193
        }
194
    }
195

196
    private List<MethodAnalysisContext>? _overrides;
197

198
    /// <summary>
199
    /// The set of interface methods which this method explicitly overrides.
200
    /// </summary>
201
    public List<MethodAnalysisContext> Overrides
202
    {
203
        get
204
        {
205
            // Lazy load the overrides
206
            return _overrides ??= GetOverrides().ToList();
87,376!
207
        }
208
    }
209

210
    private IEnumerable<MethodAnalysisContext> GetOverrides()
211
    {
212
        if (Definition == null)
87,376!
213
            return [];
×
214

215
        var declaringTypeDefinition = DeclaringType?.Definition;
87,376!
216
        if (declaringTypeDefinition == null)
87,376!
217
            return [];
×
218

219
        var vtable = declaringTypeDefinition.VTable;
87,376✔
220
        if (vtable == null)
87,376!
221
            return [];
×
222

223
        return GetOverriddenMethods(declaringTypeDefinition, vtable);
87,376✔
224

225
        IEnumerable<MethodAnalysisContext> GetOverriddenMethods(Il2CppTypeDefinition declaringTypeDefinition, MetadataUsage?[] vtable)
226
        {
227
            for (var i = 0; i < vtable.Length; ++i)
3,585,100✔
228
            {
229
                var vtableEntry = vtable[i];
1,705,174✔
230
                if (vtableEntry is null or { Type: not MetadataUsageType.MethodDef })
1,705,174✔
231
                    continue;
232

233
                if (vtableEntry.AsMethod() != Definition)
1,648,449✔
234
                    continue;
235

236
                // Interface inheritance
237
                foreach (var interfaceOffset in declaringTypeDefinition.InterfaceOffsets)
220,600✔
238
                {
239
                    if (i >= interfaceOffset.offset)
84,165✔
240
                    {
241
                        var interfaceTypeContext = AppContext.ResolveIl2CppType(interfaceOffset.Type);
59,018✔
242
                        var slot = i - interfaceOffset.offset;
59,018✔
243
                        if (TryGetMethodForSlot(interfaceTypeContext, slot, out var method) && !IsInterfaceSlot(method, slot))
59,018!
244
                        {
245
                            yield return method;
9,059✔
246
                        }
247
                    }
248
                }
249
            }
250
        }
87,376✔
251
    }
252

253
    private static bool IsInterfaceSlot(MethodAnalysisContext method, int slot)
254
    {
255
        var declaringTypeDefinition = method.DeclaringType?.Definition;
9,065!
256
        if (declaringTypeDefinition == null)
9,065✔
257
            return false;
1,593✔
258

259
        foreach (var interfaceOffset in declaringTypeDefinition.InterfaceOffsets)
14,990✔
260
        {
261
            if (slot >= interfaceOffset.offset)
25✔
262
            {
263
                var interfaceTypeContext = method.AppContext.ResolveIl2CppType(interfaceOffset.Type);
18✔
264
                if (HasMethodForSlot(interfaceTypeContext, slot - interfaceOffset.offset))
18✔
265
                {
266
                    return true;
4✔
267
                }
268
            }
269
        }
270
        return false;
7,468✔
271
    }
272

273
    private static bool HasMethodForSlot(TypeAnalysisContext declaringType, int slot)
274
    {
275
        if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType)
18✔
276
        {
277
            return genericInstanceType.GenericType.Methods.Any(m => m.Slot == slot);
44✔
278
        }
279
        else
280
        {
281
            return declaringType.Methods.Any(m => m.Slot == slot);
36✔
282
        }
283
    }
284

285
    private static bool TryGetMethodForSlot(TypeAnalysisContext declaringType, int slot, [NotNullWhen(true)] out MethodAnalysisContext? method)
286
    {
287
        if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType)
59,021✔
288
        {
289
            var genericMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot);
15,523✔
290
            if (genericMethod is not null)
4,752✔
291
            {
292
                method = new ConcreteGenericMethodAnalysisContext(genericMethod, genericInstanceType.GenericArguments, []);
1,593✔
293
                return true;
1,593✔
294
            }
295
        }
296
        else
297
        {
298
            var baseMethod = declaringType.Methods.FirstOrDefault(m => m.Slot == slot);
153,009✔
299
            if (baseMethod is not null)
54,269✔
300
            {
301
                method = baseMethod;
7,468✔
302
                return true;
7,468✔
303
            }
304
        }
305

306
        method = null;
49,960✔
307
        return false;
49,960✔
308
    }
309

310
    public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext)
1,257,762✔
311
    {
312
        DeclaringType = parent;
1,257,762✔
313
        Definition = definition;
1,257,762✔
314

315
        if (Definition != null)
1,257,762✔
316
        {
317
            InitCustomAttributeData();
723,375✔
318

319
            for (var i = 0; i < Definition.InternalParameterData!.Length; i++)
3,131,964✔
320
            {
321
                var parameterDefinition = Definition.InternalParameterData![i];
842,607✔
322
                Parameters.Add(new(parameterDefinition, i, this));
842,607✔
323
            }
324
        }
325
    }
1,257,762✔
326

327
    public void EnsureRawBytes()
328
    {
329
        //Some abstract methods (on interfaces, no less) apparently have a body? Unity doesn't support default interface methods so idk what's going on here.
330
        //E.g. UnityEngine.Purchasing.AppleCore.dll: UnityEngine.Purchasing.INativeAppleStore::SetUnityPurchasingCallback on among us (itch.io build)
331
        if (Definition != null && Definition.MethodPointer != 0 && !Definition.Attributes.HasFlag(MethodAttributes.Abstract))
723,375!
332
        {
333
            RawBytes = AppContext.InstructionSet.GetRawBytesForMethod(this, this is AttributeGeneratorMethodAnalysisContext);
700,059✔
334

335
            if (RawBytes.Length == 0)
700,059✔
336
            {
337
                Logger.VerboseNewline("\t\t\tUnexpectedly got 0-byte method body for " + this + $". Pointer was 0x{Definition.MethodPointer:X}", "MAC");
58!
338
            }
339
        }
340
    }
723,375✔
341

342
    protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, context)
×
343
    { }
×
344

345
    [MemberNotNull(nameof(ConvertedIsil))]
346
    public void Analyze()
347
    {
348
        if (MaxMethodSizeBytes != -1 && RawBytes.Length > MaxMethodSizeBytes)
×
349
        {
350
            Logger.WarnNewline($"Method {FullName} is too big ({RawBytes.Length} bytes), skipping analysis.");
×
351
            ConvertedIsil = [];
×
352
            return;
×
353
        }
354

355
        if (ConvertedIsil != null)
×
356
            return;
×
357

358
        if (UnderlyingPointer == 0)
×
359
        {
360
            ConvertedIsil = [];
×
361
            return;
×
362
        }
363

364
        ConvertedIsil = AppContext.InstructionSet.GetIsilFromMethod(this);
×
365
        ParameterOperands = AppContext.InstructionSet.GetParameterOperandsFromMethod(this);
×
366

367
        if (ConvertedIsil.Count == 0)
×
368
            return; //Nothing to do, empty function
×
369

370
        ControlFlowGraph = new ISILControlFlowGraph(ConvertedIsil);
×
371

372
        // Indirect jumps/calls should probably be resolved here before stack analysis
373

374
        StackAnalyzer.Analyze(this);
×
375

376
        // Dominator info must be computed after stack analysis, which removes unreachable/empty
377
        // blocks and would otherwise leave the dominator tree out of sync with the graph SSA sees.
378
        DominatorInfo = new DominatorInfo(ControlFlowGraph);
×
379

380
        // Create locals
381
        SsaForm.Build(this);
×
382
        LocalVariables.CreateAll(this);
×
383

384
        // Fold the explicit per-comparison flag arithmetic back into single relational comparisons,
385
        // then eliminate the now-dead flag computations. Both run in SSA form, where each
386
        // flag/temporary has a single, version-stable definition.
387
        FlagConditionRecovery.Run(this);
×
388
        DeadCodeEliminator.Run(this);
×
389

390
        // Resolve call targets, strings and getters, then run the combined type-propagation and
391
        // field-resolution fixpoint - all while still in SSA form, so every local is
392
        // single-assignment and a type, once known, is stable for that value.
393
        MetadataResolver.ResolveAll(this);
×
394

395
        // Resolve KeyFunctionAddress calls, then collect what removing the write barriers left dead.
396
        KeyFunctionRecovery.Run(this);
×
397
        DeadCodeEliminator.Run(this);
×
398

399
        // Delete any il2cpp_codegen_initialize_runtime_metadata/il2cpp_codegen_initialize_method
400
        MetadataInitGuardRemover.Run(this);
×
401

402
        LocalVariables.ResolveTypesAndFields(this);
×
403

404
        // Needs type resolved for delegate locals
405
        DelegateInvokeRecovery.Run(this);
×
NEW
406
        BooleanFlagSimplifier.Run(this);
×
UNCOV
407
        DeadCodeEliminator.Run(this);
×
408

409
        // Copy/constant propagation belongs in SSA, where one definition dominates all uses and phis
410
        // make joins explicit, so forwarding a value is an unconditional global substitution.
411
        SsaSimplifier.Run(this);
×
412

413
        SsaForm.Remove(this);
×
414

415
        // Now out of SSA: clean up the per-edge copies that phi removal introduced (a local can have
416
        // several definitions merging at a join here, so this pass propagates conservatively), then
417
        // drop dead locals.
418
        Simplifier.Simplify(this);
×
419

420
        // Fix float literals
421
        FloatLiteralRecovery.Run(this);
×
422

423
        LocalVariables.RemoveUnused(this);
×
424
    }
×
425

426
    public void AddWarning(string warning) => AnalysisWarnings.Add(warning);
×
427

428
    public void ReleaseAnalysisData()
429
    {
430
        ConvertedIsil = null;
×
431
        ControlFlowGraph = null;
×
432
        DominatorInfo = null;
×
433
    }
×
434

435
    public ConcreteGenericMethodAnalysisContext MakeGenericInstanceMethod(params IEnumerable<TypeAnalysisContext> methodGenericParameters)
436
    {
437
        if (this is ConcreteGenericMethodAnalysisContext methodOnGenericInstanceType)
×
438
        {
439
            return new ConcreteGenericMethodAnalysisContext(methodOnGenericInstanceType.BaseMethodContext, methodOnGenericInstanceType.TypeGenericParameters, methodGenericParameters);
×
440
        }
441
        else
442
        {
443
            return new ConcreteGenericMethodAnalysisContext(this, [], methodGenericParameters);
×
444
        }
445
    }
446

447
    public ConcreteGenericMethodAnalysisContext MakeConcreteGenericMethod(IEnumerable<TypeAnalysisContext> typeGenericParameters, IEnumerable<TypeAnalysisContext> methodGenericParameters)
448
    {
449
        if (this is ConcreteGenericMethodAnalysisContext)
×
450
        {
451
            throw new InvalidOperationException($"Attempted to make a {nameof(ConcreteGenericMethodAnalysisContext)} concrete: {this}");
×
452
        }
453
        else
454
        {
455
            return new ConcreteGenericMethodAnalysisContext(this, typeGenericParameters, methodGenericParameters);
×
456
        }
457
    }
458

459
    public override string ToString() => $"Method: {FullName}";
58✔
460

461
    #region StableNameDot implementation
462

463
    ITypeInfoProvider IMethodInfoProvider.ReturnType =>
464
        Definition!.RawReturnType!.ThisOrElementIsGenericParam()
×
465
            ? new GenericParameterTypeInfoProviderWrapper(Definition.RawReturnType!.GetGenericParamName())
×
466
            : TypeAnalysisContext.GetSndnProviderForType(AppContext, Definition!.RawReturnType);
×
467

468
    IEnumerable<IParameterInfoProvider> IMethodInfoProvider.ParameterInfoProviders => Parameters;
×
469

470
    string IMethodInfoProvider.MethodName => Name;
×
471

472
    MethodAttributes IMethodInfoProvider.MethodAttributes => Attributes;
×
473

474
    MethodSemantics IMethodInfoProvider.MethodSemantics
475
    {
476
        get
477
        {
478
            if (DeclaringType != null)
×
479
            {
480
                //This one is a bit trickier, as il2cpp doesn't use semantics.
481
                foreach (var prop in DeclaringType.Properties)
×
482
                {
483
                    if (prop.Getter == this)
×
484
                        return MethodSemantics.Getter;
×
485
                    if (prop.Setter == this)
×
486
                        return MethodSemantics.Setter;
×
487
                }
488

489
                foreach (var evt in DeclaringType.Events)
×
490
                {
491
                    if (evt.Adder == this)
×
492
                        return MethodSemantics.AddOn;
×
493
                    if (evt.Remover == this)
×
494
                        return MethodSemantics.RemoveOn;
×
495
                    if (evt.Invoker == this)
×
496
                        return MethodSemantics.Fire;
×
497
                }
498
            }
499

500
            return 0;
×
501
        }
×
502
    }
503

504
    #endregion
505
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc