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

SamboyCoding / Cpp2IL / 13988197905

21 Mar 2025 09:03AM UTC coverage: 23.407% (-3.8%) from 27.187%
13988197905

Pull #426

github

web-flow
Merge 4aa36af61 into 76a9b72af
Pull Request #426: ISIL -> CIL Decompiler

1270 of 7736 branches covered (16.42%)

Branch coverage included in aggregate %.

5 of 1526 new or added lines in 31 files covered. (0.33%)

4 existing lines in 3 files now uncovered.

3383 of 12143 relevant lines covered (27.86%)

107291.03 hits per line

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

0.09
/Cpp2IL.Core/InstructionSets/X86InstructionSet.cs
1
using System;
2
using System.Collections.Concurrent;
3
using System.Collections.Generic;
4
using System.Linq;
5
using Cpp2IL.Core.Api;
6
using Cpp2IL.Core.Extensions;
7
using Cpp2IL.Core.Il2CppApiFunctions;
8
using Cpp2IL.Core.ISIL;
9
using Cpp2IL.Core.Model.Contexts;
10
using Cpp2IL.Core.Utils;
11
using Cpp2IL.Core.Utils.AsmResolver;
12
using Cpp2IL.Decompiler;
13
using Cpp2IL.Decompiler.IL;
14
using Iced.Intel;
15
using LibCpp2IL.BinaryStructures;
16
using Instruction = Iced.Intel.Instruction;
17
using Register = Iced.Intel.Register;
18

19
namespace Cpp2IL.Core.InstructionSets;
20

21
// This is honestly an X64InstructionSet by all means. Everything here screams "I AM X64".
22
public class X86InstructionSet : Cpp2IlInstructionSet
23
{
24
    private struct InstructionAddress(ulong address)
25
    {
NEW
26
        public ulong Address = address;
×
NEW
27
        public override string ToString() => $"@{Address}";
×
28
    }
29

30
    private static readonly MasmFormatter Formatter = new();
×
31
    private static readonly StringOutput Output = new();
×
32

NEW
33
    private static ConcurrentDictionary<string, int> _registerNumbers = [];
×
34

NEW
35
    private static object _carryFlag = CreateRegister("cf");
×
NEW
36
    private static object _overflowFlag = CreateRegister("of");
×
NEW
37
    private static object _signFlag = CreateRegister("sf");
×
NEW
38
    private static object _zeroFlag = CreateRegister("zf");
×
NEW
39
    private static object _parityFlag = CreateRegister("pf");
×
NEW
40
    private static object _xmm0 = CreateRegister("xmm0");
×
NEW
41
    private static object _rax = CreateRegister("rax");
×
42

43
    private static string FormatInstructionInternal(Instruction instruction)
44
    {
45
        Formatter.Format(instruction, Output);
×
46
        return Output.ToStringAndReset();
×
47
    }
48

49
    public static string FormatInstruction(Instruction instruction)
50
    {
51
        lock (Formatter)
×
52
        {
53
            return FormatInstructionInternal(instruction);
×
54
        }
55
    }
×
56

57
    public override Memory<byte> GetRawBytesForMethod(MethodAnalysisContext context, bool isAttributeGenerator) => X86Utils.GetRawManagedOrCaCacheGenMethodBody(context.UnderlyingPointer, isAttributeGenerator);
385,360✔
58

59
    public override BaseKeyFunctionAddresses CreateKeyFunctionAddressesInstance() => new X86KeyFunctionAddresses();
×
60

61
    public override string PrintAssembly(MethodAnalysisContext context)
62
    {
63
        lock (Formatter)
×
64
        {
65
            var insns = X86Utils.Iterate(X86Utils.GetRawManagedOrCaCacheGenMethodBody(context.UnderlyingPointer, false), context.UnderlyingPointer);
×
66

67
            return string.Join("\n", insns.Select(FormatInstructionInternal));
×
68
        }
69
    }
×
70

71
    public override List<InstructionSetIndependentInstruction> GetIsilFromMethod(MethodAnalysisContext context)
72
    {
73
        var builder = new IsilBuilder();
×
74

75
        foreach (var instruction in X86Utils.Iterate(context))
×
76
        {
77
            ConvertInstructionStatement(instruction, builder, context);
×
78
        }
79

80
        builder.FixJumps();
×
81

82
        return builder.BackingStatementList;
×
83
    }
84

85

86
    private void ConvertInstructionStatement(Instruction instruction, IsilBuilder builder, MethodAnalysisContext context)
87
    {
88
        var callNoReturn = false;
×
89
        int operandSize;
90

91
        switch (instruction.Mnemonic)
×
92
        {
93
            case Mnemonic.Mov:
94
            case Mnemonic.Movzx: // For all intents and purposes we don't care about zero-extending
95
            case Mnemonic.Movsx: // move with sign-extendign
96
            case Mnemonic.Movsxd: // same
97
            case Mnemonic.Movaps: // Movaps is basically just a mov but with the potential future detail that the size is dependent on reg size
98
            case Mnemonic.Movups: // Movaps but unaligned
99
            case Mnemonic.Movss: // Same as movaps but for floats
100
            case Mnemonic.Movd: // Mov but specifically dword
101
            case Mnemonic.Movq: // Mov but specifically qword
102
            case Mnemonic.Movsd: // Mov but specifically double
103
            case Mnemonic.Movdqa: // Movaps but multiple integers at once in theory
104
            case Mnemonic.Cvtdq2ps: // Technically a convert double to single, but for analysis purposes we can just treat it as a move
105
            case Mnemonic.Cvtps2pd: // same, but float to double
106
            case Mnemonic.Cvttsd2si: // same, but double to integer
107
            case Mnemonic.Movdqu: // DEST[127:0] := SRC[127:0]
108
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
109
                break;
×
110
            case Mnemonic.Cbw: // AX := sign-extend AL
NEW
111
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AX)),
×
112
                    InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AL)));
×
113
                break;
×
114
            case Mnemonic.Cwde: // EAX := sign-extend AX
NEW
115
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EAX)),
×
116
                    InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AX)));
×
117
                break;
×
118
            case Mnemonic.Cdqe: // RAX := sign-extend EAX
NEW
119
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RAX)),
×
120
                    InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EAX)));
×
121
                break;
×
122
            // it's very unsafe if there's been a jump to the next instruction here before.
123
            case Mnemonic.Cwd: // Convert Word to Doubleword
124
            {
125
                // The CWD instruction copies the sign (bit 15) of the value in the AX register into every bit position in the DX register
126
                var temp = InstructionSetIndependentOperand.MakeRegister("TEMP");
×
127
                builder.Move(instruction.IP, temp, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.AX))); // TEMP = AX
×
128
                builder.ShiftRight(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(15)); // TEMP >>= 15
×
129
                builder.Compare(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(1)); // temp == 1
×
130
                builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1);
×
131
                // temp == 1 ? DX := ushort.Max (1111111111) or DX := 0
132
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.DX)), InstructionSetIndependentOperand.MakeImmediate(ushort.MaxValue));
×
133
                builder.Goto(instruction.IP, instruction.IP + 2);
×
134
                builder.Move(instruction.IP + 1, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.DX)), InstructionSetIndependentOperand.MakeImmediate(0));
×
135
                builder.Nop(instruction.IP + 2);
×
136
                break;
×
137
            }
138
            case Mnemonic.Cdq: // Convert Doubleword to Quadword
139
            {
140
                // The CDQ instruction copies the sign (bit 31) of the value in the EAX register into every bit position in the EDX register.
141
                var temp = InstructionSetIndependentOperand.MakeRegister("TEMP");
×
142
                builder.Move(instruction.IP, temp, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EAX))); // TEMP = EAX
×
143
                builder.ShiftRight(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(31)); // TEMP >>= 31
×
144
                builder.Compare(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(1)); // temp == 1
×
145
                builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1);
