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

SamboyCoding / Cpp2IL / 15052278841

15 May 2025 06:16PM UTC coverage: 34.038% (-0.4%) from 34.453%
15052278841

push

github

SamboyCoding
Support injecting anything
* Nested types
* Events
* Properties
* Assemblies

Support additional metadata in injected assemblies

Make setter for TypeAnalysisContext::OverrideBaseType public

TypeAnalysisContext::InterfaceContexts as list rather than array

Fix sign bug

Support generic parameters on type contexts and method contexts

Make GenericParameterTypeAnalysisContext instances unique

Revert change to Il2CppGenericParameter::Index

Use attributes to determine if injected methods are static
* Also add hide by sig attribute for injected constructors

Support overrides on injected methods

In ControlFlowGraphOutputFormat, exclude injected assemblies

Ensure injected assemblies can't delete existing assemblies

Backing field on .NET Standard for injected method overrides

Implement requested change

1774 of 6622 branches covered (26.79%)

Branch coverage included in aggregate %.

147 of 196 new or added lines in 27 files covered. (75.0%)

53 existing lines in 2 files now uncovered.

4155 of 10797 relevant lines covered (38.48%)

188267.61 hits per line

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

42.93
/Cpp2IL.Core/Model/Contexts/HasCustomAttributes.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Diagnostics;
4
using System.IO;
5
using System.Linq;
6
using Cpp2IL.Core.Extensions;
7
using Cpp2IL.Core.Logging;
8
using Cpp2IL.Core.Model.CustomAttributes;
9
using Cpp2IL.Core.Utils;
10
using LibCpp2IL;
11
using LibCpp2IL.BinaryStructures;
12
using LibCpp2IL.Metadata;
13

14
namespace Cpp2IL.Core.Model.Contexts;
15

16
/// <summary>
17
/// A base class to represent any type which has, or can have, custom attributes.
18
/// </summary>
19
public abstract class HasCustomAttributes(uint token, ApplicationAnalysisContext appContext)
20
    : HasToken(token, appContext)
