• 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

0.0
/Cpp2IL.Core/IlGenerator.cs
1
using System.Collections.Generic;
2
using System.Linq;
3
using AsmResolver.DotNet;
4
using AsmResolver.DotNet.Code.Cil;
5
using AsmResolver.DotNet.Signatures;
6
using AsmResolver.PE.DotNet.Cil;
7
using Cpp2IL.Core.Graphs;
8
using Cpp2IL.Core.ISIL;
9
using Cpp2IL.Core.Model.Contexts;
10
using Cpp2IL.Core.Utils.AsmResolver;
11

12
namespace Cpp2IL.Core;
13

14
public static class IlGenerator
15
{
16
    public static void GenerateIl(MethodAnalysisContext context, MethodDefinition definition)
17
    {
NEW
18
        var assembly = context.DeclaringType!.DeclaringAssembly;
×
NEW
19
        var module = definition.Module!;
×
NEW
20
        var importer = module.DefaultImporter;
×
NEW
21
        var factory = module.CorLibTypeFactory;
×
22

NEW
23
        var writeLine = factory.CorLibScope
×
NEW
24
            .CreateTypeReference("System", "Console")
×
NEW
25
            .CreateMemberReference("WriteLine", MethodSignature.CreateStatic(factory.Void, factory.String))
×
NEW
26
            .ImportWith(importer);
×
27

NEW
28
        var stringType = factory.CorLibScope.CreateTypeReference("System", "String");
×
NEW
29
        var stringCtor = stringType
×
NEW
30
            .CreateMemberReference(".ctor", MethodSignature.CreateStatic(stringType.ToTypeSignature(), factory.String))
×
NEW
31
            .ImportWith(importer);
×
32

33
        // Change branch targets to instructions
NEW
34
        foreach (var instruction in context.ControlFlowGraph!.Blocks.SelectMany(block => block.Instructions))
×
35
        {
NEW
36
            if (instruction.Operands.Count > 0 && instruction.Operands[0] is Block target)
×
37
            {
NEW
38
                if (target.Instructions.Count > 0)
×
NEW
39
                    instruction.Operands[0] = target.Instructions[^1];
×
40
            }
41
        }
42

NEW
43
        var body = new CilMethodBody(definition)
×
NEW
44
        {
×
NEW
45
            InitializeLocals = true, // Without this ILSpy does: CompilerServices.Unsafe.SkipInit(out object obj);
×
NEW
46
            ComputeMaxStackOnBuild = false // There's stack imbalance somewhere, but this works for now
×
NEW
47
        };
×
48

NEW
49
        definition.CilMethodBody = body;
×
50

51
        // Make sure context.Locals actually has all locals (idk why it doesn't sometimes)
NEW
52
        foreach (var operand in context.ControlFlowGraph.Instructions.SelectMany(i => i.Operands))
×
53
        {
NEW
54
            LocalVariable? local = null;
×
55

NEW
56
            if (operand is FieldReference field)
×
NEW
57
                local = field.Local;
×
58

NEW
59
            if (operand is LocalVariable local2)
×
NEW
60
                local = local2;
×
61

NEW
62
            if (operand is MemoryOperand memory && memory.Base is LocalVariable local3)
×
NEW
63
                local = local3;
×
64

NEW
65
            if (local != null && !context.Locals.Contains(local))
×
NEW
66
                context.Locals.Add(local);
×
67
        }
68

69
        // Map ISIL locals to IL
NEW
70
        Dictionary<LocalVariable, CilLocalVariable> locals = [];
×
NEW
71
        foreach (var local in context.Locals)
×
72
        {
73
            TypeSignature ilType;
74

75
            // Use object if type couldn't be determined
NEW
76
            if (local.Type != null)
×
NEW
77
                ilType = local.Type.ToTypeSignature(module);
×
78
            else
NEW
79
                ilType = module.CorLibTypeFactory.Object;
×
80

NEW
81
            var ilLocal = new CilLocalVariable(ilType);
×
NEW
82
            body.LocalVariables.Add(ilLocal);
×
NEW
83
            locals.Add(local, ilLocal);
×
84
        }
85

86
        /* foreach (var instruction in context.ControlFlowGraph!.Instructions)
87
        {
88
            body.Instructions.Add(CilOpCodes.Ldstr, instruction.ToString());
89
            body.Instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!));
90
        }
91
        body.Instructions.Add(CilOpCodes.Ldstr, "-------------------------------------------------------------------------");
92
        body.Instructions.Add(CilOpCodes.Call, _importer!.ImportMethod(_writeLine!)); */
93

94
        // Generate IL
NEW
95
        Dictionary<Instruction, List<CilInstruction>> instructionMap = [];
×
NEW
96
        foreach (var instruction in context.ControlFlowGraph!.Instructions) // context.ConvertedIsil is probably not up to date anymore here
×
NEW
97
            instructionMap.Add(instruction, GenerateInstructions(instruction, context, definition, locals, writeLine, stringCtor));
×
98

99
        // Set IL branch targets
NEW
100
        foreach (var kvp in instructionMap)
×
101
        {
NEW
102
            var instruction = kvp.Key;
×
NEW
103
            var il = kvp.Value;
×
104

NEW
105
            if (instruction.OpCode == OpCode.Jump || instruction.OpCode == OpCode.ConditionalJump)
×
106
            {
NEW
107
                var ilBranch = il.First(i => i.OpCode == CilOpCodes.Br || i.OpCode == CilOpCodes.Brtrue);
×
108

NEW
109
                if (instruction.Operands[0] is Block targetBlock)
×
110
                {
NEW
111
                    context.AddWarning($"Branch target block not in cfg: {instruction} ({targetBlock})");
×
NEW
112
                    ilBranch.OpCode = CilOpCodes.Nop;
×
NEW
113
                    ilBranch.Operand = null;
×
NEW
114
                    continue;
×
115
                }
116

NEW
117
                var target = (Instruction)instruction.Operands[0];
×
118

NEW
119
                if (!instructionMap.ContainsKey(target))
×
120
                {
NEW
121
                    context.AddWarning($"Branch target not in ISIL to IL map: {instruction} --- {target}");
×
NEW
122
                    ilBranch.OpCode = CilOpCodes.Nop;
×
NEW
123
                    ilBranch.Operand = null;
×
NEW
124
                    continue;
×
125
                }
126

NEW
127
                ilBranch.Operand = new CilInstructionLabel(instructionMap[target][0]);
×
128
            }
129
        }
130

131
        // Add analysis warnings
NEW
132
        var instructions = body.Instructions;
×
NEW
133
        foreach (var warning in context.AnalysisWarnings)
×
134
        {
NEW
135
            instructions.Add(CilOpCodes.Ldstr, "Warning: " + warning);
×
NEW
136
            instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
137
        }
NEW
138
    }
×
139

140
    private static List<CilInstruction> GenerateInstructions(Instruction instruction, MethodAnalysisContext context,
141
        MethodDefinition method, Dictionary<LocalVariable, CilLocalVariable> locals, MemberReference writeLine, MemberReference stringCtor)
142
    {
NEW
143
        var body = method.CilMethodBody!;
×
NEW
144
        var instructions = body.Instructions;
×
NEW
145
        var currentCount = instructions.Count;
×
NEW
146
        var startIndex = instructions.Count;
×
147

NEW
148
        var module = method.Module!;
×
NEW
149
        var importer = module.DefaultImporter!;
×
150

151
        switch (instruction.OpCode)
152
        {
153
            case OpCode.Invalid:
NEW
154
                instructions.Add(CilOpCodes.Ldstr, $"Invalid instruction: {instruction}");
×
NEW
155
                instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
NEW
156
                break;
×
157

158
            case OpCode.NotImplemented:
NEW
159
                instructions.Add(CilOpCodes.Ldstr, $"Not implemented instruction: {instruction.Operands[0]}");
×
NEW
160
                instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
NEW
161
                break;
×
162

163
            case OpCode.Interrupt:
164
            case OpCode.Nop:
NEW
165
                instructions.Add(CilOpCodes.Nop);
×
NEW
166
                break;
×
167

168
            case OpCode.Move:
NEW
169
                if (instruction.Operands[0] is FieldReference field) // stfld takes instance before value so LoadOperand StoreToOperand doesn't work
×
170
                {
NEW
171
                    var param = method.Parameters.FirstOrDefault(p => p.Name == field.Local.Name);
×
NEW
172
                    if (param != null)
×
NEW
173
                        instructions.Add(CilOpCodes.Ldarg, param);
×
NEW
174
                    else if (field.Local.IsThis)
×
NEW
175
                        instructions.Add(CilOpCodes.Ldarg_0);
×
176
                    else
NEW
177
                        instructions.Add(CilOpCodes.Ldloc, locals[field.Local]);
×
178

NEW
179
                    LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor);
×
NEW
180
                    instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(module));
×
NEW
181
                    break;
×
182
                }
183

NEW
184
                LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor);
