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

SamboyCoding / Cpp2IL / 15377256860

01 Jun 2025 04:44PM UTC coverage: 34.308% (+0.2%) from 34.079%
15377256860

Pull #462

github

web-flow
Merge a1c3f6168 into 9dff465a4
Pull Request #462: Support overriding anything

1779 of 6562 branches covered (27.11%)

Branch coverage included in aggregate %.

133 of 243 new or added lines in 35 files covered. (54.73%)

5 existing lines in 5 files now uncovered.

4183 of 10816 relevant lines covered (38.67%)

183729.6 hits per line

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

0.0
/Cpp2IL.Core/Utils/WasmUtils.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Reflection;
5
using System.Text.RegularExpressions;
6
using Cpp2IL.Core.Model.Contexts;
7
using LibCpp2IL;
8
using LibCpp2IL.BinaryStructures;
9
using LibCpp2IL.Metadata;
10
using LibCpp2IL.Reflection;
11
using LibCpp2IL.Wasm;
12

13
namespace Cpp2IL.Core.Utils;
14

15
public static class WasmUtils
16
{
17
    internal static readonly Dictionary<int, List<Il2CppMethodDefinition>> MethodDefinitionIndices = new();
×
18
    private static Regex DynCallRemappingRegex = new(@"Module\[\s*[""'](dynCall_[^""']+)[""']\s*\]\s*=\s*Module\[\s*[""']asm[""']\s*\]\[\s*[""']([^""']+)[""']\s*\]\s*\)\.apply", RegexOptions.Compiled);
×
19

20
    public static string BuildSignature(MethodAnalysisContext definition)
21
    {
22
        var instanceParam = definition.IsStatic ? "" : "i";
×
23

24
        //Something still off about p/invoke functions. They do have methodinfo args, but something is wrong somewhere.
25
        
26
        //Also, this is STILL wrong for a lot of methods in DateTimeFormat and TimeZoneInfo.
27
        //It feels like it's something to do with when DateTime is considered a struct and when it's considered a class.
28
        //But I can find no rhyme nor reason to it.
29

NEW
30
        var returnTypeSignature = definition.ReturnType.IsWasmPrimitive()
×
NEW
31
            ? GetSignatureLetter(definition.ReturnType)
×
NEW
32
            : definition.ReturnType switch
×
33
            {
×
34
                { Namespace: nameof(System), Name: "Void" } => "v",
×
35
                { IsValueType: true, Definition: null or { Size: < 0 or > 8 } } => "vi", //Large or Generic Struct returns have a void return type, but the actual return value is the first parameter.
×
36
                { IsValueType: true, Definition.Size: > 4 } => "j", //Medium structs are returned as longs
×
37
                { IsValueType: true, Definition.Size: <= 4 } => "i", //Small structs are returned as ints
×
NEW
38
                _ => GetSignatureLetter(definition.ReturnType!)
×
39
            };
×
40

NEW
41
        return $"{returnTypeSignature}{instanceParam}{string.Join("", definition.Parameters!.Select(p => GetSignatureLetter(p.ParameterType, p.IsRef)))}i"; //Add an extra i on the end for the method info param
×
42
    }
43

44
    public static bool IsWasmPrimitive(this TypeAnalysisContext type)
45
    {
46
        var typeEnum = type.Type;
×
47

48
        //TODO Validate this, it's only the remnant from some poorly written logic for checking if a TypeAnalysisContext IsPrimitive.
49
        return typeEnum is >= Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN and <= Il2CppTypeEnum.IL2CPP_TYPE_R8;
×
50
    }
51

52
    private static string GetSignatureLetter(TypeAnalysisContext type, bool isRefOrOut = false)
53
    {
54
        if (isRefOrOut)
×
55
            //ref/out params are passed as pointers 
56
            return "i";
×
57

58
        if (type is WrappedTypeAnalysisContext)
×
59
            //Pointers, arrays, etc are ints
60
            return "i";
×
61

62
        if (type.IsEnumType)
×
63
            type = type.EnumUnderlyingType ?? throw new($"Enum type {type} has no underlying type");
×
64

65
        // var typeDefinition = type.BaseType ?? type.AppContext.SystemTypes.SystemInt32Type;
66

67
        return type.Name switch
×
68
        {
×
69
            "Void" => "v",
×
70
            "Int64" => "j",
×
71
            "Single" => "f",
×
72
            "Double" => "d",
×
73
            "Int32" => "i",
×
74
            _ when !type.IsWasmPrimitive() && type is { IsValueType: true, IsEnumType: false, Definition.Size: <= 8 and > 0 } => "j", //TODO check - value types < 16 bytes (including base object header which is irrelevant here) are passed directly as long?
×
75
            _ => "i"
×
76
        };
×
77
    }
78

79
    public static string GetGhidraFunctionName(WasmFunctionDefinition functionDefinition)
80
    {
81
        var index = functionDefinition.IsImport
×
82
            ? ((WasmFile)LibCpp2IlMain.Binary!).FunctionTable.IndexOf(functionDefinition)
×
83
            : functionDefinition.FunctionTableIndex;
×
84

85
        return $"unnamed_function_{index}";
×
86
    }
87

88
    public static WasmFunctionDefinition? TryGetWasmDefinition(MethodAnalysisContext definition)
89
    {
90
        try
91
        {
92
            return GetWasmDefinition(definition);
×
93
        }
94
        catch
×
95
        {
96
            return null;
×
97
        }
98
    }
×
99

100
    public static WasmFunctionDefinition GetWasmDefinition(MethodAnalysisContext context)
101
    {
102
        if (context.Definition == null)
×
103
            throw new($"Attempted to get wasm definition for probably-injected method context: {context}");
×
104
        
105
        //First, we have to calculate the signature
106
        var signature = BuildSignature(context);
×
107
        try
108
        {
109
            return ((WasmFile)LibCpp2IlMain.Binary!).GetFunctionFromIndexAndSignature(context.Definition.MethodPointer, signature);
×
110
        }
111
        catch (Exception e)
×
112
        {
113
            throw new($"Failed to find wasm definition for {context}\nwhich has params {context.Parameters?.ToStringEnumerable()}", e);
×
114
        }
115
    }
×
116

117
    // private static void CalculateAllMethodDefinitionIndices()
118
    // {
119
    //     foreach (var il2CppMethodDefinition in LibCpp2IlMain.TheMetadata!.methodDefs)
120
    //     {
121
    //         var methodDefinition = il2CppMethodDefinition;
122
    //
123
    //         try
124
    //         {
125
    //             var wasmDef = GetWasmDefinition(methodDefinition);
126
    //             var index = ((WasmFile)LibCpp2IlMain.Binary!).FunctionTable.IndexOf(wasmDef);
127
    //
128
    //             if (!MethodDefinitionIndices.TryGetValue(index, out var mDefs))
129
    //                 MethodDefinitionIndices[index] = mDefs = [];
130
    //
131
    //             mDefs.Add(methodDefinition);
132
    //         }
133
    //         catch (Exception)
134
    //         {
135
    //             //Ignore
136
    //         }
137
    //     }
138
    // }
139
    //
140
    // public static List<Il2CppMethodDefinition>? GetMethodDefinitionsAtIndex(int index)
141
    // {
142
    //     if (MethodDefinitionIndices.Count == 0)
143
    //         CalculateAllMethodDefinitionIndices();
144
    //
145
    //     if (MethodDefinitionIndices.TryGetValue(index, out var methodDefinitions))
146
    //         return methodDefinitions;
147
    //
148
    //     return null;
149
    // }
150

151
    public static Dictionary<string, string> ExtractAndParseDynCallRemaps(string frameworkJsFile)
152
    {
153
        //At least one WASM binary found in the wild had the exported function names obfuscated.
154
        //However, the framework.js file has mappings to the correct names.
155
        /*e.g.
156
         var dynCall_viffiiii = Module["dynCall_viffiiii"] = function() {
157
            return (dynCall_viffiiii = Module["dynCall_viffiiii"] = Module["asm"]["Wo"]).apply(null, arguments)
158
         }
159
        */
160

161
        var ret = new Dictionary<string, string>();
×
162
        var matches = DynCallRemappingRegex.Matches(frameworkJsFile);
×
163
        foreach (Match match in matches)
×
164
        {
165
            //Group 1 is the original method name, e.g. dynCall_viffiiii
166
            //Group 2 is the remapped name, e.g Wo
167
            var origName = match.Groups[1];
×
168
            var remappedName = match.Groups[2];
×
169

170
            ret[remappedName.Value] = origName.Value;
×
171
        }
172

173
        return ret;
×
174
    }
175
}
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