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

SamboyCoding / Cpp2IL / 17216182984

25 Aug 2025 05:35PM UTC coverage: 30.38% (-4.0%) from 34.352%
17216182984

Pull #481

github

web-flow
Merge b82763a24 into d5260685f
Pull Request #481: Decompiler

1804 of 7561 branches covered (23.86%)

Branch coverage included in aggregate %.

100 of 1839 new or added lines in 29 files covered. (5.44%)

41 existing lines in 6 files now uncovered.

4093 of 11850 relevant lines covered (34.54%)

165575.01 hits per line

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

54.87
/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");
865,848!
38

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

41
    /// <summary>
42
    /// The raw method body as machine code in the active instruction set.
43
    /// </summary>
44
    public Memory<byte> RawBytes => rawMethodBody ??= InitRawBytes();
×
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 = [];
738,870✔
55

56
    /// <summary>
57
    /// Operands used as parameters.
58
    /// </summary>
59
    public List<object> ParameterOperands = [];
738,870✔
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 = [];
738,870✔
72

73
    private const int MaxMethodSizeBytes = 18000; // 18KB
74

75
    public List<ParameterAnalysisContext> Parameters = [];
738,870✔
76

77
    public List<LocalVariable> ParameterLocals = [];
738,870✔
78

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

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

86
    public bool IsVirtual => (Attributes & MethodAttributes.Virtual) != 0;
×
87

88
    protected override int CustomAttributeIndex => Definition?.customAttributeIndex ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeIndex if they have custom attributes");
365,526!
89

90
    public override AssemblyAnalysisContext CustomAttributeAssembly => DeclaringType?.DeclaringAssembly ?? throw new("Subclasses of MethodAnalysisContext should override CustomAttributeAssembly if they have custom attributes");
968,522!
91

92
    public override string DefaultName => Definition?.Name ?? throw new("Subclasses of MethodAnalysisContext should override DefaultName");
87,698!
93

94
    public string FullName => DeclaringType == null ? Name : $"{DeclaringType.FullName}::{Name}";
33!
95

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

98
    public virtual MethodAttributes DefaultAttributes => Definition?.Attributes ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultAttributes)}");
261,415!
99

100
    public virtual MethodAttributes? OverrideAttributes { get; set; }
261,415✔
101

102
    public MethodAttributes Attributes => OverrideAttributes ?? DefaultAttributes;
261,415!
103

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

106
    public virtual MethodImplAttributes? OverrideImplAttributes { get; set; }
87,030✔
107

108
    public MethodImplAttributes ImplAttributes => OverrideImplAttributes ?? DefaultImplAttributes;
87,030!
109

110
    public MethodAttributes Visibility
111
    {
112
        get
113
        {
114
            return Attributes & MethodAttributes.MemberAccessMask;
×
115
        }
116
        set
117
        {
118
            OverrideAttributes = (Attributes & ~MethodAttributes.MemberAccessMask) | (value & MethodAttributes.MemberAccessMask);
×
119
        }
×
120
    }
121

122
    private List<GenericParameterTypeAnalysisContext>? _genericParameters;
123
    public override List<GenericParameterTypeAnalysisContext> GenericParameters
124
    {
125
        get
126
        {
127
            // Lazy load the generic parameters
128
            _genericParameters ??= Definition?.GenericContainer?.GenericParameters.Select(p => new GenericParameterTypeAnalysisContext(p, this)).ToList() ?? [];
478,429!
129
            return _genericParameters;
473,408✔
130
        }
131
    }
132

133
    private ushort Slot => Definition?.slot ?? ushort.MaxValue;
75,705!
134

135
    public virtual TypeAnalysisContext DefaultReturnType => DeclaringType?.DeclaringAssembly.ResolveIl2CppType(Definition?.RawReturnType) ?? throw new($"Subclasses of MethodAnalysisContext should override {nameof(DefaultReturnType)}");
385,993!
136

137
    public TypeAnalysisContext? OverrideReturnType { get; set; }
385,996✔
138

139
    //TODO Support custom attributes on return types (v31 feature)
140
    public TypeAnalysisContext ReturnType => OverrideReturnType ?? DefaultReturnType;
385,996✔
141

142
    protected Memory<byte>? rawMethodBody;
143

144
    public MethodAnalysisContext? BaseMethod => Overrides.FirstOrDefault(m => m.DeclaringType?.IsInterface is false);
8!
145

146
    /// <summary>
147
    /// The set of methods which this method overrides.
148
    /// </summary>
149
    public virtual IEnumerable<MethodAnalysisContext> Overrides
