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

SamboyCoding / Cpp2IL / 15051179934

15 May 2025 05:16PM UTC coverage: 34.039% (-0.4%) from 34.453%
15051179934

Pull #451

github

web-flow
Merge 59eb1b3f5 into d3bbbb38e
Pull Request #451: Support injecting anything

1774 of 6622 branches covered (26.79%)

Branch coverage included in aggregate %.

147 of 195 new or added lines in 27 files covered. (75.38%)

53 existing lines in 2 files now uncovered.

4155 of 10796 relevant lines covered (38.49%)

188285.05 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

© 2025 Coveralls, Inc