• 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.41
/Cpp2IL.Core/OutputFormats/DiffableCsOutputFormat.cs
1
using System.Collections.Generic;
2
using System.IO;
3
using System.Linq;
4
using System.Reflection;
5
using System.Text;
6
using Cpp2IL.Core.Api;
7
using Cpp2IL.Core.Extensions;
8
using Cpp2IL.Core.Logging;
9
using Cpp2IL.Core.Model.Contexts;
10
using Cpp2IL.Core.Utils;
11
using LibCpp2IL;
12

13
namespace Cpp2IL.Core.OutputFormats;
14

15
public class DiffableCsOutputFormat : Cpp2IlOutputFormat
16
{
17
    public static bool IncludeMethodLength = false;
18

19
    public override string OutputFormatId => "diffable-cs";
1✔
20
    public override string OutputFormatName => "Diffable C#";
×
21

22
    public override void DoOutput(ApplicationAnalysisContext context, string outputRoot)
23
    {
24
        //General principle of diffable CS:
25
        //- Same-line method bodies ({ })
26
        //- Attributes in alphabetical order
27
        //- Members in alphabetical order and in nested type-field-event-prop-method member order
28
        //- No info on addresses or tokens as these change with every rebuild
29

30
        //The idea is to make it as easy as possible for software like WinMerge, github, etc, to diff the two versions of the code and show the user exactly what changed.
31

32
        outputRoot = Path.Combine(outputRoot, "DiffableCs");
×
33

34
        if (Directory.Exists(outputRoot))
×
35
        {
36
            Logger.InfoNewline("Removing old DiffableCs output directory...", "DiffableCsOutputFormat");
×
37
            Directory.Delete(outputRoot, true);
×
38
        }
39

40
        Logger.InfoNewline("Building C# files and directory structure...", "DiffableCsOutputFormat");
×
41
        var files = BuildOutput(context, outputRoot);
×
42

43
        Logger.InfoNewline("Writing C# files...", "DiffableCsOutputFormat");
×
44
        foreach (var (filePath, fileContent) in files)
×
45
        {
46
            File.WriteAllText(filePath, fileContent.ToString());
×
47
        }
48
    }
×
49

50
    private static Dictionary<string, StringBuilder> BuildOutput(ApplicationAnalysisContext context, string outputRoot)
51
    {
52
        var ret = new Dictionary<string, StringBuilder>();
×
53

54
        foreach (var assembly in context.Assemblies)
×
55
        {
56
            var asmPath = Path.Combine(outputRoot, assembly.CleanAssemblyName);
×
57
            Directory.CreateDirectory(asmPath);
×
58

59
            foreach (var type in assembly.TopLevelTypes)
×
60
            {
61
                if (type is InjectedTypeAnalysisContext)
×
62
                    continue;
63

64
                var path = Path.Combine(asmPath, type.NamespaceAsSubdirs, MiscUtils.CleanPathElement(type.Name + ".cs"));
×
65
                Directory.CreateDirectory(Path.GetDirectoryName(path)!);
×
66

67
                var sb = new StringBuilder();
×
68

69
                //Namespace at top of file
70
                if (!string.IsNullOrEmpty(type.Namespace))
×
71
                    sb.AppendLine($"namespace {type.Namespace};").AppendLine();
×
72
                else
73
                    sb.AppendLine("//Type is in global namespace").AppendLine();
×
74

75
                AppendType(sb, type);
×
76

77
                ret[path] = sb;
×
78
            }
79
        }
80

81
        return ret;
×
82
    }
83

84
    private static void AppendType(StringBuilder sb, TypeAnalysisContext type, int indent = 0)
85
    {
86
        // if (type.IsCompilerGeneratedBasedOnCustomAttributes)
87
        //Do not output compiler-generated types
88
        // return;
89

90
        //Custom attributes for type. Includes a trailing newline
91
        AppendCustomAttributes(sb, type, indent);
×
92

93
        //Type declaration line
94
        sb.Append('\t', indent);
×
95

96
        sb.Append(CsFileUtils.GetKeyWordsForType(type));
×
97
        sb.Append(' ');
×
98
        sb.Append(CsFileUtils.GetTypeName(type.Name));
×
99
        CsFileUtils.AppendInheritanceInfo(type, sb);
×
100
        sb.AppendLine();
×
101
        sb.Append('\t', indent);
×
102
        sb.Append('{');
×
103
        sb.AppendLine();
×
104

105
        //Type declaration done, increase indent
106
        indent++;
×
107

108
        if (type.IsEnumType)
×
109
        {
110
            var enumValues = type.Fields.Where(f => f.IsStatic).ToList();
×
111
            enumValues.SortByExtractedKey(e => e.Token); //Not as good as sorting by value but it'll do
×
112
            foreach (var enumValue in enumValues)
×
113
            {
114
                sb.Append('\t', indent);
×
115
                sb.Append(enumValue.Name);
×
116
                sb.Append(" = ");
×
117
                sb.Append(enumValue.BackingData!.DefaultValue);
×
118
                sb.Append(',');
×
119
                sb.AppendLine();
×
120
            }
121
        }
122
        else
123
        {
124
            //Nested classes, alphabetical order
125
            var nestedTypes = type.NestedTypes.Clone();
×
126
            nestedTypes.SortByExtractedKey(t => t.Name);
×
127
            foreach (var nested in nestedTypes)
×
128
                AppendType(sb, nested, indent);
×
129

130
            //Fields, offset order, static first
131
            var fields = type.Fields.Clone();
×
132
            fields.SortByExtractedKey(f => f.IsStatic ? f.Offset : f.Offset + 0x1000);
×
133
            foreach (var field in fields)
×
134
                AppendField(sb, field, indent);
×
135

136
            sb.AppendLine();
×
137

138
            //Events, alphabetical order
139
            var events = type.Events.Clone();
×
140
            events.SortByExtractedKey(e => e.Name);
×
141
            foreach (var evt in events)
×
142
                AppendEvent(sb, evt, indent);
×
143

144
            //Properties, alphabetical order
145
            var properties = type.Properties.Clone();
×
146
            properties.SortByExtractedKey(p => p.Name);
×
147
            foreach (var prop in properties)
×
148
                AppendProperty(sb, prop, indent);
×
149

150
            //Methods, alphabetical order
151
            var methods = type.Methods.Clone();
×
152
            methods.SortByExtractedKey(m => m.Name);
×
153
            foreach (var method in methods)
×
154
                AppendMethod(sb, method, indent);
×
155
        }
156

157
        //Decrease indent, close brace
158
        indent--;
×
159
        sb.Append('\t', indent);
×
160
        sb.Append('}');
×
161
        sb.AppendLine().AppendLine();
×
162
    }
×
163

164
    private static void AppendField(StringBuilder sb, FieldAnalysisContext field, int indent)
165
    {
166
        if (field is InjectedFieldAnalysisContext)
×
167
            return;
×
168

169
        //Custom attributes for field. Includes a trailing newline
170
        AppendCustomAttributes(sb, field, indent);
×
171

172
        //Field declaration line
173
        sb.Append('\t', indent);
×
174
        sb.Append(CsFileUtils.GetKeyWordsForField(field));
×
175
        sb.Append(' ');
×
NEW
176
        sb.Append(CsFileUtils.GetTypeName(field.FieldType.Name));
×
177
        sb.Append(' ');
×
178
        sb.Append(field.Name);
×
179

180
        if (field.BackingData?.DefaultValue is { } defaultValue)
×
181
        {
182
            sb.Append(" = ");
×
183

184
            if (defaultValue is string stringDefaultValue)
×
185
                sb.Append('"').Append(stringDefaultValue).Append('"');
×
186
            else if (defaultValue is char charDefaultValue)
×
187
                sb.Append("'\\u").Append(((int)charDefaultValue).ToString("X")).Append("'");
×
188
            else
189
                sb.Append(defaultValue);
×
190
        }
191

192
        sb.Append("; //Field offset: 0x");
×
193
        sb.Append(field.Offset.ToString("X"));
×
194

195
        if ((field.Attributes & FieldAttributes.HasFieldRVA) != 0 && field.BackingData != null)
×
196
        {
197
            sb.Append(" || Has Field RVA (address hidden for diffability)");
×
198
            // var (dataIndex, _) = LibCpp2IlMain.TheMetadata!.GetFieldDefaultValue(field.BackingData.Field.FieldIndex);
199
            // var pointer = LibCpp2IlMain.TheMetadata!.GetDefaultValueFromIndex(dataIndex);
200
            // sb.Append(pointer.ToString("X8"));
201

202
            var actualValue = field.BackingData.Field.StaticArrayInitialValue;
×
203
            if (actualValue is { Length: > 0 })
×
204
            {
205
                sb.Append(" || Field RVA Decoded (hex blob): [");
×
206
                sb.Append(actualValue[0].ToString("X2"));
×
207
                for (var i = 1; i < actualValue.Length; i++)
×
208
                {
209
                    var b = actualValue[i];
×
210
                    sb.Append(' ').Append(b.ToString("X2"));
×
211
                }
212

213
                sb.Append(']');
×
214
            }
215
        }
216

217
        sb.AppendLine();
×
218
    }
×
219

220
    private static void AppendEvent(StringBuilder sb, EventAnalysisContext evt, int indent)
221
    {
222
        //Custom attributes for event. Includes a trailing newline
223
        AppendCustomAttributes(sb, evt, indent);
×
224

225
        //Event declaration line
226
        sb.Append('\t', indent);
×
227
        sb.Append(CsFileUtils.GetKeyWordsForEvent(evt));
×
228
        sb.Append(' ');
×
NEW
229
        sb.Append(CsFileUtils.GetTypeName(evt.EventType.Name));
×
230
        sb.Append(' ');
×
231
        sb.Append(evt.Name).AppendLine();
×
232
        sb.Append('\t', indent);
×
233
        sb.Append('{');
×
234
        sb.AppendLine();
×
235

236
        //Add/Remove/Invoke
237
        indent++;
×
238
        if (evt.Adder != null)
×
239
            AppendAccessor(sb, evt.Adder, "add", indent);
×
240
        if (evt.Remover != null)
×
241
            AppendAccessor(sb, evt.Remover, "remove", indent);
×
242
        if (evt.Invoker != null)
×
243
            AppendAccessor(sb, evt.Invoker, "fire", indent);
×
244
        indent--;
×
245

246
        sb.Append('\t', indent);
×
247
        sb.Append('}');
×
248
        sb.AppendLine().AppendLine();
×
249
    }
×
250

251
    private static void AppendProperty(StringBuilder sb, PropertyAnalysisContext prop, int indent)
252
    {
253
        //Custom attributes for property. Includes a trailing newline
254
        AppendCustomAttributes(sb, prop, indent);
×
255

256
        //Property declaration line
257
        sb.Append('\t', indent);
×
258
        sb.Append(CsFileUtils.GetKeyWordsForProperty(prop));
×
259
        sb.Append(' ');
×
NEW
260
        sb.Append(CsFileUtils.GetTypeName(prop.PropertyType.Name));
×
261
        sb.Append(' ');
×
262
        sb.Append(prop.Name);
×
263
        sb.AppendLine();
×
264
        sb.Append('\t', indent);
×
265
        sb.Append('{');
×
266
        sb.AppendLine();
×
267

268
        //Get/Set
269
        indent++;
×
270
        if (prop.Getter != null)
×
271
            AppendAccessor(sb, prop.Getter, "get", indent);
×
272
        if (prop.Setter != null)
×
273
            AppendAccessor(sb, prop.Setter, "set", indent);
×
274
        indent--;
×
275

276
        sb.Append('\t', indent);
×
277
        sb.Append('}');
×
278
        sb.AppendLine().AppendLine();
×
279
    }
×
280

281
    private static void AppendMethod(StringBuilder sb, MethodAnalysisContext method, int indent)
282
    {
283
        if (method is InjectedMethodAnalysisContext)
×
284
            return;
×
285

286
        //Custom attributes for method. Includes a trailing newline
287
        AppendCustomAttributes(sb, method, indent);
×
288

289
        //Method declaration line
290
        sb.Append('\t', indent);
×
291
        sb.Append(CsFileUtils.GetKeyWordsForMethod(method));
×
292
        sb.Append(' ');
×
293
        if (method.Name is not ".ctor" and not ".cctor")
×
294
        {
NEW
295
            sb.Append(CsFileUtils.GetTypeName(method.ReturnType.Name));
×
296
            sb.Append(' ');
×
297
            sb.Append(method.Name);
×
298
        }
299
        else
300
        {
301
            //Constructor
302
            sb.Append(method.DeclaringType!.Name);
×
303
        }
304

305
        sb.Append('(');
×
306
        sb.Append(CsFileUtils.GetMethodParameterString(method));
×
307
        sb.Append(") { }");
×
308

309
        if (IncludeMethodLength)
×
310
        {
311
            sb.Append(" //Length: ");
×
312
            sb.Append(method.RawBytes.Length);
×
313
        }
314

315
        sb.AppendLine().AppendLine();
×
316
    }
×
317

318
    //get/set/add/remove/raise
319
    private static void AppendAccessor(StringBuilder sb, MethodAnalysisContext accessor, string accessorType, int indent)
320
    {
321
        //Custom attributes for accessor. Includes a trailing newline
322
        AppendCustomAttributes(sb, accessor, indent);
×
323

324
        sb.Append('\t', indent);
×
325
        sb.Append(CsFileUtils.GetKeyWordsForMethod(accessor, true, true));
×
326
        sb.Append(' ');
×
327
        sb.Append(accessorType);
×
328
        sb.Append(" { } //Length: ");
×
329
        sb.Append(accessor.RawBytes.Length);
×
330
        sb.AppendLine();
×
331
    }
×
332

333
    private static void AppendCustomAttributes(StringBuilder sb, HasCustomAttributes owner, int indent)
334
        => sb.Append(CsFileUtils.GetCustomAttributeStrings(owner, indent, true, true));
×
335
}
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