×
146
                // temp == 1 ? EDX := uint.Max (1111111111) or EDX := 0
147
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EDX)), InstructionSetIndependentOperand.MakeImmediate(uint.MaxValue));
×
148
                builder.Goto(instruction.IP, instruction.IP + 2);
×
149
                builder.Move(instruction.IP + 1, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.EDX)), InstructionSetIndependentOperand.MakeImmediate(0));
×
150
                builder.Nop(instruction.IP + 2);
×
151
                break;
×
152
            }
153
            case Mnemonic.Cqo: // same...
154
            {
155
                // The CQO instruction copies the sign (bit 63) of the value in the EAX register into every bit position in the RDX register.
156
                var temp = InstructionSetIndependentOperand.MakeRegister("TEMP");
×
157
                builder.Move(instruction.IP, temp, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RAX))); // TEMP = RAX
×
158
                builder.ShiftRight(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(63)); // TEMP >>= 63
×
159
                builder.Compare(instruction.IP, temp, InstructionSetIndependentOperand.MakeImmediate(1)); // temp == 1
×
160
                builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1);
×
161
                // temp == 1 ? RDX := ulong.Max (1111111111) or RDX := 0
162
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RDX)), InstructionSetIndependentOperand.MakeImmediate(ulong.MaxValue));
×
163
                builder.Goto(instruction.IP, instruction.IP + 2);
×
164
                builder.Move(instruction.IP + 1, InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(Register.RDX)), InstructionSetIndependentOperand.MakeImmediate(0));
×
165
                builder.Nop(instruction.IP + 2);
×
166
                break;
×
167
            }
168
            case Mnemonic.Lea:
169
                builder.LoadAddress(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
170
                break;
×
171
            case Mnemonic.Xor:
172
            case Mnemonic.Xorps: //xorps is just floating point xor
173
                if (instruction.Op0Kind == OpKind.Register && instruction.Op1Kind == OpKind.Register && instruction.Op0Register == instruction.Op1Register)
×
174
                    builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(0));
×
175
                else