×
NEW
185
                StoreToOperand(instruction.Operands[0], method, locals, writeLine);
×
NEW
186
                break;
×
187

188
            case OpCode.Phi:
NEW
189
                instructions.Add(CilOpCodes.Ldstr, $"Phi opcodes should not exist at this point in decompilation ({instruction})");
×
NEW
190
                instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
NEW
191
                break;
×
192

193
            case OpCode.Call:
194
            case OpCode.CallVoid:
NEW
195
                if (instruction.Operands[0] is not MethodAnalysisContext targetMethod)
×
196
                {
NEW
197
                    if (instruction.Operands[0] is ulong targetAddress)
×
NEW
198
                        instructions.Add(CilOpCodes.Ldstr, $"Method not found @{targetAddress:X}");
×
199
                    else // Probably key function
NEW
200
                        instructions.Add(CilOpCodes.Ldstr, $"Unknown call target operand: {instruction}");
×
201

NEW
202
                    instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
NEW
203
                    break;
×
204
                }
205

NEW
206
                var importedMethod = importer.ImportMethod(targetMethod.ToMethodDescriptor(module));
×
NEW
207
                var resolvedMethod = importedMethod.Resolve()!;
×
208

NEW
209
                var thisParamIndex = instruction.OpCode == OpCode.Call ? 2 : 1;