150
    {
151
        get
152
        {
153
            if (Definition == null)
18,047!
154
                return [];
×
155

156
            var declaringTypeDefinition = DeclaringType?.Definition;
18,047!
157
            if (declaringTypeDefinition == null)
18,047!
158
                return [];
×
159

160
            var vtable = declaringTypeDefinition.VTable;
18,047✔
161
            if (vtable == null)
18,047!
162
                return [];
×
163

164
            return GetOverriddenMethods(declaringTypeDefinition, vtable);
18,047✔
165

166
            bool TryGetMethodForSlot(TypeAnalysisContext declaringType, int slot, [NotNullWhen(true)] out MethodAnalysisContext? method)
167
            {
168
                if (declaringType is GenericInstanceTypeAnalysisContext genericInstanceType)
13,520✔
169
                {
170
                    var genericMethod = genericInstanceType.GenericType.Methods.FirstOrDefault(m => m.Slot == slot);
7,102✔
171
                    if (genericMethod is not null)
1,802✔
172
                    {
173
                        method = new ConcreteGenericMethodAnalysisContext(genericMethod, genericInstanceType.GenericArguments, []);
330✔
174
                        return true;
330✔
175
                    }
176
                }
177
                else
178
                {
179
                    var baseMethod = declaringType.Methods.FirstOrDefault(m => m.Slot == slot);
82,123✔
180
                    if (baseMethod is not null)
11,718✔
181
                    {
182
                        method = baseMethod;
2,830✔
183
                        return true;
2,830✔
184
                    }
185
                }
186

187
                method = null;
10,360✔
188
                return false;
10,360✔
189
            }
190

191
            IEnumerable<MethodAnalysisContext> GetOverriddenMethods(Il2CppTypeDefinition declaringTypeDefinition, MetadataUsage?[] vtable)
192
            {
193
                for (var i = 0; i < vtable.Length; ++i)
683,696✔
194
                {
195
                    var vtableEntry = vtable[i];
323,803✔
196
                    if (vtableEntry is null or { Type: not MetadataUsageType.MethodDef })
323,803✔
197
                        continue;
198

199
                    if (vtableEntry.AsMethod() != Definition)
318,193✔
200
                        continue;
201

202
                    // Normal inheritance
203
                    var baseType = DeclaringType?.BaseType;
3,125!
204
                    while (baseType is not null)
7,853✔
205
                    {
206
                        if (TryGetMethodForSlot(baseType, i, out var method))
4,766✔
207
                        {
208
                            yield return method;
38✔
209
                            break; // We only want direct overrides, not the entire inheritance chain.
36✔
210
                        }
211
                        baseType = baseType.BaseType;
4,728✔
212
                    }
213

214
                    // Interface inheritance
215
                    foreach (var interfaceOffset in declaringTypeDefinition.InterfaceOffsets)
34,564✔
216
                    {
217
                        if (i >= interfaceOffset.offset)
14,159✔
218
                        {
219
                            var interfaceTypeContext = interfaceOffset.Type.ToContext(CustomAttributeAssembly);
8,754✔
220
                            if (interfaceTypeContext != null && TryGetMethodForSlot(interfaceTypeContext, i - interfaceOffset.offset, out var method))
8,754✔
221
                            {
222
                                yield return method;
3,122✔
223
                            }
224
                        }
225
                    }
226
                }
227
            }
18,045✔
228
        }
229
    }
230

231
    public MethodAnalysisContext(Il2CppMethodDefinition? definition, TypeAnalysisContext parent) : base(definition?.token ?? 0, parent.AppContext)
738,870✔
232
    {
233
        DeclaringType = parent;
738,870✔
234
        Definition = definition;
738,870✔
235

236
        if (Definition != null)
738,870✔
237
        {
238
            InitCustomAttributeData();
439,980✔
239

240
            for (var i = 0; i < Definition.InternalParameterData!.Length; i++)
1,890,450✔
241
            {
242
                var parameterDefinition = Definition.InternalParameterData![i];
505,245✔
243
                Parameters.Add(new(parameterDefinition, i, this));
505,245✔
244
            }
245
        }
246
        else
247
            rawMethodBody = Array.Empty<byte>();
298,890✔
248
    }
298,890✔
249

250
    [MemberNotNull(nameof(rawMethodBody))]
251
    public void EnsureRawBytes()
252
    {
253
        rawMethodBody ??= InitRawBytes();
439,980✔
254
    }
439,980✔
255

256
    private Memory<byte> InitRawBytes()
257
    {
258
        //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.
259
        //E.g. UnityEngine.Purchasing.AppleCore.dll: UnityEngine.Purchasing.INativeAppleStore::SetUnityPurchasingCallback on among us (itch.io build)
260
        if (Definition != null && Definition.MethodPointer != 0 && !Definition.Attributes.HasFlag(MethodAttributes.Abstract))
439,980✔
261
        {
262
            var ret = AppContext.InstructionSet.GetRawBytesForMethod(this, false);
425,868✔
263

264
            if (ret.Length == 0)
425,868✔
265
            {
266
                Logger.VerboseNewline("\t\t\tUnexpectedly got 0-byte method body for " + this + $". Pointer was 0x{Definition.MethodPointer:X}", "MAC");
33!
267
            }
268

269
            return ret;
425,868✔
270
        }
271
        else
272
            return Array.Empty<byte>();
14,112✔
273
    }
274

