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

SamboyCoding / Cpp2IL / 17989030358

24 Sep 2025 08:39PM UTC coverage: 30.294% (-4.0%) from 34.302%
17989030358

Pull #481

github

web-flow
Merge be900a5ca into 8cf262f50
Pull Request #481: Decompiler

1806 of 7579 branches covered (23.83%)

Branch coverage included in aggregate %.

100 of 1854 new or added lines in 29 files covered. (5.39%)

41 existing lines in 6 files now uncovered.

4084 of 11864 relevant lines covered (34.42%)

165379.78 hits per line

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

75.98
/Cpp2IL.Core/OutputFormats/AsmResolverDllOutputFormat.cs
1
// #define VERBOSE_LOGGING
2

3
using System;
4
using System.Collections.Generic;
5
using System.IO;
6
using System.Linq;
7
using System.Threading.Tasks;
8
using AsmResolver.DotNet;
9
using AsmResolver.DotNet.Builder;
10
using AsmResolver.PE.Builder;
11
using AsmResolver.PE.DotNet.Metadata.Tables;
12
using Cpp2IL.Core.Api;
13
using Cpp2IL.Core.Logging;
14
using Cpp2IL.Core.Model.Contexts;
15
using Cpp2IL.Core.Utils;
16
using Cpp2IL.Core.Utils.AsmResolver;
17
using LibCpp2IL.Metadata;
18

19
namespace Cpp2IL.Core.OutputFormats;
20