×
210

NEW
211
                if (!resolvedMethod.IsStatic) // Load 'this' param
×
212
                {
NEW
213
                    if ((instruction.Operands.Count - 1) >= thisParamIndex)
×
NEW
214
                        LoadOperand(instruction.Operands[thisParamIndex], method, locals, writeLine, stringCtor);
×
215
                    else
NEW
216
                        instructions.Add(CilOpCodes.Ldstr, $"Non static method called without 'this' param ({instruction})");
×
217
                }
218

219
                // Load normal params
NEW
220
                var callParams = instruction.Operands.Skip(thisParamIndex + (resolvedMethod.IsStatic ? 0 : -1));
×
NEW
221
                foreach (var param in callParams)
×
NEW
222
                    LoadOperand(param, method, locals, writeLine, stringCtor);
×
223

NEW
224
                instructions.Add(CilOpCodes.Call, importedMethod);
×
225

NEW
226
                if (instruction.OpCode == OpCode.Call) // Store return value
×
NEW
227
                    StoreToOperand(instruction.Operands[1], method, locals, writeLine);
×
228

NEW
229
                break;
×
230

231
            case OpCode.IndirectCall:
NEW
232
                instructions.Add(CilOpCodes.Ldstr, $"Indirect call: {instruction} (should have been resolved before IL gen)");