176
                    builder.Xor(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
177
                break;
×
178
            case Mnemonic.Shl: // unsigned shift
179
            case Mnemonic.Sal: // signed shift
180
                builder.ShiftLeft(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
181
                break;
×
182
            case Mnemonic.Shr: // unsigned shift
183
            case Mnemonic.Sar: // signed shift
184
                builder.ShiftRight(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
185
                break;
×
186
            case Mnemonic.And:
187
            case Mnemonic.Andps: //Floating point and
188
                builder.And(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
189
                break;
×
190
            case Mnemonic.Or:
191
            case Mnemonic.Orps: //Floating point or
192
                builder.Or(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
193
                break;
×
194
            case Mnemonic.Not:
195
                builder.Neg(instruction.IP, ConvertOperand(instruction, 0));
×
196
                break;
×
197
            case Mnemonic.Neg: // dest := -dest
198
                builder.Neg(instruction.IP, ConvertOperand(instruction, 0));
×
199
                break;
×
200
            case Mnemonic.Imul:
201
                if (instruction.OpCount == 1)
×
202
                {
203
                    int opSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize();
×
204
                    switch (opSize) // TODO: I don't know how to work with dual registers here, I left hints though
205
                    {
206
                        case 1: // Op0 * AL -> AX
207
                            builder.Multiply(instruction.IP, Register.AX.MakeIndependent(), ConvertOperand(instruction, 0), Register.AL.MakeIndependent());
×
208
                            return;
×
209
                        case 2: // Op0 * AX -> DX:AX
210

211
                            break;
212
                        case 4: // Op0 * EAX -> EDX:EAX
213

214
                            break;
215
                        case 8: // Op0 * RAX -> RDX:RAX
216

217
                            break;
218
                        default: // prob 0, I think fallback to architecture alignment would be good here(issue: idk how to find out arch alignment)
219

220
                            break;
221
                    }
222

223
                    // if got to here, it didn't work
224
                    goto default;
225
                }
226
                else if (instruction.OpCount == 3) builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
227
                else builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
228

229
                break;
×
230
            case Mnemonic.Mulss:
231
            case Mnemonic.Vmulss:
232
                if (instruction.OpCount == 3)
×
233
                    builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
234
                else if (instruction.OpCount == 2)
×
235
                    builder.Multiply(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
236
                else
237
                    goto default;
238

239
                break;
240

241
            case Mnemonic.Divss: // Divide Scalar Single Precision Floating-Point Values. DEST[31:0] = DEST[31:0] / SRC[31:0]
242
                builder.Divide(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
243
                break;
×
244
            case Mnemonic.Vdivss: // VEX Divide Scalar Single Precision Floating-Point Values. DEST[31:0] = SRC1[31:0] / SRC2[31:0]
245
                builder.Divide(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1), ConvertOperand(instruction, 2));
×
246
                break;
×
247

248
            case Mnemonic.Ret:
249
                // TODO: Verify correctness of operation with Vectors.
250

251
                // On x32, this will require better engineering since ulongs are handled somehow differently (return in 2 registers, I think?)
252
                // The x64 prototype should work.
253
                // Are st* registers even used in il2cpp games?
254

255
                if (context.IsVoid)
×
256
                    builder.Return(instruction.IP);
×
257
                else if (context.Definition?.RawReturnType?.Type is Il2CppTypeEnum.IL2CPP_TYPE_R4 or Il2CppTypeEnum.IL2CPP_TYPE_R8)
×
258
                    builder.Return(instruction.IP, InstructionSetIndependentOperand.MakeRegister("xmm0"));
×
259
                else
260
                    builder.Return(instruction.IP, InstructionSetIndependentOperand.MakeRegister("rax"));
×
261
                break;
×
262
            case Mnemonic.Push:
263
                operandSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize();
×
264
                builder.ShiftStack(instruction.IP, -operandSize);
×
265
                builder.Move(instruction.IP, InstructionSetIndependentOperand.MakeStack(0), ConvertOperand(instruction, 0));
×
266
                break;
×
267
            case Mnemonic.Pop:
268
                operandSize = instruction.Op0Kind == OpKind.Register ? instruction.Op0Register.GetSize() : instruction.MemorySize.GetSize();
×
269
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeStack(0));
×
270
                builder.ShiftStack(instruction.IP, operandSize);
×
271
                break;
×
272
            case Mnemonic.Sub:
273
            case Mnemonic.Add:
274
                var isSubtract = instruction.Mnemonic == Mnemonic.Sub;
×
275

276
                // Special case - stack shift
277
                if (instruction.Op0Register == Register.RSP && instruction.Op1Kind.IsImmediate())
×
278
                {
279
                    var amount = (int)instruction.GetImmediate(1);
×
280
                    builder.ShiftStack(instruction.IP, isSubtract ? -amount : amount);
×
281
                    break;
×
282
                }
283

284
                var left = ConvertOperand(instruction, 0);
×
285
                var right = ConvertOperand(instruction, 1);
×
286
                if (isSubtract)
×
287
                    builder.Subtract(instruction.IP, left, left, right);
×
288
                else
289
                    builder.Add(instruction.IP, left, left, right);
×
290

291
                break;
×
292
            case Mnemonic.Addss:
293
            case Mnemonic.Subss:
294
            {
295
                // Addss and subss are just floating point add/sub, but we don't need to handle the stack stuff
296
                // But we do need to handle 2 vs 3 operand forms
297
                InstructionSetIndependentOperand dest;
298
                InstructionSetIndependentOperand src1;
299
                InstructionSetIndependentOperand src2;
300

301
                if (instruction.OpCount == 3)
×
302
                {
303
                    //dest, src1, src2
304
                    dest = ConvertOperand(instruction, 0);
×
305
                    src1 = ConvertOperand(instruction, 1);
×
306
                    src2 = ConvertOperand(instruction, 2);
×
307
                }
308
                else if (instruction.OpCount == 2)
×
309
                {
310
                    //DestAndSrc1, Src2
311
                    dest = ConvertOperand(instruction, 0);
×
312
                    src1 = dest;
×
313
                    src2 = ConvertOperand(instruction, 1);
×
314
                }
315
                else
316
                    goto default;
317

318
                if (instruction.Mnemonic == Mnemonic.Subss)
×
319
                    builder.Subtract(instruction.IP, dest, src1, src2);
×
320
                else
321
                    builder.Add(instruction.IP, dest, src1, src2);
×
322
                break;
×
323
            }
324
            // The following pair of instructions does not update the Carry Flag (CF):
325
            case Mnemonic.Dec:
326
                builder.Subtract(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(1));
×
327
                break;
×
328
            case Mnemonic.Inc:
329
                builder.Add(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(1));
×
330
                break;
×
331

332
            case Mnemonic.Shufps: // Packed Interleave Shuffle of Quadruplets of Single Precision Floating-Point Values
333
            {
334
                if (instruction.Op1Kind == OpKind.Memory)
×
335
                    goto default;
336

337
                var imm = instruction.Immediate8;
×
338
                var src1 = X86Utils.GetRegisterName(instruction.Op0Register);
×
339
                var src2 = X86Utils.GetRegisterName(instruction.Op1Register);
×
340
                var dest = "XMM_TEMP";
×
341
                //TEMP_DEST[31:0] := Select4(SRC1[127:0], imm8[1:0]);
NEW
342
                builder.Move(instruction.IP, ConvertVector(dest, 0), ConvertVector(src1, imm & 0b11));
×
343
                //TEMP_DEST[63:32] := Select4(SRC1[127:0], imm8[3:2]);
NEW
344
                builder.Move(instruction.IP, ConvertVector(dest, 1), ConvertVector(src1, (imm >> 2) & 0b11));
×
345
                //TEMP_DEST[95:64] := Select4(SRC2[127:0], imm8[5:4]);
346
                builder.Move(instruction.IP, ConvertVector(dest, 2), ConvertVector(src2, (imm >> 4) & 0b11));
×
347
                //TEMP_DEST[127:96] := Select4(SRC2[127:0], imm8[7:6]);
348
                builder.Move(instruction.IP, ConvertVector(dest, 3), ConvertVector(src2, (imm >> 6) & 0b11));
×
349
                // where Select4(regSlice, imm) => regSlice.[imm switch => { 0 => 0..31, 1 => 32..63, 2 => 64..95, 3 => 96...127 }];
350
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeRegister(dest)); // DEST = TEMP_DEST
×
351
                break;
×
352

353
                static InstructionSetIndependentOperand ConvertVector(string reg, int imm) =>
354
                    InstructionSetIndependentOperand.MakeVectorElement(reg, IsilVectorRegisterElementOperand.VectorElementWidth.S, imm);
×
355
            }
356

357
            case Mnemonic.Unpcklps: // Unpack and Interleave Low Packed Single Precision Floating-Point Values
358
            {
359
                if (instruction.Op1Kind == OpKind.Memory)
×
360
                    goto default;
361

362
                var src1 = X86Utils.GetRegisterName(instruction.Op0Register);
×
363
                var src2 = X86Utils.GetRegisterName(instruction.Op1Register);
×
364
                var dest = "XMM_TEMP";
×
365
                builder.Move(instruction.IP, ConvertVector(dest, 0), ConvertVector(src1, 0)); //TMP_DEST[31:0] := SRC1[31:0]
×
366
                builder.Move(instruction.IP, ConvertVector(dest, 1), ConvertVector(src2, 0)); //TMP_DEST[63:32] := SRC2[31:0]
×
367
                builder.Move(instruction.IP, ConvertVector(dest, 2), ConvertVector(src1, 1)); //TMP_DEST[95:64] := SRC1[63:32]
×
368
                builder.Move(instruction.IP, ConvertVector(dest, 3), ConvertVector(src2, 1)); //TMP_DEST[127:96] := SRC2[63:32]
×
369
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeRegister(dest)); // DEST = TEMP_DEST
×
370
                break;
×
371

372
                static InstructionSetIndependentOperand ConvertVector(string reg, int imm) =>
373
                    InstructionSetIndependentOperand.MakeVectorElement(reg, IsilVectorRegisterElementOperand.VectorElementWidth.S, imm);
×
374
            }
375

376
            case Mnemonic.Call:
377
                // We don't try and resolve which method is being called, but we do need to know how many parameters it has
378
                // I would hope that all of these methods have the same number of arguments, else how can they be inlined?
379

380
                var target = instruction.NearBranchTarget;
×
381

382
                if (instruction.Op0Kind == OpKind.Register)
×
383
                {
384
                    builder.CallRegister(instruction.IP, ConvertOperand(instruction, 0));
×
385
                }
386
                else if (context.AppContext.MethodsByAddress.TryGetValue(target, out var possibleMethods))
×
387
                {
388
                    if (possibleMethods.Count == 1)
×
389
                    {
390
                        builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForManaged(possibleMethods[0]));
×
391
                    }
392
                    else
393
                    {
394
                        MethodAnalysisContext ctx = null!;
×
395
                        var lpars = -1;
×
396

397
                        // Very naive approach, folds with structs in parameters if GCC is used:
398
                        foreach (var method in possibleMethods)
×
399
                        {
400
                            var pars = method.ParameterCount;
×
401
                            if (method.IsStatic) pars++;
×
402
                            if (pars > lpars)
×
403
                            {
404
                                lpars = pars;
×
405
                                ctx = method;
×
406
                            }
407
                        }
408

409
                        // On post-analysis, you can discard methods according to the registers used, see X64CallingConventionResolver.
410
                        // This is less effective on GCC because MSVC doesn't overlap registers.
411

412
                        builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForManaged(ctx));
×
413
                    }
414
                }
415
                else
416
                {
417
                    // This isn't a managed method, so for now we don't know its parameter count.
418
                    // This will need to be rewritten if we ever stumble upon an unmanaged method that accepts more than 4 parameters.
419
                    // These can be converted to dedicated ISIL instructions for specific API functions at a later stage. (by a post-processing step)
420

421
                    builder.Call(instruction.IP, target, X64CallingConventionResolver.ResolveForUnmanaged(context.AppContext, target));
×
422
                }
423

424
                if (callNoReturn)
×
425
                {
426
                    // Our function decided to jump into a thunk or do a funny return.
427
                    // We will insert a return after the call.
428
                    // According to common sense, such callee must have the same return value as the caller, unless it's __noreturn.
429
                    // I hope someone else will catch up on this and figure out non-returning functions.
430

431
                    // TODO: Determine whether a function is an actual thunk and it's *technically better* to duplicate code for it, or if it's a regular retcall.
432
                    // Basic implementation may use context.AppContext.MethodsByAddress, but this doesn't catch thunks only.
433
                    // For example, SWDT often calls gc::GarbageCollector::SetWriteBarrier through a long jmp chain. That's a whole function, not just a thunk.
434

435
                    goto case Mnemonic.Ret;
×
436
                }
437

438
                break;
439
            case Mnemonic.Test:
440
                if (instruction.Op0Kind == OpKind.Register && instruction.Op1Kind == OpKind.Register && instruction.Op0Register == instruction.Op1Register)
×
441
                {
442
                    builder.Compare(instruction.IP, ConvertOperand(instruction, 0), InstructionSetIndependentOperand.MakeImmediate(0));
×
443
                    break;
×
444
                }
445

446
                //Fall through to cmp, as test is just a cmp that doesn't set flags
447
                goto case Mnemonic.Cmp;
448
            case Mnemonic.Cmp:
449
            case Mnemonic.Comiss: //comiss is just a floating point compare dest[31:0] == src[31:0]
450
            case Mnemonic.Ucomiss: // same, but unsigned
451
                builder.Compare(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
452
                break;
×
453

454
            case Mnemonic.Cmove: // move if condition
455
            case Mnemonic.Cmovne:
456
            case Mnemonic.Cmova:
457
            case Mnemonic.Cmovg:
458
            case Mnemonic.Cmovae:
459
            case Mnemonic.Cmovge:
460
            case Mnemonic.Cmovb:
461
            case Mnemonic.Cmovl:
462
            case Mnemonic.Cmovbe:
463
            case Mnemonic.Cmovle:
464
            case Mnemonic.Cmovs:
465
            case Mnemonic.Cmovns:
UNCOV
466
                switch (instruction.Mnemonic)
×
467
                {
468
                    case Mnemonic.Cmove: // equals
469
                        builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1); // skip if not eq
×
470
                        break;
×
471
                    case Mnemonic.Cmovne: // not equals
472
                        builder.JumpIfEqual(instruction.IP, instruction.IP + 1); // skip if eq
×
473
                        break;
×
474
                    case Mnemonic.Cmovs: // sign
475
                        builder.JumpIfNotSign(instruction.IP, instruction.IP + 1); // skip if not sign
×
476
                        break;
×
477
                    case Mnemonic.Cmovns: // not sign
478
                        builder.JumpIfSign(instruction.IP, instruction.IP + 1); // skip if sign
×
479
                        break;
×
480
                    case Mnemonic.Cmova:
481
                    case Mnemonic.Cmovg: // greater
482
                        builder.JumpIfLessOrEqual(instruction.IP, instruction.IP + 1); // skip if not gt
×
483
                        break;
×
484
                    case Mnemonic.Cmovae:
485
                    case Mnemonic.Cmovge: // greater or eq
486
                        builder.JumpIfLess(instruction.IP, instruction.IP + 1); // skip if not gt or eq
×
487
                        break;
×
488
                    case Mnemonic.Cmovb:
489
                    case Mnemonic.Cmovl: // less
490
                        builder.JumpIfGreaterOrEqual(instruction.IP, instruction.IP + 1); // skip if not lt
×
491
                        break;
×
492
                    case Mnemonic.Cmovbe:
493
                    case Mnemonic.Cmovle: // less or eq
494
                        builder.JumpIfGreater(instruction.IP, instruction.IP + 1); // skip if not lt or eq
×
495
                        break;
496
                }
497

498
                builder.Move(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1)); // set if cond