275
    protected MethodAnalysisContext(ApplicationAnalysisContext context) : base(0, context)
×
276
    {
277
        rawMethodBody = Array.Empty<byte>();
×
278
    }
×
279

280
    [MemberNotNull(nameof(ConvertedIsil))]
281
    public void Analyze()
282
    {
NEW
283
        if (RawBytes.Length > MaxMethodSizeBytes)
×
284
        {
NEW
285
            Logger.WarnNewline($"Method {FullName} is too big ({RawBytes.Length} bytes), skipping analysis.");
×
NEW
286
            ConvertedIsil = [];
×
NEW
287
            return;
×
288
        }
289

290
        if (ConvertedIsil != null)
×
291
            return;
×
292

293
        if (UnderlyingPointer == 0)
×
294
        {
295
            ConvertedIsil = [];
×
296
            return;
×
297
        }
298

299
        ConvertedIsil = AppContext.InstructionSet.GetIsilFromMethod(this);
×
NEW
300
        ParameterOperands = AppContext.InstructionSet.GetParameterOperandsFromMethod(this);
×
301

302
        if (ConvertedIsil.Count == 0)
×
303
            return; //Nothing to do, empty function
×
304

NEW
305
        ControlFlowGraph = new ISILControlFlowGraph(ConvertedIsil);
×
NEW
306
        DominatorInfo = new DominatorInfo(ControlFlowGraph);
×
307

308
        // Indirect jumps/calls should probably be resolved here before stack analysis
309

NEW
310
        StackAnalyzer.Analyze(this);
×
311

312
        // Create locals
NEW
313
        SsaForm.Build(this);
×
NEW
314
        LocalVariables.CreateAll(this);
×
NEW
315
        SsaForm.Remove(this);
×
316

NEW
317
        MetadataResolver.ResolveAll(this);
×
NEW
318
        Simplifier.Simplify(this);
×
319

320
        // Propagate types and clean up locals
NEW
321
        LocalVariables.PropagateTypes(this);
×
NEW
322
        LocalVariables.RemoveUnused(this);
×
UNCOV
323
    }
×
324

NEW
325
    public void AddWarning(string warning) => AnalysisWarnings.Add(warning);
×
326

327
    public void ReleaseAnalysisData()
328
    {
329
        ConvertedIsil = null;
×
330
        ControlFlowGraph = null;
×
NEW
331
        DominatorInfo = null;
×
UNCOV
332
    }
×
333

334
    public ConcreteGenericMethodAnalysisContext MakeGenericInstanceMethod(params IEnumerable<TypeAnalysisContext> methodGenericParameters)
335
    {
336
        if (this is ConcreteGenericMethodAnalysisContext methodOnGenericInstanceType)
×
337
        {
338
            return new ConcreteGenericMethodAnalysisContext(methodOnGenericInstanceType.BaseMethodContext, methodOnGenericInstanceType.TypeGenericParameters, methodGenericParameters);
×
339
        }
340
        else
341
        {
342
            return new ConcreteGenericMethodAnalysisContext(this, [], methodGenericParameters);
×
343
        }
344
    }
345

346
    public override string ToString() => $"Method: {FullName}";
33✔
347

348
    #region StableNameDot implementation
349

350
    ITypeInfoProvider IMethodInfoProvider.ReturnType =>
351
        Definition!.RawReturnType!.ThisOrElementIsGenericParam()
×
352
            ? new GenericParameterTypeInfoProviderWrapper(Definition.RawReturnType!.GetGenericParamName())
×
353
            : TypeAnalysisContext.GetSndnProviderForType(AppContext, Definition!.RawReturnType);
×
354

355
    IEnumerable<IParameterInfoProvider> IMethodInfoProvider.ParameterInfoProviders => Parameters;
×
356

357
    string IMethodInfoProvider.MethodName => Name;
×
358

359
    MethodAttributes IMethodInfoProvider.MethodAttributes => Attributes;
×
360

361
    MethodSemantics IMethodInfoProvider.MethodSemantics
362
    {
363
        get
364
        {
365
            if (DeclaringType != null)
×
366
            {
367
                //This one is a bit trickier, as il2cpp doesn't use semantics.
368
                foreach (var prop in DeclaringType.Properties)
×
369
                {
370
                    if (prop.Getter == this)
×
371
                        return MethodSemantics.Getter;
×
372
                    if (prop.Setter == this)
×
373
                        return MethodSemantics.Setter;
×
374
                }
375

376
                foreach (var evt in DeclaringType.Events)
×
377
                {
378
                    if (evt.Adder == this)
×
379
                        return MethodSemantics.AddOn;
×
380
                    if (evt.Remover == this)
×
381
                        return MethodSemantics.RemoveOn;
×
382
                    if (evt.Invoker == this)
×
383
                        return MethodSemantics.Fire;
×
384
                }
385
            }
386

387
            return 0;
×
388
        }
×
389
    }
390

391
    #endregion
392
}
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

© 2025 Coveralls, Inc