×
NEW
233
                instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
NEW
234
                break;
×
235

236
            case OpCode.Return:
NEW
237
                if (!context.IsVoid && instruction.Operands.Count == 1)
×
NEW
238
                    LoadOperand(instruction.Operands[0], method, locals, writeLine, stringCtor);
×
NEW
239
                instructions.Add(CilOpCodes.Ret);
×
NEW
240
                break;
×
241

242
            case OpCode.Jump:
NEW
243
                instructions.Add(CilOpCodes.Br, new CilInstructionLabel());
×
NEW
244
                break;
×
245

246
            case OpCode.ConditionalJump:
NEW
247
                LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor);
×
NEW
248
                instructions.Add(CilOpCodes.Brtrue, new CilInstructionLabel());
×
NEW
249
                break;
×
250

251
            case OpCode.IndirectJump:
NEW
252
                instructions.Add(CilOpCodes.Ldstr, $"Indirect jump: {instruction} (should have been resolved before IL gen)");
×
NEW
253
                instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
NEW
254
                break;
×
255

256
            case OpCode.ShiftStack:
NEW
257
                instructions.Add(CilOpCodes.Ldstr, $"Stack shift: {instruction} (stack analysis should have removed these)");
×
NEW
258
                instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
NEW
259
                break;
×
260

261
            case OpCode.CheckEqual:
262
            case OpCode.CheckGreater:
263
            case OpCode.CheckLess:
264

265
            case OpCode.Add:
266
            case OpCode.Subtract:
267
            case OpCode.Multiply:
268
            case OpCode.Divide:
269

270
            case OpCode.ShiftLeft:
271
            case OpCode.ShiftRight:
272

273
            case OpCode.And:
274
            case OpCode.Or:
275
            case OpCode.Xor:
NEW
276
                LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor);
×
NEW
277
                LoadOperand(instruction.Operands[2], method, locals, writeLine, stringCtor);
×
278

NEW
279
                switch (instruction.OpCode)
×
280
                {
NEW
281
                    case OpCode.CheckEqual: instructions.Add(CilOpCodes.Ceq); break;
×
NEW
282
                    case OpCode.CheckGreater: instructions.Add(CilOpCodes.Cgt); break;
×
NEW
283
                    case OpCode.CheckLess: instructions.Add(CilOpCodes.Clt); break;
×
284

NEW
285
                    case OpCode.Add: instructions.Add(CilOpCodes.Add); break;
×
NEW
286
                    case OpCode.Subtract: instructions.Add(CilOpCodes.Sub); break;
×
NEW
287
                    case OpCode.Multiply: instructions.Add(CilOpCodes.Mul); break;
×
NEW
288
                    case OpCode.Divide: instructions.Add(CilOpCodes.Div); break;
×
289

NEW
290
                    case OpCode.ShiftLeft: instructions.Add(CilOpCodes.Shl); break;
×
NEW
291
                    case OpCode.ShiftRight: instructions.Add(CilOpCodes.Shr); break;
×
292

NEW
293
                    case OpCode.And: instructions.Add(CilOpCodes.And); break;
×
NEW
294
                    case OpCode.Or: instructions.Add(CilOpCodes.Or); break;
×
NEW
295
                    case OpCode.Xor: instructions.Add(CilOpCodes.Xor); break;
×
296
                }
297

NEW
298
                StoreToOperand(instruction.Operands[0], method, locals, writeLine);
×
NEW
299
                break;
×
300

301
            case OpCode.Not:
302
            case OpCode.Negate:
NEW
303
                LoadOperand(instruction.Operands[1], method, locals, writeLine, stringCtor);
×
304

NEW
305
                switch (instruction.OpCode)