×
499
                builder.Nop(instruction.IP + 1);
×
500
                break;
×
501

502
            case Mnemonic.Maxss: // dest < src ? src : dest
503
            case Mnemonic.Minss: // dest > src ? src : dest
504
            {
505
                var dest = ConvertOperand(instruction, 0);
×
506
                var src = ConvertOperand(instruction, 1);
×
507
                builder.Compare(instruction.IP, dest, src); // compare dest & src
×
508
                if (instruction.Mnemonic == Mnemonic.Maxss)
×
509
                    builder.JumpIfGreaterOrEqual(instruction.IP, instruction.IP + 1); // enter if dest < src
×
510
                else
511
                    builder.JumpIfLessOrEqual(instruction.IP, instruction.IP + 1); // enter if dest > src
×
512
                builder.Move(instruction.IP, dest, src); // dest = src
×
513
                builder.Nop(instruction.IP + 1); // exit for IF
×
514
                break;
×
515
            }
516

517
            case Mnemonic.Cmpxchg: // compare and exchange
518
            {
519
                var accumulator = InstructionSetIndependentOperand.MakeRegister(instruction.Op1Register.GetSize() switch
×
520
                {
×
521
                    8 => X86Utils.GetRegisterName(Register.RAX),
×
522
                    4 => X86Utils.GetRegisterName(Register.EAX),
×
523
                    2 => X86Utils.GetRegisterName(Register.AX),
×
524
                    1 => X86Utils.GetRegisterName(Register.AL),
×
525
                    _ => throw new NotSupportedException("unexpected behavior")
×
526
                });
×
527
                var dest = ConvertOperand(instruction, 0);
×
528
                var src = ConvertOperand(instruction, 1);
×
529
                builder.Compare(instruction.IP, accumulator, dest);
×
530
                builder.JumpIfNotEqual(instruction.IP, instruction.IP + 1); // if accumulator == dest
×
531
                // SET ZF = 1
532
                builder.Move(instruction.IP, dest, src); // DEST = SRC
×
533
                builder.Goto(instruction.IP, instruction.IP + 2); // END IF
×
534
                // ELSE
535
                // SET ZF = 0
536
                builder.Move(instruction.IP + 1, accumulator, dest); // accumulator = dest
×
537

538
                builder.Nop(instruction.IP + 2); // exit for IF
×
539
                break;
×
540
            }
541

542
            case Mnemonic.Jmp:
543
                if (instruction.Op0Kind != OpKind.Register)
×
544
                {
545
                    var jumpTarget = instruction.NearBranchTarget;
×
546

547
                    var methodEnd = instruction.IP + (ulong)context.RawBytes.Length;
×
548
                    var methodStart = context.UnderlyingPointer;
×
549

550
                    if (jumpTarget < methodStart || jumpTarget > methodEnd)
×
551
                    {
552
                        callNoReturn = true;
×
553
                        goto case Mnemonic.Call;
×
554
                    }
555
                    else
556
                    {
557
                        builder.Goto(instruction.IP, jumpTarget);
×
558
                        break;
×
559
                    }
560
                }
561

UNCOV
562
                if (instruction.Op0Kind == OpKind.Register) // ex: jmp rax
×
563
                {
564
                    builder.CallRegister(instruction.IP, ConvertOperand(instruction, 0), noReturn: true);
×
565
                    break;
×
566
                }
567

568
                goto default;
569
            case Mnemonic.Je:
570
                if (instruction.Op0Kind != OpKind.Register)
×
571
                {
572
                    var jumpTarget = instruction.NearBranchTarget;
×
573

574
                    builder.JumpIfEqual(instruction.IP, jumpTarget);
×
575
                    break;
×
576
                }
577

578
                goto default;
579
            case Mnemonic.Jne:
580
                if (instruction.Op0Kind != OpKind.Register)
×
581
                {
582
                    var jumpTarget = instruction.NearBranchTarget;
×
583

584
                    builder.JumpIfNotEqual(instruction.IP, jumpTarget);
×
585
                    break;
×
586
                }
587

588
                goto default;
589
            case Mnemonic.Js:
590
                if (instruction.Op0Kind != OpKind.Register)
×
591
                {
592
                    var jumpTarget = instruction.NearBranchTarget;
×
593

594
                    builder.JumpIfSign(instruction.IP, jumpTarget);
×
595
                    break;
×
596
                }
597

598
                goto default;
599
            case Mnemonic.Jns:
600
                if (instruction.Op0Kind != OpKind.Register)
×
601
                {
602
                    var jumpTarget = instruction.NearBranchTarget;
×
603

604
                    builder.JumpIfNotSign(instruction.IP, jumpTarget);
×
605
                    break;
×
606
                }
607

608
                goto default;
609
            case Mnemonic.Jg:
610
            case Mnemonic.Ja:
611
                if (instruction.Op0Kind != OpKind.Register)
×
612
                {
613
                    var jumpTarget = instruction.NearBranchTarget;
×
614

615
                    builder.JumpIfGreater(instruction.IP, jumpTarget);
×
616
                    break;
×
617
                }
618

619
                goto default;
620
            case Mnemonic.Jl:
621
            case Mnemonic.Jb:
622
                if (instruction.Op0Kind != OpKind.Register)
×
623
                {
624
                    var jumpTarget = instruction.NearBranchTarget;
×
625

626
                    builder.JumpIfLess(instruction.IP, jumpTarget);
×
627
                    break;
×
628
                }
629

630
                goto default;
631
            case Mnemonic.Jge:
632
            case Mnemonic.Jae:
633
                if (instruction.Op0Kind != OpKind.Register)