2,461,380✔
21
{
22
    private bool _hasAnalyzedCustomAttributeData;
23
    private bool _hasInitCustomAttributeData;
24

25
    /// <summary>
26
    /// On V29, stores the custom attribute blob. Pre-29, stores the bytes for the custom attribute generator function.
27
    /// </summary>
28
    public Memory<byte> RawIl2CppCustomAttributeData = Memory<byte>.Empty;
2,461,380✔
29

30
    /// <summary>
31
    /// Stores the analyzed custom attribute data once analysis has actually run.
32
    /// </summary>
33
    public List<AnalyzedCustomAttribute>? CustomAttributes;
34

35
    /// <summary>
36
    /// Stores the attribute type range for this member, which references which custom attributes are present.
37
    ///
38
    /// Null on v29+, nonnull prior to that
39
    /// </summary>
40
    public Il2CppCustomAttributeTypeRange? AttributeTypeRange;
41

42
    /// <summary>
43
    /// Stores the raw types of the custom attributes on this member.
44
    ///
45
    /// Null on v29+ (constructors are in the blob), nonnull prior to that
46
    /// </summary>
47
    public List<Il2CppType>? AttributeTypes;
48

49
    /// <summary>
50
    /// Prior to v29, stores the analysis context for custom attribute cache generator function.
51
    ///
52
    /// On v29, is null because there is no method, the attribute blob is stored instead, in the metadata file.
53
    /// </summary>
54
    public AttributeGeneratorMethodAnalysisContext? CaCacheGeneratorAnalysis;
55

56
    /// <summary>
57
    /// Returns this member's custom attribute index, or -1 if it has no custom attributes.
58
    /// </summary>
59
    protected abstract int CustomAttributeIndex { get; }
60

61
    /// <summary>
62
    /// Returns this member's assembly context for use in custom attribute reconstruction.
63
    /// </summary>
64
    public abstract AssemblyAnalysisContext CustomAttributeAssembly { get; }
65

66
    /// <summary>
67
    /// Returns true if this member is injected by Cpp2IL (and thus should not be analyzed for custom attributes).
68
    /// </summary>
69
    protected virtual bool IsInjected => false;
1,398,123✔
70

71
    /// <summary>
72
    /// Pre-v29, stores the index of the custom attribute range for this member. Post-v29, always -1.
73
    /// </summary>
74
    private int Pre29RangeIndex = -1;
2,461,380✔
75

76
    public bool IsCompilerGeneratedBasedOnCustomAttributes =>
77
        CustomAttributes?.Any(a => a.Constructor.DeclaringType!.FullName.Contains("CompilerGeneratedAttribute"))
×
78
        ?? AttributeTypes?.Any(t => t.Type == Il2CppTypeEnum.IL2CPP_TYPE_CLASS && t.AsClass().FullName!.Contains("CompilerGeneratedAttribute"))
×
79
        ?? false;
×
80

81

82
#pragma warning disable CS8618 //Non-null member is not initialized.
83
#pragma warning restore CS8618
84

85
    protected void InitCustomAttributeData()
86
    {
87
        if(IsInjected)
1,399,215!
88
            return;
×
89
        
90
        _hasInitCustomAttributeData = true;
1,399,215✔
91
        if (AppContext.MetadataVersion >= 29)
1,399,215✔
92
        {
93
            var offsets = GetV29BlobOffsets();
246,588✔
94

95
            if (!offsets.HasValue)
246,588✔
96
                return;
228,024✔
97

98
            var (blobStart, blobEnd) = offsets.Value;
18,564✔
99
            RawIl2CppCustomAttributeData = AppContext.Metadata.ReadByteArrayAtRawAddress(blobStart, (int)(blobEnd - blobStart));
18,564✔
100

101
            return;
18,564✔
102
        }
103

104
        if (CustomAttributeAssembly.Definition is null)
1,152,627!
NEW
105
            return;
×
106

107
        AttributeTypeRange = AppContext.Metadata.GetCustomAttributeData(CustomAttributeAssembly.Definition.Image, CustomAttributeIndex, Token, out Pre29RangeIndex);
1,152,627✔
108

109
        if (AttributeTypeRange == null || AttributeTypeRange.count == 0)
1,152,627✔
110
        {
111
            RawIl2CppCustomAttributeData = Array.Empty<byte>();
1,063,167✔
112
            AttributeTypes = [];
1,063,167✔
113
            return; //No attributes
1,063,167✔
114
        }
115

116
        AttributeTypes = Enumerable.Range(AttributeTypeRange.start, AttributeTypeRange.count)
89,460✔
117
            .Select(attrIdx => AppContext.Metadata!.attributeTypes![attrIdx]) //Not null because we've checked we're not on v29
201,390✔
118
            .Select(typeIdx => AppContext.Binary!.GetType(typeIdx))
201,390✔
119
            .ToList();
89,460✔
120
    }
89,460✔
121

122
    private (long blobStart, long blobEnd)? GetV29BlobOffsets()
123
    {
124
        if (CustomAttributeAssembly.Definition is null)
246,588!
NEW
125
            return null;
×
126

127
        var target = new Il2CppCustomAttributeDataRange() { token = Token };
246,588✔
128
        var caIndex = AppContext.Metadata.AttributeDataRanges.BinarySearch
246,588✔
129
        (
246,588✔
130
            CustomAttributeAssembly.Definition.Image.customAttributeStart,
246,588✔
131
            (int)CustomAttributeAssembly.Definition.Image.customAttributeCount,
246,588✔
132
            target,
246,588✔
133
            new TokenComparer()
246,588✔
134
        );
246,588✔
135

136
        if (caIndex < 0)
246,588✔
137
        {
138
            RawIl2CppCustomAttributeData = Array.Empty<byte>();
228,024✔
139
            return null;
228,024✔
140
        }
141

142
        var attributeDataRange = AppContext.Metadata.AttributeDataRanges[caIndex];
18,564✔
143
        var next = AppContext.Metadata.AttributeDataRanges[caIndex + 1];
18,564✔
144

145
        var blobStart = AppContext.Metadata.metadataHeader.attributeDataOffset + attributeDataRange.startOffset;
18,564✔
146
        var blobEnd = AppContext.Metadata.metadataHeader.attributeDataOffset + next.startOffset;
18,564✔
147
        return (blobStart, blobEnd);
18,564✔
148
    }
149

150
    private void InitPre29AttributeGeneratorAnalysis(int rangeIndex)
151
    {
152
        ulong generatorPtr;
153
        if (AppContext.MetadataVersion < 27)
×
154
        {
155
            if (rangeIndex < 0)
×
156
            {
157
                RawIl2CppCustomAttributeData = Array.Empty<byte>();
×
158
                return;
×
159
            }
160

161
            try
162
            {
163
                generatorPtr = AppContext.Binary.GetCustomAttributeGenerator(rangeIndex);
×
164
            }
×
165
            catch (IndexOutOfRangeException)
×
166
            {
167
                Logger.WarnNewline("Custom attribute generator out of range for " + this, "CA Restore");
×
168
                RawIl2CppCustomAttributeData = Array.Empty<byte>();
×
169
                return;
×
170
            }
171
        }
172
        else
173
        {
NEW
174
            if(AttributeTypeRange == null || AttributeTypeRange.count == 0 || CustomAttributeAssembly.Definition is null)
×
175
            {
176
                RawIl2CppCustomAttributeData = Array.Empty<byte>();
×
177
                return;
×
178
            }
179
            
180
            var baseAddress = CustomAttributeAssembly.CodeGenModule!.customAttributeCacheGenerator;
×
181
            var relativeIndex = rangeIndex - CustomAttributeAssembly.Definition.Image.customAttributeStart;
×
182
            var ptrToAddress = baseAddress + (ulong)relativeIndex * AppContext.Binary.PointerSize;
×
183
            generatorPtr = AppContext.Binary.ReadPointerAtVirtualAddress(ptrToAddress);
×
184
        }
185

186
        if (generatorPtr == 0 || !AppContext.Binary.TryMapVirtualAddressToRaw(generatorPtr, out _))
×
187
        {
188
            Logger.WarnNewline($"Supposedly had custom attributes ({string.Join(", ", AttributeTypes)}), but generator was null for " + this, "CA Restore");
×
189
            RawIl2CppCustomAttributeData = Memory<byte>.Empty;
×
190
            return;
×
191
        }
192

193
        CaCacheGeneratorAnalysis = new(generatorPtr, AppContext, this);
×
194
        RawIl2CppCustomAttributeData = CaCacheGeneratorAnalysis.RawBytes;
×
195
    }
×
196

197
    /// <summary>
198
    /// Attempt to parse the Il2CppCustomAttributeData blob into custom attributes.
199
    /// </summary>
200
    public void AnalyzeCustomAttributeData(bool allowAnalysis = true)
201
    {
202
        if (_hasAnalyzedCustomAttributeData)
6!
203
            return;
×
204
        
205
        if(IsInjected)
6!
206
            return;
×
207
        
208
        if(!_hasInitCustomAttributeData)
6!
209
            throw new($"Must call InitCustomAttributeData before AnalyzeCustomAttributeData on {this}");
×
210

211
        _hasAnalyzedCustomAttributeData = true;
6✔
212

213
        CustomAttributes = [];
6✔
214

215
        if (AppContext.MetadataVersion >= 29)
6!
216
        {
217
            AnalyzeCustomAttributeDataV29();
6✔
218
            return;
6✔
219
        }
220
        
221
        InitPre29AttributeGeneratorAnalysis(Pre29RangeIndex);
×
222

223
        if (RawIl2CppCustomAttributeData.Length == 0)
×
224
            return;
×
225

226
        if (allowAnalysis)
×
227
        {
228
            try
229
            {
230
                CaCacheGeneratorAnalysis!.Analyze();
×
231
            }
×
232
            catch (Exception e)
×
233
            {
234
                Logger.WarnNewline("Failed to analyze custom attribute cache generator for " + this + " because " + e.Message, "CA Restore");
×
235
                return;
×
236
            }
237
        }
238

239
        //Basically, extract actions from the analysis, and compare with the type list we have to resolve parameters and populate the CustomAttributes list.
240

241
        foreach (var il2CppType in AttributeTypes ?? []) //Can be null for injected objects
×
242
        {
243
            var typeDef = il2CppType.AsClass();
×
244
            var attributeTypeContext = AppContext.ResolveContextForType(typeDef) ?? throw new("Unable to find type " + typeDef.FullName);
×
245

246
            AnalyzedCustomAttribute attribute;
247
            if (attributeTypeContext.Methods.FirstOrDefault(c => c.Name == ".ctor" && c.Definition!.parameterCount == 0) is { } constructor)
×
248
            {
249
                attribute = new(constructor);
×
250
            }
251
            else if (attributeTypeContext.Methods.FirstOrDefault(c => c.Name == ".ctor") is { } anyConstructor)
×
252
            {
253
                //TODO change this to actual constructor w/ params once anaylsis is available
254
                attribute = new(anyConstructor);
×
255
            }
256
            else
257
                //No constructor - shouldn't happen?
258
                continue;
259

260
            //Add the attribute, even if we don't have constructor params, so it can be read regardless
261
            CustomAttributes.Add(attribute);
×
262
        }
263
    }
×
264

265
    /// <summary>
266
    /// Parses the Il2CppCustomAttributeData blob as a v29 metadata attribute blob into custom attributes.
267
    /// </summary>
268
    private void AnalyzeCustomAttributeDataV29()
269
    {
270
        if (RawIl2CppCustomAttributeData.Length == 0)
6!
271
            return;
×
272

273
        using var blobStream = new MemoryStream(RawIl2CppCustomAttributeData.ToArray());
6✔
274
        var attributeCount = blobStream.ReadUnityCompressedUint();
6✔
275
        var constructors = V29AttributeUtils.ReadConstructors(blobStream, attributeCount, AppContext);
6✔
276

277
        //Diagnostic data
278
        var startOfData = blobStream.Position;
6✔
279
        var perAttributeStartOffsets = new Dictionary<Il2CppMethodDefinition, long>();
6✔
280

281
        CustomAttributes = [];
6✔
282
        foreach (var constructor in constructors)
36✔
283
        {
284
            perAttributeStartOffsets[constructor] = blobStream.Position;
12✔
285

286
            var attributeTypeContext = AppContext.ResolveContextForType(constructor.DeclaringType!) ?? throw new($"Unable to find type {constructor.DeclaringType!.FullName}");
12!
287
            var attributeMethodContext = attributeTypeContext.GetMethod(constructor) ?? throw new($"Unable to find method {constructor.Name} in type {attributeTypeContext.Definition?.FullName}");
12!
288

289
            try
290
            {
291
                CustomAttributes.Add(V29AttributeUtils.ReadAttribute(blobStream, attributeMethodContext, AppContext));
12✔
292
            }
12✔
293
            catch (Exception e)
×
294
            {
295
                Logger.ErrorNewline($"Failed to read attribute data for {constructor}, which has parameters {string.Join(", ", constructor.Parameters!.Select(p => p.Type))}", "CA Restore");
×
296
                Logger.ErrorNewline($"This member ({ToString()}) has {RawIl2CppCustomAttributeData.Length} bytes of data starting at 0x{GetV29BlobOffsets()!.Value.blobStart:X}", "CA Restore");
×
297
                Logger.ErrorNewline($"The post-constructor data started at 0x{startOfData:X} bytes into our blob", "CA Restore");
×
298
                Logger.ErrorNewline($"Data for this constructor started at 0x{perAttributeStartOffsets[constructor]:X} bytes into our blob, we are now 0x{blobStream.Position:X} bytes into the blob", "CA Restore");
×
299
                Logger.ErrorNewline($"The exception message was {e.Message}", "CA Restore");
×
300

301
                throw;
×
302
            }
303
        }
304
    }
12✔
305
}
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