×
306
                {
NEW
307
                    case OpCode.Not: instructions.Add(CilOpCodes.Not); break;
×
NEW
308
                    case OpCode.Negate: instructions.Add(CilOpCodes.Neg); break;
×
309
                }
310

NEW
311
                StoreToOperand(instruction.Operands[0], method, locals, writeLine);
×
NEW
312
                break;
×
313

314
            default:
NEW
315
                instructions.Add(CilOpCodes.Ldstr, $"Unknown instruction: {instruction}");
×
NEW
316
                instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
317
                break;
318
        }
319

NEW
320
        return instructions.ToList().GetRange(startIndex, instructions.Count - startIndex); // Return added IL
×
321
    }
322

323
    private static void LoadOperand(object operand, MethodDefinition method,
324
        Dictionary<LocalVariable, CilLocalVariable> locals, MemberReference writeLine, MemberReference stringCtor)
325
    {
NEW
326
        var instructions = method.CilMethodBody!.Instructions;
×
327

NEW
328
        var module = method.Module!;
×
NEW
329
        var importer = module.DefaultImporter!;
×
330

331
        switch (operand)
332
        {
333
            case int i:
NEW
334
                instructions.Add(CilOpCodes.Ldc_I4, i);
×
NEW
335
                break;
×
336
            case uint ui:
NEW
337
                instructions.Add(CilOpCodes.Ldc_I4, unchecked((int)ui));
×
NEW
338
                break;
×
339
            case short s:
NEW
340
                instructions.Add(CilOpCodes.Ldc_I4, s);
×
NEW
341
                break;
×
342
            case ushort us:
NEW
343
                instructions.Add(CilOpCodes.Ldc_I4, us);
×
NEW
344
                break;
×
345
            case byte b8:
NEW
346
                instructions.Add(CilOpCodes.Ldc_I4, b8);
×
NEW
347
                break;
×
348
            case sbyte sb8:
NEW
349
                instructions.Add(CilOpCodes.Ldc_I4, sb8);
×
NEW
350
                break;
×
351
            case long l:
NEW
352
                instructions.Add(CilOpCodes.Ldc_I8, l);
×
NEW
353
                break;
×
354
            case ulong ul:
NEW
355
                instructions.Add(CilOpCodes.Ldc_I8, unchecked((long)ul));
×
NEW
356
                break;
×
357
            case float f:
NEW
358
                instructions.Add(CilOpCodes.Ldc_R4, f);
×
NEW
359
                break;
×
360
            case double d:
NEW
361
                instructions.Add(CilOpCodes.Ldc_R8, d);
×
NEW
362
                break;
×
363
            case bool b:
NEW
364
                instructions.Add(CilOpCodes.Ldc_I4, b ? 1 : 0);
×
NEW
365
                break;
×
366
            case string s:
NEW
367
                instructions.Add(CilOpCodes.Ldstr, s);
×
NEW
368
                break;
×
369
            case LocalVariable local:
NEW
370
                var param = method.Parameters.FirstOrDefault(p => p.Name == local.Name);
×
NEW
371
                if (param != null)
×
NEW
372
                    instructions.Add(CilOpCodes.Ldarg, param);
×
373
                else
NEW
374
                    instructions.Add(CilOpCodes.Ldloc, locals[local]);
×
NEW
375
                break;
×
376
            case FieldReference field:
NEW
377
                instructions.Add(CilOpCodes.Ldarg_0); // TODO: Use local instead of 'this' without causing stack imbalance, i have no idea why that happens
×
378
                //instructions.Add(CilOpCodes.Ldloca, _locals[field.Local]);
NEW
379
                instructions.Add(CilOpCodes.Ldfld, field.Field.ToFieldDescriptor(module));
×
NEW
380
                break;
×
381
            case MemoryOperand memory:
NEW
382
                if (memory.Index == null && memory.Addend == 0 && memory.Scale == 0
×
NEW
383
                    && memory.Base is LocalVariable local2)
×
384
                {
NEW
385
                    var param2 = method.Parameters.FirstOrDefault(p => p.Name == local2.Name);
×
NEW
386
                    if (param2 != null)
×
NEW
387
                        instructions.Add(CilOpCodes.Ldarg, param2);
×
388
                    else
NEW
389
                        instructions.Add(CilOpCodes.Ldloc, locals[local2]);
×
NEW
390
                    break;
×
391
                }
NEW
392
                instructions.Add(CilOpCodes.Ldstr, "Unmanaged memory load: " + operand.ToString());
×
NEW
393
                instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(stringCtor));
×
NEW
394
                break;
×
395
            case TypeAnalysisContext type:
NEW
396
                if (type.Name == "T")
×
397
                {
398
                    // idk what to do here
NEW
399
                    instructions.Add(CilOpCodes.Ldstr, "<T>");
×
NEW
400
                    instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(stringCtor));
×
NEW
401
                    break;
×
402
                }