×
634
                {
635
                    var jumpTarget = instruction.NearBranchTarget;
×
636

637
                    builder.JumpIfGreaterOrEqual(instruction.IP, jumpTarget);
×
638
                    break;
×
639
                }
640

641
                goto default;
642
            case Mnemonic.Jle:
643
            case Mnemonic.Jbe:
644
                if (instruction.Op0Kind != OpKind.Register)
×
645
                {
646
                    var jumpTarget = instruction.NearBranchTarget;
×
647

648
                    builder.JumpIfLessOrEqual(instruction.IP, jumpTarget);
×
649
                    break;
×
650
                }
651

652
                goto default;
653
            case Mnemonic.Xchg:
654
                // There was supposed to be a push-mov-pop set but instructionAddress said no
655
                builder.Exchange(instruction.IP, ConvertOperand(instruction, 0), ConvertOperand(instruction, 1));
×
656
                break;
×
657
            case Mnemonic.Int:
658
            case Mnemonic.Int3:
659
                builder.Interrupt(instruction.IP); // We'll add it but eliminate later, can be used as a hint since compilers only emit it in normally unreachable code or in error handlers
×
660
                break;
×
661
            case Mnemonic.Prefetchw: // Fetches the cache line containing the specified byte from memory to the 1st or 2nd level cache, invalidating other cached copies.
662
            case Mnemonic.Nop:
663
                // While this is literally a nop and there's in theory no point emitting anything for it, it could be used as a jump target.
664
                // So we'll emit an ISIL nop for it.
665
                builder.Nop(instruction.IP);
×
666
                break;
×
667
            default:
668
                builder.NotImplemented(instruction.IP, FormatInstruction(instruction));
×
669
                break;
670
        }
671
    }
×
672

673

674
    private InstructionSetIndependentOperand ConvertOperand(Instruction instruction, int operand)
675
    {
676
        var kind = instruction.GetOpKind(operand);
×
677

678
        if (kind == OpKind.Register)
×
679
            return InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.GetOpRegister(operand)));
×
680
        if (kind.IsImmediate())
×
681
            return InstructionSetIndependentOperand.MakeImmediate(instruction.GetImmediate(operand));
×
682
        if (kind == OpKind.Memory && instruction.MemoryBase == Register.RSP)
×
683
            return InstructionSetIndependentOperand.MakeStack((int)instruction.MemoryDisplacement32);
×
684

685
        //Memory
686
        //Most complex to least complex
687

688
        if (instruction.IsIPRelativeMemoryOperand)
×
689
            return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.IPRelativeMemoryAddress));
×
690

691
        //All four components
692
        if (instruction.MemoryIndex != Register.None && instruction.MemoryBase != Register.None && instruction.MemoryDisplacement64 != 0)
×
693
        {
694
            var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase));
×
695
            var mIndex = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryIndex));
×
696
            return InstructionSetIndependentOperand.MakeMemory(new(mBase, mIndex, instruction.MemoryDisplacement32, instruction.MemoryIndexScale));
×
697
        }
698

699
        //No addend
700
        if (instruction.MemoryIndex != Register.None && instruction.MemoryBase != Register.None)
×
701
        {
702
            var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase));
×
703
            var mIndex = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryIndex));
×
704
            return InstructionSetIndependentOperand.MakeMemory(new(mBase, mIndex, instruction.MemoryIndexScale));
×
705
        }
706

707
        //No index (and so no scale)
708
        if (instruction.MemoryBase != Register.None && instruction.MemoryDisplacement64 > 0)
×
709
        {
710
            var mBase = InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase));
×
711
            return InstructionSetIndependentOperand.MakeMemory(new(mBase, (long)instruction.MemoryDisplacement64));
×
712
        }
713

714
        //Only base
715
        if (instruction.MemoryBase != Register.None)
×
716
        {
717
            return InstructionSetIndependentOperand.MakeMemory(new(InstructionSetIndependentOperand.MakeRegister(X86Utils.GetRegisterName(instruction.MemoryBase))));
×
718
        }
719

720
        //Only addend
721
        return InstructionSetIndependentOperand.MakeMemory(new((long)instruction.MemoryDisplacement64));
×
722
    }
723

724
    public override List<Decompiler.IL.Instruction> GetDecompilerIlFromMethod(MethodAnalysisContext context, out List<object> ilParams)
725
    {
NEW
726
        var isilParams = X64CallingConventionResolver.ResolveForManaged(context);
×
NEW
727
        ilParams = isilParams.Select(ConvertOperand).ToList();
×
728

NEW
729
        var addressMap = new List<(ulong, Decompiler.IL.Instruction)>();
×
NEW
730
        var instructions = new List<Decompiler.IL.Instruction>();
×
NEW
731
        var appContext = context.AppContext;
×
NEW
732
        var keyFunctionAddresses = appContext.GetOrCreateKeyFunctionAddresses();
×
NEW
733
        var isil = GetIsilFromMethod(context);
×
734

NEW
735
        for (var i = 0; i < isil.Count; i++)
×
736
        {
NEW
737
            var instruction = isil[i];
×
738

739
            OpCode opCode;
NEW
740
            var operands = instruction.Operands.Select(ConvertOperand).ToList();
×
741

742
            // Using -1 for index here should actually be fine
NEW
743
            switch (instruction.OpCode.Mnemonic)
×
744
            {
745
                case IsilMnemonic.Move:
746
                case IsilMnemonic.LoadAddress:
NEW
747
                    opCode = instruction.OpCode.Mnemonic switch
×
NEW
748
                    {
×
NEW
749
                        IsilMnemonic.Move => OpCode.Move,
×
NEW
750
                        IsilMnemonic.LoadAddress => OpCode.LoadAddress
×
NEW
751
                    };
×
752

NEW
753
                    Add(new Decompiler.IL.Instruction(-1, opCode, operands[0], operands[1]), instruction);
×
NEW
754
                    break;
×
755
                case IsilMnemonic.ShiftLeft:
756
                case IsilMnemonic.ShiftRight:
NEW
757
                    opCode = instruction.OpCode.Mnemonic switch
×
NEW
758
                    {
×
NEW
759
                        IsilMnemonic.ShiftRight => OpCode.ShiftRight,
×
NEW
760
                        IsilMnemonic.ShiftLeft => OpCode.ShiftLeft
×
NEW
761
                    };
×
762

NEW
763
                    Add(new Decompiler.IL.Instruction(-1, opCode, operands[0], operands[0], operands[1]), instruction);
×
NEW
764
                    break;
×
765

766
                case IsilMnemonic.Call:
767
                case IsilMnemonic.CallNoReturn:
NEW
768
                    if (instruction.Operands[0].Data is IsilRegisterOperand)
×
769
                    {
NEW
770
                        Add(new Decompiler.IL.Instruction(-1, OpCode.Unknown, $"Indirect call: {instruction}"), instruction);
×
NEW
771
                        break;
×
772
                    }
773

NEW
774
                    var address = ((ulong)((IsilImmediateOperand)instruction.Operands[0].Data).Value);
×
NEW
775
                    MethodAnalysisContext? calledMethod = null;
×
776

NEW
777
                    if (appContext.MethodsByAddress.TryGetValue(address, out var possibleMethods))
×
778
                    {
NEW
779
                        if (possibleMethods.Count == 1)
×
780
                        {
NEW
781
                            calledMethod = possibleMethods[0];
×
782
                        }
783
                        else
784
                        {
NEW
785
                            var lpars = -1;
×
786

NEW
787
                            foreach (var possible in possibleMethods)
×
788
                            {
NEW
789
                                var pars = possible.ParameterCount;
×
NEW
790
                                if (possible.IsStatic) pars++;
×
NEW
791
                                if (pars > lpars)
×
792
                                {
NEW
793
                                    lpars = pars;
×
NEW
794
                                    calledMethod = possible;
×
795
                                }
796
                            }
797
                        }
798
                    }
799

NEW
800
                    if (calledMethod == null)
×
801
                    {
NEW
802
                        var target = (ulong)((IsilImmediateOperand)(instruction.Operands[0].Data)).Value;
×
803

NEW
804
                        if (keyFunctionAddresses.IsKeyFunctionAddress(target))
×
NEW
805
                            Add(new Decompiler.IL.Instruction(-1, OpCode.Unknown, GetKeyFunctionName(appContext, target, keyFunctionAddresses)), instruction);
×
806
                        else
NEW
807
                            Add(new Decompiler.IL.Instruction(-1, OpCode.Unknown, $"Method not found: {address:X}"), instruction);
×
808

NEW
809
                        break;
×
810
                    }
811

NEW
812
                    object? returnValue = null;
×
813

NEW
814
                    if (calledMethod.Definition?.RawReturnType?.Type is Il2CppTypeEnum.IL2CPP_TYPE_R4 or Il2CppTypeEnum.IL2CPP_TYPE_R8)
×
NEW
815
                        returnValue = _xmm0;
×
NEW
816
                    else if (!calledMethod.IsVoid)
×
NEW
817
                        returnValue = _rax;
×
818

NEW
819
                    opCode = returnValue == null ? OpCode.CallVoid : OpCode.Call;
×
820

821
                    // If it's last instruction then it's tail call
NEW
822
                    var isTailCall = i == isil.Count - 1;
×
823

824
                    // Call -> interrupt
NEW
825
                    if (!isTailCall)
×
NEW
826
                        isTailCall = isil[i + 1].OpCode.Mnemonic == IsilMnemonic.Interrupt;
×
827

NEW
828
                    if (isTailCall)
×
829
                    {
NEW
830
                        if (opCode == OpCode.Call)
×
NEW
831
                            opCode = OpCode.TailCall;
×
832

NEW
833
                        if (opCode == OpCode.CallVoid)
×
NEW
834
                            opCode = OpCode.TailCallVoid;
×
835
                    }
836

NEW
837
                    var call = new Decompiler.IL.Instruction(-1, opCode, calledMethod);
×
838

NEW
839
                    if (returnValue != null)
×
NEW
840
                        call.Operands.Insert(0, returnValue);
×
841

NEW
842
                    foreach (var arg in operands.Skip(1))
×
NEW
843
                        call.Operands.Add(arg);
×
844

NEW
845
                    Add(call, instruction);
×
NEW
846
                    break;
×
847

848
                case IsilMnemonic.Exchange:
849
                    // tmp = b, b = a, a = tmp
NEW
850
                    var exchangeTemp = CreateRegister("exchangeTemp");
×
851

NEW
852
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Move, exchangeTemp, operands[1]), instruction); // tmp = b
×
NEW
853
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Move, operands[1], operands[0]), instruction); // b = a
×
NEW
854
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Move, operands[0], exchangeTemp), instruction); // a = tmp
×
NEW
855
                    break;