21
public abstract class AsmResolverDllOutputFormat : Cpp2IlOutputFormat
22
{
23
    private AssemblyDefinition? MostRecentCorLib { get; set; }
225✔
24
    protected int TotalMethodCount;
25
    protected int SuccessfulMethodCount;
26

27
    public sealed override void DoOutput(ApplicationAnalysisContext context, string outputRoot)
28
    {
29
        var ret = BuildAssemblies(context);
×
30

31
        var start = DateTime.Now;
×
32
        Logger.Verbose("Generating PE images...", "DllOutput");
×
33

34
        if (!Directory.Exists(outputRoot))
×
35
            Directory.CreateDirectory(outputRoot);
×
36

37
        //Convert assembly definitions to PE files
38
        var peImagesToWrite = ret
×
39
            .AsParallel()
×
40
            .Select(a => (image: a.ManifestModule!.ToPEImage(new ManagedPEImageBuilder()), name: a.ManifestModule.Name!))
×
41
            .ToList();
×
42

43
        Logger.VerboseNewline($"{(DateTime.Now - start).TotalMilliseconds:F1}ms", "DllOutput");
×
44

45
        start = DateTime.Now;
×
46
        Logger.Verbose("Building and writing managed PE files to disk...", "DllOutput");
×
47

48
        //Save them
49
        var fileBuilder = new ManagedPEFileBuilder();
×
50
        foreach (var (image, name) in peImagesToWrite)
×
51
        {
52
            var dllPath = Path.Combine(outputRoot, name);
×
53
            fileBuilder.CreateFile(image).Write(dllPath);
×
54
        }
55

56
        Logger.VerboseNewline($"{(DateTime.Now - start).TotalMilliseconds:F1}ms", "DllOutput");
×
57

NEW
58
        if (TotalMethodCount != 0)
×
59
        {
NEW
60
            var percent = Math.Round((SuccessfulMethodCount / (float)TotalMethodCount) * 100);
×
NEW
61
            Logger.InfoNewline($"{percent}% of methods successfully decompiled ({SuccessfulMethodCount} / {TotalMethodCount})", "DllOutput");
×
62
        }
63
    }
×
64

65
    public virtual List<AssemblyDefinition> BuildAssemblies(ApplicationAnalysisContext context)
66
    {
67
#if VERBOSE_LOGGING
68
        var asmCount = context.Assemblies.Count;
69
        var typeCount = context.AllTypes.Count();
70
        var methodCount = context.AllTypes.SelectMany(t => t.Methods).Count();
71
        var fieldCount = context.AllTypes.SelectMany(t => t.Fields).Count();
72
        var propertyCount = context.AllTypes.SelectMany(t => t.Properties).Count();
73
        var eventCount = context.AllTypes.SelectMany(t => t.Events).Count();
74
#endif
75

76
        //Build the stub assemblies
77
        var start = DateTime.Now;
5✔
78
#if VERBOSE_LOGGING
79
        Logger.Verbose($"Building stub assemblies ({asmCount} assemblies, {typeCount} types)...", "DllOutput");
80
#else
81
        Logger.Verbose($"Building stub assemblies...", "DllOutput");
5✔
82
#endif
83
        List<AssemblyDefinition> ret = BuildStubAssemblies(context);
5✔
84
        Logger.VerboseNewline($"{(DateTime.Now - start).TotalMilliseconds:F1}ms", "DllOutput");
5✔
85

86
        start = DateTime.Now;
5✔
87
        Logger.Verbose("Configuring inheritance and generics...", "DllOutput");
5✔
88

89
        Parallel.ForEach(context.Assemblies, AsmResolverAssemblyPopulator.ConfigureHierarchy);
5✔
90

91
        Logger.VerboseNewline($"{(DateTime.Now - start).TotalMilliseconds:F1}ms", "DllOutput");
5✔
92

93
        //Populate them
94
        start = DateTime.Now;
5✔
95

96
#if VERBOSE_LOGGING
97
        Logger.Verbose($"Adding {fieldCount} fields, {methodCount} methods, {propertyCount} properties, and {eventCount} events (in parallel)...", "DllOutput");
98
#else
99
        Logger.Verbose($"Adding fields, methods, properties, and events (in parallel)...", "DllOutput");
5✔
100
#endif
101

102
        MiscUtils.ExecuteParallel(context.Assemblies, AsmResolverAssemblyPopulator.CopyDataFromIl2CppToManaged);
5✔
103
        MiscUtils.ExecuteParallel(context.Assemblies, AsmResolverAssemblyPopulator.AddExplicitInterfaceImplementations);
5✔
104

105
        Logger.VerboseNewline($"{(DateTime.Now - start).TotalMilliseconds:F1}ms", "DllOutput");
5✔
106

107
        //Populate custom attributes
108
        start = DateTime.Now;
5✔
109
        Logger.Verbose("Adding custom attributes to all of the above...", "DllOutput");
5✔
110
        MiscUtils.ExecuteParallel(context.Assemblies, AsmResolverAssemblyPopulator.PopulateCustomAttributes);
5✔
111

112
        Logger.VerboseNewline($"{(DateTime.Now - start).TotalMilliseconds:F1}ms", "DllOutput");
5✔
113

114
        //Fill method bodies - this should always be done last
115
        start = DateTime.Now;
5✔
116
        Logger.Verbose($"Filling method bodies (in parallel)...", "DllOutput");
5✔
117
        MiscUtils.ExecuteParallel(context.Assemblies, FillMethodBodies);
5✔
118

119
        Logger.VerboseNewline($"{(DateTime.Now - start).TotalMilliseconds:F1}ms", "DllOutput");
5✔
120

121
        TypeDefinitionsAsmResolver.Reset();
5✔
122

123
        return ret;
5✔
124
    }
125

126
    protected abstract void FillMethodBody(MethodDefinition methodDefinition, MethodAnalysisContext methodContext);
127

128
    protected virtual void FillMethodBodies(AssemblyAnalysisContext context)
129
    {
130
        foreach (var typeContext in context.Types)
29,680✔
131
        {
132
            if (AsmResolverAssemblyPopulator.IsTypeContextModule(typeContext))
14,630✔
133
                continue;
134

135
#if !DEBUG
136
            try
137
#endif
138
            {
139
                foreach (var methodCtx in typeContext.Methods)
202,900✔
140
                {
141
                    var managedMethod = methodCtx.GetExtraData<MethodDefinition>("AsmResolverMethod") ?? throw new($"AsmResolver method not found in method analysis context for {typeContext.FullName}.{methodCtx.Name}");
87,030!
142

143
                    FillMethodBody(managedMethod, methodCtx);
87,030✔
144
                }
145
            }
14,420✔
146
#if !DEBUG
147
            catch (System.Exception e)
×
148
            {
149
                var managedType = typeContext.GetExtraData<TypeDefinition>("AsmResolverType") ?? throw new($"AsmResolver type not found in type analysis context for {typeContext.FullName}");
×
150
                throw new($"Failed to process type {managedType.FullName} (module {managedType.DeclaringModule?.Name}, declaring type {managedType.DeclaringType?.FullName}) in {context.Name}", e);
×
151
            }
152
#endif
153
        }
154
    }
210✔
155

156
    private List<AssemblyDefinition> BuildStubAssemblies(ApplicationAnalysisContext context)
157
    {
158
        var assemblyResolver = new Il2CppAssemblyResolver();
5✔
159
        var metadataResolver = new DefaultMetadataResolver(assemblyResolver);
5✔
160

161
        var corlib = context.Assemblies.First(a => a.Name == "mscorlib");
10✔
162
        MostRecentCorLib = BuildStubAssembly(corlib, null, metadataResolver);
5✔
163
        assemblyResolver.DummyAssemblies.Add(MostRecentCorLib.Name!, MostRecentCorLib);
5✔
164

165
        var ret = context.Assemblies
5✔
166
            // .AsParallel()
5✔
167
            .Where(a => a.Name != "mscorlib")
210✔
168
            .Select(a => BuildStubAssembly(a, MostRecentCorLib, metadataResolver))
205✔
169
            .ToList();
5✔
170

171
        ret.ForEach(a => assemblyResolver.DummyAssemblies.Add(a.Name!, a));
210✔
172

173
        ret.Add(MostRecentCorLib);
5✔
174
        return ret;
5✔
175
    }
176

177
    private static AssemblyDefinition BuildStubAssembly(AssemblyAnalysisContext assemblyContext, AssemblyDefinition? corLib, IMetadataResolver metadataResolver)
178
    {
179
        //Get the name of the assembly (= the name of the DLL without the file extension)
180
        //Build an AsmResolver assembly from this definition
181
        var ourAssembly = new AssemblyDefinition(assemblyContext.Name, assemblyContext.Version)
210✔
182
        {
210✔
183
            HashAlgorithm = (AssemblyHashAlgorithm)assemblyContext.HashAlgorithm,
210✔
184
            Attributes = (AssemblyAttributes)assemblyContext.Flags,
210✔
185
            Culture = assemblyContext.Culture,
210✔
186
            PublicKey = assemblyContext.PublicKey,
210✔
187
        };
210✔
188

189
        //Setting the corlib module allows element types in references to that assembly to be set correctly without us having to manually set them.
190
        var moduleName = assemblyContext.CleanAssemblyName + ".dll";
210✔
191

192
        //Use either ourself as corlib, if we are corlib, otherwise the provided one
193
        var managedModule = new ModuleDefinition(moduleName, corLib is not null ? new(corLib) : null)
210✔
194
        {
210✔
195
            MetadataResolver = metadataResolver
210✔
196
        };
210✔
197
        ourAssembly.Modules.Add(managedModule);
210✔
198

199
        foreach (var il2CppTypeDefinition in assemblyContext.TopLevelTypes)
23,340✔
200
        {
201
            if (il2CppTypeDefinition.Name != "<Module>")
11,460✔
202
                //We skip module because I've never come across an il2cpp assembly with any top-level functions, and it's simpler to skip it as AsmResolver adds one by default.
203
                managedModule.TopLevelTypes.Add(BuildStubType(il2CppTypeDefinition));
11,250✔
204
        }
205

206
        if (corLib == null)
210✔
207
        {
208
            //We *are* the corlib, so cache defs now
209
            TypeDefinitionsAsmResolver.CacheNeededTypeDefinitions();
5✔
210
        }
211

212
        //We can get issues with consumers of the API if the base type is not set correctly for value types or enums, so we set it here (as early as possible) if we can
213
        foreach (var assemblyContextType in assemblyContext.Types)
29,680✔
214
        {
215
            if (assemblyContextType.Definition is not { } def || assemblyContextType.GetExtraData<TypeDefinition>("AsmResolverType") is not { } asmResolverType)
14,630✔
216
                continue;
217

218
            if (def.IsValueType)
14,420✔
219
                asmResolverType.BaseType = managedModule.DefaultImporter.ImportType(TypeDefinitionsAsmResolver.ValueType);
4,475✔
220
            else if (def.IsEnumType)
9,945!
221
                asmResolverType.BaseType = managedModule.DefaultImporter.ImportType(TypeDefinitionsAsmResolver.Enum);
×
222
        }
223

224
        //Store the managed assembly in the context so we can use it later.
225
        assemblyContext.PutExtraData("AsmResolverAssembly", ourAssembly);
210✔
226

227
        return ourAssembly;
210✔
228
    }
229

230
    private static TypeDefinition BuildStubType(TypeAnalysisContext typeContext)
231
    {
232
        var typeDef = typeContext.Definition;
14,420✔
233

234
        //Initialize an empty type definition
235
        var ret = new TypeDefinition(typeContext.Namespace, typeContext.Name, (TypeAttributes)typeContext.Attributes);
14,420✔
236

237
        //Set up its layout
238
        if (typeDef != null)
14,420✔
239
            ConfigureTypeSize(typeDef, ret);
14,420✔
240

241
        //Create nested types
242
        foreach (var cppNestedType in typeContext.NestedTypes)
35,180✔
243
            ret.NestedTypes.Add(BuildStubType(cppNestedType));
3,170✔
244

245
        //Associate this asm resolve td with the type context
246
        typeContext.PutExtraData("AsmResolverType", ret);
14,420✔
247

248
        //Add to the lookup-by-id table used by the resolver
249
        if (typeDef != null)
14,420✔
250
            AsmResolverUtils.TypeDefsByIndex[typeDef.TypeIndex] = ret;
14,420✔
251

252
        return ret;
14,420✔
253
    }
254

255
    private static void ConfigureTypeSize(Il2CppTypeDefinition il2CppDefinition, TypeDefinition asmResolverDefinition)
256
    {
257
        if (!il2CppDefinition.IsValueType || il2CppDefinition.IsEnumType)
14,420✔
258
            return; // Only structs can have their layout changed
11,865✔
259

260
        ushort packingSize = 0;
2,555✔
261
        var classSize = 0U;
2,555✔
262
        if (!il2CppDefinition.PackingSizeIsDefault)
2,555✔
263
            packingSize = (ushort)il2CppDefinition.PackingSize;
915✔
264

265
        if (!il2CppDefinition.ClassSizeIsDefault)
2,555✔
266
        {
267
            if (il2CppDefinition.Size > 1 << 30)
915!
268
                throw new Exception($"Got invalid size for type {il2CppDefinition}: {il2CppDefinition.RawSizes}");
×
269

270
            if (il2CppDefinition.Size != -1)
915!
271
                classSize = (uint)il2CppDefinition.Size;
915✔
272
            else
273
                classSize = 0; //Not sure what this value actually implies but it seems to work
×
274
        }
275

276
        if (packingSize != 0 || classSize != 0)
2,555✔
277
            asmResolverDefinition.ClassLayout = new(packingSize, classSize);
915✔
278
    }
2,555✔
279
}
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