403

NEW
404
                var cilType = type.ToTypeSignature(module!).Resolve()!;
×
405

406
                // Try to first get constructor without params
NEW
407
                var constructor = cilType.Methods.FirstOrDefault(m => m.ParameterDefinitions.Count == 0 && m.Name == ".ctor" || m.Name == ".cctor");
×
NEW
408
                constructor ??= cilType.Methods.FirstOrDefault(m => m.Name == ".ctor" || m.Name == ".cctor");
×
409

NEW
410
                if (constructor == null)
×
411
                {
NEW
412
                    instructions.Add(CilOpCodes.Ldstr, $"Constructor not found for: {operand} (probably static type)");
×
NEW
413
                    instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
NEW
414
                    break;
×
415
                }
416

NEW
417
                foreach (var param2 in constructor.ParameterDefinitions)
×
NEW
418
                    instructions.Add(CilOpCodes.Ldstr, "Constructor param: " + param2.ToString());
×
NEW
419
                instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(constructor));
×
NEW
420
                break;
×
421
            default:
NEW
422
                instructions.Add(CilOpCodes.Ldstr, "Unknown operand: " + operand.ToString());
×
NEW
423
                instructions.Add(CilOpCodes.Newobj, importer.ImportMethod(stringCtor));
×
424
                break;
425
        }
NEW
426
    }
×
427

428
    private static void StoreToOperand(object operand, MethodDefinition method,
429
        Dictionary<LocalVariable, CilLocalVariable> locals, MemberReference writeLine)
430
    {
NEW
431
        var instructions = method.CilMethodBody!.Instructions;
×
432

NEW
433
        var module = method.Module!;
×
NEW
434
        var importer = module.DefaultImporter!;
×
435

436
        switch (operand)
437
        {
438
            case LocalVariable local:
NEW
439
                instructions.Add(CilOpCodes.Stloc, locals[local]);
×
NEW
440
                break;
×
441

442
            case FieldReference field:
NEW
443
                instructions.Add(CilOpCodes.Ldarg_0);
×
NEW
444
                instructions.Add(CilOpCodes.Stfld, field.Field.ToFieldDescriptor(module));
×
NEW
445
                break;
×
446

447
            case MemoryOperand memory:
NEW
448
                if (memory.Index == null && memory.Addend == 0 && memory.Scale == 0
×
NEW
449
                    && memory.Base is LocalVariable local2)
×
450
                {
451
                    // Can pointer assignments just be ignored because it's C#? (Move [local], 123)
NEW
452
                    instructions.Add(CilOpCodes.Stloc, locals[local2]);
×
453
                }
NEW
454
                break;
×
455

456
            default:
NEW
457
                instructions.Add(CilOpCodes.Ldstr, $"Store into unknown operand: {operand}");
×
NEW
458
                instructions.Add(CilOpCodes.Call, importer.ImportMethod(writeLine));
×
459
                break;
460
        }
NEW
461
    }
×
462
}
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