×
856

857
                case IsilMnemonic.Add:
858
                case IsilMnemonic.Subtract:
859
                case IsilMnemonic.Multiply:
860
                case IsilMnemonic.Divide:
861
                case IsilMnemonic.And:
862
                case IsilMnemonic.Or:
863
                case IsilMnemonic.Xor:
NEW
864
                    opCode = instruction.OpCode.Mnemonic switch
×
NEW
865
                    {
×
NEW
866
                        IsilMnemonic.Add => OpCode.Add,
×
NEW
867
                        IsilMnemonic.Subtract => OpCode.Subtract,
×
NEW
868
                        IsilMnemonic.Multiply => OpCode.Multiply,
×
NEW
869
                        IsilMnemonic.Divide => OpCode.Divide,
×
NEW
870
                        IsilMnemonic.And => OpCode.And,
×
NEW
871
                        IsilMnemonic.Or => OpCode.Or,
×
NEW
872
                        IsilMnemonic.Xor => OpCode.Xor
×
NEW
873
                    };
×
874

NEW
875
                    Add(new Decompiler.IL.Instruction(-1, opCode, operands[0], operands[1], operands[2]), instruction);
×
NEW
876
                    break;
×
877

878
                case IsilMnemonic.Not:
879
                case IsilMnemonic.Neg:
NEW
880
                    opCode = instruction.OpCode.Mnemonic switch
×
NEW
881
                    {
×
NEW
882
                        IsilMnemonic.Not => OpCode.Not,
×
NEW
883
                        IsilMnemonic.Neg => OpCode.Negate
×
NEW
884
                    };
×
885

NEW
886
                    Add(new Decompiler.IL.Instruction(-1, opCode, operands[0], operands[0]), instruction);
×
NEW
887
                    break;
×
888

889
                case IsilMnemonic.ShiftStack:
890
                case IsilMnemonic.Goto:
891
                case IsilMnemonic.Invalid:
892
                case IsilMnemonic.NotImplemented:
NEW
893
                    opCode = instruction.OpCode.Mnemonic switch
×
NEW
894
                    {
×
NEW
895
                        IsilMnemonic.ShiftStack => OpCode.ShiftStack,
×
NEW
896
                        IsilMnemonic.Goto => OpCode.Jump,
×
NEW
897
                        IsilMnemonic.Invalid => OpCode.Unknown,
×
NEW
898
                        IsilMnemonic.NotImplemented => OpCode.Unknown
×
NEW
899
                    };
×
900

NEW
901
                    Add(new Decompiler.IL.Instruction(-1, opCode, operands[0]), instruction);
×
NEW
902
                    break;
×
903

904
                case IsilMnemonic.Compare: // Set flags
NEW
905
                    var cmpA = operands[0];
×
NEW
906
                    var cmpB = operands[1];
×
907

NEW
908
                    var cmpTemp = CreateRegister("cmpTemp");
×
909

910
                    // cmpTemp = a - b
NEW
911
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Subtract, cmpTemp, cmpA, cmpB), instruction);
×
912
                    // CF = a < b
NEW
913
                    Add(new Decompiler.IL.Instruction(-1, OpCode.CheckLess, _carryFlag, cmpA, cmpB), instruction);
×
914
                    // OF = cmpTemp > a
NEW
915
                    Add(new Decompiler.IL.Instruction(-1, OpCode.CheckGreater, _overflowFlag, cmpTemp, cmpA), instruction);
×
916
                    // SF = cmpTemp < 0
NEW
917
                    Add(new Decompiler.IL.Instruction(-1, OpCode.CheckLess, _signFlag, cmpTemp, 0), instruction);
×
918
                    // ZF = cmpTemp == 0
NEW
919
                    Add(new Decompiler.IL.Instruction(-1, OpCode.CheckEqual, _zeroFlag, cmpTemp, 0), instruction);
×
920
                    // PF = cmpTemp & 1
NEW
921
                    Add(new Decompiler.IL.Instruction(-1, OpCode.And, _parityFlag, cmpTemp, 1), instruction);
×
NEW
922
                    break;
×
923

924
                case IsilMnemonic.Push:
925
                case IsilMnemonic.Pop:
NEW
926
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Unknown, $"Somehow Cpp2IL didn't translate {instruction} to ISIL!"), instruction);
×
NEW
927
                    break;
×
928

929
                case IsilMnemonic.Return:
NEW
930
                    Add(
×
NEW
931
                        instruction.Operands.Length == 0
×
NEW
932
                            ? new Decompiler.IL.Instruction(-1, OpCode.ReturnVoid)
×
NEW
933
                            : new Decompiler.IL.Instruction(-1, OpCode.Return, operands[0]),
×
NEW
934
                        instruction);
×
NEW
935
                    break;
×
936

937
                case IsilMnemonic.JumpIfEqual:
938
                    // ZF = 1
NEW
939
                    Add(new Decompiler.IL.Instruction(-1, OpCode.ConditionalJump, operands[0], _zeroFlag), instruction);
×
NEW
940
                    break;
×
941

942
                case IsilMnemonic.JumpIfNotEqual:
943
                    // ZF = 0
NEW
944
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Not, CreateRegister("notZf"), _zeroFlag), instruction); // notZf = !zf
×
NEW
945
                    Add(new Decompiler.IL.Instruction(-1, OpCode.ConditionalJump, operands[0], CreateRegister("notZf")), instruction);
×
NEW
946
                    break;
×
947

948
                case IsilMnemonic.JumpIfGreater:
949
                    // ZF = 0 & SF = OF
NEW
950
                    var zfIsZero = CreateRegister("zfIsZero");
×
NEW
951
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Not, zfIsZero, _zeroFlag), instruction); // zfIsZero = !zf
×
NEW
952
                    Add(new Decompiler.IL.Instruction(-1, OpCode.CheckEqual, CreateRegister("sfIsOf"), _signFlag, _overflowFlag), instruction); // sfIsOf = sf == of
×
NEW
953
                    Add(new Decompiler.IL.Instruction(-1, OpCode.And, zfIsZero, CreateRegister("sfIsOf"), zfIsZero), instruction); // zfIsZero &= sfIsOf
×
NEW
954
                    Add(new Decompiler.IL.Instruction(-1, OpCode.ConditionalJump, operands[0], zfIsZero), instruction);
×
NEW
955
                    break;
×
956

957
                case IsilMnemonic.JumpIfGreaterOrEqual:
958
                    // SF = OF
NEW
959
                    Add(new Decompiler.IL.Instruction(-1, OpCode.CheckEqual, CreateRegister("sfIsOf"), _signFlag, _overflowFlag), instruction); // sfIsOf = sf == of
×
NEW
960
                    Add(new Decompiler.IL.Instruction(-1, OpCode.ConditionalJump, operands[0], CreateRegister("sfIsOf")), instruction);
×
NEW
961
                    break;
×
962

963
                case IsilMnemonic.JumpIfLess:
964
                    // SF != OF
NEW
965
                    Add(new Decompiler.IL.Instruction(-1, OpCode.CheckEqual, CreateRegister("sfIsNotOf"), _signFlag, _overflowFlag), instruction); // sfIsNotOf = sf == of
×
NEW
966
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Not, CreateRegister("sfIsNotOf"), CreateRegister("sfIsNotOf")), instruction); // sfIsNotOf = !sfIsNotOf
×
NEW
967
                    Add(new Decompiler.IL.Instruction(-1, OpCode.ConditionalJump, operands[0], CreateRegister("sfIsNotOf")), instruction);
×
NEW
968
                    break;
×
969

970
                case IsilMnemonic.JumpIfLessOrEqual:
971
                    // ZF = 1 | SF != OF
NEW
972
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Move, CreateRegister("zfCopy"), _zeroFlag), instruction); // zfCopy = zf
×
NEW
973
                    Add(new Decompiler.IL.Instruction(-1, OpCode.CheckEqual, CreateRegister("sfIsNotOf"), _signFlag, _overflowFlag), instruction); // sfIsNotOf = sf == of
×
NEW
974
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Not, CreateRegister("sfIsNotOf"), CreateRegister("sfIsNotOf")), instruction); // sfIsNotOf = !sfIsNotOf
×
NEW
975
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Or, CreateRegister("zfCopy"), CreateRegister("sfIsNotOf"), CreateRegister("zfCopy")), instruction); // zfCopy |= sfIsNotOf
×
NEW
976
                    Add(new Decompiler.IL.Instruction(-1, OpCode.ConditionalJump, operands[0], CreateRegister("zfCopy")), instruction);
×
NEW
977
                    break;
×
978

979
                case IsilMnemonic.JumpIfSign:
980
                    // SF = 1
NEW
981
                    Add(new Decompiler.IL.Instruction(-1, OpCode.ConditionalJump, operands[0], _signFlag), instruction);
×
NEW
982
                    break;
×
983

984
                case IsilMnemonic.JumpIfNotSign:
985
                    // SF = 0
NEW
986
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Not, CreateRegister("notSf"), _signFlag), instruction); // notSf = !sf
×
NEW
987
                    Add(new Decompiler.IL.Instruction(-1, OpCode.ConditionalJump, operands[0], CreateRegister("notSf")), instruction);
×
NEW
988
                    break;
×
989

990
                case IsilMnemonic.SignExtend:
991
                    // IsilMnemonic.SignExtend is not used anywhere
NEW
992
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Unknown, $"SignExtend is not implemented ({instruction})"), instruction);
×
NEW
993
                    break;
×
994

995
                case IsilMnemonic.Interrupt:
NEW
996
                    if (context.IsVoid)
×
NEW
997
                        Add(new Decompiler.IL.Instruction(-1, OpCode.ReturnVoid), instruction);
×
NEW
998
                    else if (context.Definition?.RawReturnType?.Type is Il2CppTypeEnum.IL2CPP_TYPE_R4 or Il2CppTypeEnum.IL2CPP_TYPE_R8)
×
NEW
999
                        Add(new Decompiler.IL.Instruction(-1, OpCode.Return, _xmm0), instruction);
×
1000
                    else
NEW
1001
                        Add(new Decompiler.IL.Instruction(-1, OpCode.Return, _rax), instruction);
×
NEW
1002
                    break;
×
1003

1004
                case IsilMnemonic.Nop:
1005
                    // These could be branch targets so these need to be added
NEW
1006
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Nop), instruction);
×
NEW
1007
                    break;
×
1008

1009
                default:
NEW
1010
                    Add(new Decompiler.IL.Instruction(-1, OpCode.Unknown, $"Unknown instruction: {instruction}"),
×
NEW
1011
                        instruction);
×
1012
                    break;
1013
            }
1014
        }
1015

1016
        // Fix ranches
NEW
1017
        foreach (var instruction in instructions)
×
1018
        {
NEW
1019
            if (instruction.Operands.Count == 0) continue;
×
1020

NEW
1021
            if (instruction.Operands[0] is InstructionAddress target)
×
1022
            {
1023
                try
1024
                {
NEW
1025
                    instruction.Operands[0] = addressMap.First(i => i.Item1 == target.Address).Item2;
×
NEW
1026
                }
×
NEW
1027
                catch (Exception e)
×
1028
                {
NEW
1029
                    instruction.OpCode = OpCode.Unknown;
×
NEW
1030
                    instruction.Operands = [$"Branch target not found: @{target.Address:X}"];
×
NEW
1031
                }
×
1032
            }
1033
        }
1034

1035
        // Add return if it's not already there
NEW
1036
        if (instructions.Count > 0)
×
1037
        {
NEW
1038
            var lastInstruction = instructions.LastOrDefault();
×
1039

NEW
1040
            if (lastInstruction != null && lastInstruction.OpCode != OpCode.Return)
×
1041
            {
NEW
1042
                if (context.IsVoid)
×
NEW
1043
                    instructions.Add(new Decompiler.IL.Instruction(-1, OpCode.ReturnVoid));
×
NEW
1044
                else if (context.Definition?.RawReturnType?.Type is Il2CppTypeEnum.IL2CPP_TYPE_R4 or Il2CppTypeEnum.IL2CPP_TYPE_R8)
×
NEW
1045
                    instructions.Add(new Decompiler.IL.Instruction(-1, OpCode.Return, _xmm0));
×
1046
                else
NEW
1047
                    instructions.Add(new Decompiler.IL.Instruction(-1, OpCode.Return, _rax));
×
1048
            }
1049
        }
1050

1051
        // Fix indexes
NEW
1052
        for (var i = 0; i < instructions.Count; i++)
×
NEW
1053
            instructions[i].Index = i;
×
1054

NEW
1055
        return instructions;
×
1056

1057
        void Add(Decompiler.IL.Instruction newInstruction, InstructionSetIndependentInstruction instruction)
1058
        {
NEW
1059
            addressMap.Add((instruction.ActualAddress, newInstruction));
×
NEW
1060
            instructions.Add(newInstruction);
×
NEW
1061
        }
×
1062
    }
1063

NEW
1064
    private static object CreateRegister(string name) => ConvertOperand(InstructionSetIndependentOperand.MakeRegister(name));
×
1065

1066
    private static object ConvertOperand(InstructionSetIndependentOperand operand)
1067
    {
NEW
1068
        switch (operand.Data)
×
1069
        {
1070
            case IsilImmediateOperand immediate:
NEW
1071
                return immediate.Value switch
×
NEW
1072
                {
×
NEW
1073
                    int num => num,
×
NEW
1074
                    ushort.MaxValue => int.MaxValue,
×
NEW
1075
                    uint.MaxValue or ulong.MaxValue => ulong.MaxValue,
×
NEW
1076
                    ulong num2 => num2,
×
NEW
1077
                    string text => text,
×
NEW
1078
                    _ => $"Unknown operand: {operand}"
×
NEW
1079
                };
×
1080

1081
            case IsilStackOperand stackOffset:
NEW
1082
                return new StackOffset(stackOffset.Offset);
×
1083

1084
            case IsilRegisterOperand register:
NEW
1085
                if (!_registerNumbers.ContainsKey(register.RegisterName))
×
NEW
1086
                    _registerNumbers[register.RegisterName] = _registerNumbers.Count;
×
1087

NEW
1088
                var number = _registerNumbers[register.RegisterName];
×
NEW
1089
                return new Decompiler.IL.Register(number, register.RegisterName);
×
1090

1091
            case IsilMemoryOperand memory:
NEW
1092
                Decompiler.IL.Register? baseRegister = null;
×
NEW
1093
                if (memory.Base != null)
×
NEW
1094
                    baseRegister = (Decompiler.IL.Register)ConvertOperand((InstructionSetIndependentOperand)memory.Base!);
×
1095

NEW
1096
                Decompiler.IL.Register? index = null;
×
NEW
1097
                if (memory.Index != null)
×
NEW
1098
                    index = (Decompiler.IL.Register)ConvertOperand((InstructionSetIndependentOperand)memory.Index!);
×
1099

NEW
1100
                return new MemoryAddress(baseRegister, index, memory.Addend, memory.Scale);
×
1101
            case IsilVectorRegisterElementOperand vectorRegisterElement:
NEW
1102
                return ConvertOperand(InstructionSetIndependentOperand.MakeRegister(vectorRegisterElement.RegisterName));
×
1103

1104
            case InstructionSetIndependentInstruction instruction:
NEW
1105
                return new InstructionAddress(instruction.ActualAddress);
×
1106
        }
1107

NEW
1108
        return $"Unknown operand: {operand}";
×
1109
    }
1110

1111
    private static string GetKeyFunctionName(ApplicationAnalysisContext appContext, ulong target, BaseKeyFunctionAddresses kFA)
1112
    {
NEW
1113
        var method = "";
×
NEW
1114
        if (target == kFA.il2cpp_codegen_initialize_method || target == kFA.il2cpp_codegen_initialize_runtime_metadata)
×
1115
        {
NEW
1116
            if (appContext.MetadataVersion < 27)
×
NEW
1117
                method = nameof(kFA.il2cpp_codegen_initialize_method);
×
1118
            else
NEW
1119
                method = nameof(kFA.il2cpp_codegen_initialize_runtime_metadata);
×
1120
        }
NEW
1121
        else if (target == kFA.il2cpp_vm_metadatacache_initializemethodmetadata)
×
NEW
1122
            method = nameof(kFA.il2cpp_vm_metadatacache_initializemethodmetadata);
×
NEW
1123
        else if (target == kFA.il2cpp_runtime_class_init_export)
×
NEW
1124
            method = nameof(kFA.il2cpp_runtime_class_init_export);
×
NEW
1125
        else if (target == kFA.il2cpp_runtime_class_init_actual)
×
NEW
1126
            method = nameof(kFA.il2cpp_runtime_class_init_actual);
×
NEW
1127
        else if (target == kFA.il2cpp_object_new)
×
NEW
1128
            method = nameof(kFA.il2cpp_vm_object_new);
×
NEW
1129
        else if (target == kFA.il2cpp_codegen_object_new)
×
NEW
1130
            method = nameof(kFA.il2cpp_codegen_object_new);
×
NEW
1131
        else if (target == kFA.il2cpp_array_new_specific)
×
NEW
1132
            method = nameof(kFA.il2cpp_array_new_specific);
×
NEW
1133
        else if (target == kFA.il2cpp_vm_array_new_specific)
×
NEW
1134
            method = nameof(kFA.il2cpp_vm_array_new_specific);
×
NEW
1135
        else if (target == kFA.SzArrayNew)
×
NEW
1136
            method = nameof(kFA.SzArrayNew);
×
NEW
1137
        else if (target == kFA.il2cpp_type_get_object)
×
NEW
1138
            method = nameof(kFA.il2cpp_type_get_object);
×
NEW
1139
        else if (target == kFA.il2cpp_vm_reflection_get_type_object)
×
NEW
1140
            method = nameof(kFA.il2cpp_vm_reflection_get_type_object);
×
NEW
1141
        else if (target == kFA.il2cpp_resolve_icall)
×
NEW
1142
            method = nameof(kFA.il2cpp_resolve_icall);
×
NEW
1143
        else if (target == kFA.InternalCalls_Resolve)
×
NEW
1144
            method = nameof(kFA.InternalCalls_Resolve);
×
NEW
1145
        else if (target == kFA.il2cpp_string_new)
×
NEW
1146
            method = nameof(kFA.il2cpp_string_new);
×
NEW
1147
        else if (target == kFA.il2cpp_vm_string_new)
×
NEW
1148
            method = nameof(kFA.il2cpp_vm_string_new);
×
NEW
1149
        else if (target == kFA.il2cpp_string_new_wrapper)
×
NEW
1150
            method = nameof(kFA.il2cpp_string_new_wrapper);
×
NEW
1151
        else if (target == kFA.il2cpp_vm_string_newWrapper)
×
NEW
1152
            method = nameof(kFA.il2cpp_vm_string_newWrapper);
×
NEW
1153
        else if (target == kFA.il2cpp_codegen_string_new_wrapper)
×
NEW
1154
            method = nameof(kFA.il2cpp_codegen_string_new_wrapper);
×
NEW
1155
        else if (target == kFA.il2cpp_value_box)
×
NEW
1156
            method = nameof(kFA.il2cpp_value_box);
×
NEW
1157
        else if (target == kFA.il2cpp_vm_object_box)
×
NEW
1158
            method = nameof(kFA.il2cpp_vm_object_box);
×
NEW
1159
        else if (target == kFA.il2cpp_object_unbox)
×
NEW
1160
            method = nameof(kFA.il2cpp_object_unbox);
×
NEW
1161
        else if (target == kFA.il2cpp_vm_object_unbox)
×
NEW
1162
            method = nameof(kFA.il2cpp_vm_object_unbox);
×
NEW
1163
        else if (target == kFA.il2cpp_raise_exception)
×
NEW
1164
            method = nameof(kFA.il2cpp_raise_exception);
×
NEW
1165
        else if (target == kFA.il2cpp_vm_exception_raise)
×
NEW
1166
            method = nameof(kFA.il2cpp_vm_exception_raise);
×
NEW
1167
        else if (target == kFA.il2cpp_codegen_raise_exception)
×
NEW
1168
            method = nameof(kFA.il2cpp_codegen_raise_exception);
×
NEW
1169
        else if (target == kFA.il2cpp_vm_object_is_inst)
×
NEW
1170
            method = nameof(kFA.il2cpp_vm_object_is_inst);
×
NEW
1171
        else if (target == kFA.AddrPInvokeLookup)
×
NEW
1172
            method = nameof(kFA.AddrPInvokeLookup);
×
1173

NEW
1174
        return method;
×
1175
    }
1176
}
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