-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathProgram.cs
More file actions
352 lines (312 loc) · 19.9 KB
/
Program.cs
File metadata and controls
352 lines (312 loc) · 19.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.CSharp;
using CppAst;
namespace CodeGenFix
{
public class Program
{
/// <summary>
/// Fixes all issues in an auto-generated memflowInterop.cs from ClangSharp v13.
/// </summary>
/// <param name="args"></param>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="NotSupportedException"></exception>
public static void Main(string[] args)
{
Console.Title = "CodeGenFix";
// get interop file
string filePath = Path.Combine(Path.Combine(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "..", "..", "..", "..", "..", ".."), "memflow.NET", "memflow.NET"), "memflowInterop.cs");
if (args.Length == 1)
filePath = args[0];
Console.WriteLine($"Trying to parse '{filePath}'");
if (!File.Exists(filePath))
throw new FileNotFoundException("Generated memflow interop file not found!");
// read all text
string srcText = File.ReadAllText(filePath);
List<string> newSrcTextLines = File.ReadAllLines(filePath).ToList();
// find source errors by compiling
using MemoryStream peStream = new MemoryStream();
while (true)
{
// compile file
peStream.SetLength(0);
EmitResult compResult = CompileFromText(srcText, out SyntaxTree parsedSource).Emit(peStream);
IEnumerable<SyntaxNode> parsedNodes = parsedSource.GetRoot().DescendantNodes();
// check compilation success
if (compResult.Success)
goto FixStructs;
else
{
// check if any errors
IEnumerable<Diagnostic> codeErrors = compResult.Diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error);
// if we fixed all compilation errors fix all explicit structs
if (!codeErrors.Any())
goto FixStructs;
List<int> fixedLines = new List<int>();
// check each error
foreach (Diagnostic codeIssue in codeErrors)
{
//Console.WriteLine($"L{codeIssue.Location.GetLineSpan().StartLinePosition.Line}: ({codeIssue.Id}) {codeIssue.GetMessage()}");
string errMsg = codeIssue.GetMessage();
int errLineNo = codeIssue.Location.GetLineSpan().StartLinePosition.Line;
string errLine = newSrcTextLines[errLineNo];
switch (codeIssue.Id)
{
// The type or namespace name 'type/namespace' could not be found (are you missing a using directive or an assembly reference?)
// NativeTypeName and NativeTypeNameAttribute get inserted by ClangSharp but have to be manually defined
case "CS0246":
// find all usings directives
var Usings = parsedNodes.Where(x => x.IsKind(SyntaxKind.UsingDirective));
// find namespace line
var NameSpace = parsedNodes.First(x => x.IsKind(SyntaxKind.NamespaceDeclaration));
int line = NameSpace.GetLocation().GetLineSpan().StartLinePosition.Line + 2;
if (errMsg.Contains("NativeTypeNameAttribute") || errMsg.Contains("NativeTypeName"))
{
// insert using directives needed for definition
if (!Usings.Any(x => x.ToString().StartsWith("using System;")))
newSrcTextLines.Insert(0, "using System;");
if (!Usings.Any(x => x.ToString().StartsWith("using System.Diagnostics;")))
newSrcTextLines.Insert(1, "using System.Diagnostics;");
// insert NativeInheritanceAttribute definition
List<string> def = File.ReadAllLines(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SourceDefinitions", "NativeInheritanceAttribute.cx")).ToList();
def.Add("");
newSrcTextLines.InsertRange(line + 2, def);
// insert NativeTypeNameAttribute definition
def = File.ReadAllLines(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SourceDefinitions", "NativeTypeNameAttribute.cx")).ToList();
def.Add("");
newSrcTextLines.InsertRange(line + 2, def);
goto Recompile;
}
break;
// Type byte, sbyte, short, ushort, int, uint, long, or ulong expected
// ClangSharp uses nuint instead of uint
case "CS1008":
if (errLine.Contains("nuint"))
newSrcTextLines[errLineNo] = errLine.Replace("nuint", "uint");
else
goto ThrowError;
break;
// Invalid expression term 'character'
// Clangsharp breaks some regular '=' operators and generates '.operator=' directives
// Clangsharp gets thrown off on some defined fields
case "CS1001":
case "CS1002":
case "CS1513":
if (codeErrors.Any(x => x.Id == "CS1525"))
break;
else
goto ThrowError;
case "CS1525":
if (errLine.Contains(".operator="))
newSrcTextLines[errLineNo] = errLine.Replace(".operator=", "=");
else if (errLine.Contains(";") && (errLine.Contains("PhysicalAddress_INVALID") || errLine.Contains("Page_INVALID")))
{
if (errLine.Contains("PhysicalAddress_INVALID"))
{
// insert PhysicalAddress_INVALID definition
List<string> def = File.ReadAllLines(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SourceDefinitions", "PhysicalAddress_INVALID.cx")).ToList();
newSrcTextLines[errLineNo] = def[0];
newSrcTextLines.InsertRange(errLineNo + 1, def[1..]);
goto Recompile;
}
else
{
// insert Page_INVALID definition
List<string> def = File.ReadAllLines(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SourceDefinitions", "Page_INVALID.cx")).ToList();
newSrcTextLines[errLineNo] = def[0];
newSrcTextLines.InsertRange(errLineNo + 1, def[1..]);
goto Recompile;
}
}
else
goto ThrowError;
break;
// The operation in question is undefined on void pointers
// ClangSharp mistakenly interprets some struct pointers as void*
case "CS0242":
// don't fix line again as there can be multiple void* issues on the same line
if (fixedLines.Contains(errLineNo))
break;
// fix casting issue
if (errLine.Contains("(self)->vtbl") && errLine.Contains("(&("))
{
int i1 = errLine.IndexOf(")", errLine.IndexOf("(self)->vtbl") + 8);
string newLine = errLine.Remove(i1, 1);
newLine = newLine.Replace("(self)->", "self)->");
newLine = newLine.Replace("(&(", "(&((");
newSrcTextLines[errLineNo] = newLine;
}
else
goto ThrowError;
fixedLines.Add(errLineNo);
break;
// Cannot implicitly convert type 'type' to 'type'
// ClangSharp converts some booleans to byte
case "CS0029":
if (errLine.Contains("bool __ret") && errLine.Contains(");"))
newSrcTextLines[errLineNo] = errLine.Replace(");", ") != 0;");
else
goto ThrowError;
break;
// Argument 'number' cannot convert from TypeA to TypeB
// From previous issue, some functions require booleans as byte again
// Additionally some ints require casting to uint
case "CS1503":
if (fixedLines.Contains(errLineNo))
break;
if (errMsg.Contains("uint") && errLine.Contains("sizeof("))
newSrcTextLines[errLineNo] = errLine.Replace("sizeof(", "(uint)sizeof(");
else if (errMsg.Contains("uint") && errLine.Contains("CopyBlock") && errLine.Contains("elem_size);"))
newSrcTextLines[errLineNo] = errLine.Replace("elem_size);", "(uint)elem_size);");
else if (errMsg.Contains("uint") && errLine.Contains("CopyBlock") && errLine.Contains("iter->sz_elem);"))
newSrcTextLines[errLineNo] = errLine.Replace("iter->sz_elem);", "(uint)iter->sz_elem);");
else if (errMsg.Contains("byte") && errLine.Contains(");"))
newSrcTextLines[errLineNo] = errLine.Replace(");", " ? (byte)0x1 : (byte)0x0);");
else
goto ThrowError;
fixedLines.Add(errLineNo);
break;
// The name 'identifier' does not exist in the current context
// realloc() needs to be replaced
case "CS0103":
if (errLine.Contains("realloc"))
newSrcTextLines[errLineNo] = errLine.Replace("realloc", "NativeMemory.Realloc");
else
goto ThrowError;
break;
// make sure we don't miss any errors
default:
goto ThrowError;
}
continue;
ThrowError:
throw new NotSupportedException($"Code contained an unknown error: \nL{errLineNo}: ({codeIssue.Id}) {errMsg}");
}
Recompile:
srcText = string.Join('\n', newSrcTextLines);
}
continue;
FixStructs:
// replace lazy typed structs with explicit ones
var Structs = parsedNodes.Where(x => x.IsKind(SyntaxKind.StructDeclaration));
// ProcessInfo
if (Structs.Any(x => x.ToString().Contains("struct ProcessInfo")))
{
var structDef = Structs.First(x => x.ToString().Contains("struct ProcessInfo"));
int lineStart = structDef.GetLocation().GetLineSpan().StartLinePosition.Line;
int lineEnd = structDef.GetLocation().GetLineSpan().EndLinePosition.Line;
List<string> def = File.ReadAllLines(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SourceDefinitions", "ProcessInfo.cx")).ToList();
newSrcTextLines.RemoveRange(lineStart, (lineEnd - lineStart) + 1);
newSrcTextLines.InsertRange(lineStart, def);
}
srcText = string.Join('\n', newSrcTextLines);
// recompile to update line numbers
peStream.SetLength(0);
compResult = CompileFromText(srcText, out parsedSource).Emit(peStream);
parsedNodes = parsedSource.GetRoot().DescendantNodes();
// build index from line numbers to find new lines numbers after a comment got inserted
int[] lineIndex = new int[newSrcTextLines.Count];
for (int i = 0; i < lineIndex.Length; i++)
lineIndex[i] = i;
// add all comments from original file
CppCompilation parsedHeader = CppParser.ParseFile(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SourceDefinitions", "memflow.h"));
// enums
var Enums = parsedNodes.Where(x => x.IsKind(SyntaxKind.EnumDeclaration));
foreach (CppEnum cDef in parsedHeader.Enums)
{
if (cDef.TypeKind == CppTypeKind.Enum && cDef.Comment != null)
{
// find C# definition
SyntaxNode? csDef = Enums.FirstOrDefault(x => x.ChildTokens().FirstOrDefault(y => y.IsKind(SyntaxKind.IdentifierToken)).Text.Equals(cDef.Name, StringComparison.OrdinalIgnoreCase));
InsertCppComment(csDef, cDef.Comment, ref lineIndex, ref newSrcTextLines);
}
}
// structs
Structs = parsedNodes.Where(x => x.IsKind(SyntaxKind.StructDeclaration));
foreach (CppClass cDef in parsedHeader.Classes)
{
if (cDef.TypeKind == CppTypeKind.StructOrClass && cDef.Comment != null)
{
// find C# definition
SyntaxNode? csDef = Structs.FirstOrDefault(x => x.ChildTokens().FirstOrDefault(y => y.IsKind(SyntaxKind.IdentifierToken)).Text.Equals(cDef.Name, StringComparison.OrdinalIgnoreCase));
InsertCppComment(csDef, cDef.Comment, ref lineIndex, ref newSrcTextLines);
}
}
// functions
var Classes = parsedNodes.Where(x => x.IsKind(SyntaxKind.ClassDeclaration));
var Functions = Classes.First(x => x.ChildTokens().FirstOrDefault(y => y.IsKind(SyntaxKind.IdentifierToken)).Text.Equals("Methods", StringComparison.OrdinalIgnoreCase)).ChildNodes();
foreach (CppFunction cDef in parsedHeader.Functions)
{
// find C# field definition in Methods struct
SyntaxNode? csDef = Functions.FirstOrDefault(x => x.ChildTokens().FirstOrDefault(y => y.IsKind(SyntaxKind.IdentifierToken)).Text.Equals(cDef.Name, StringComparison.OrdinalIgnoreCase));
InsertCppComment(csDef, cDef.Comment, ref lineIndex, ref newSrcTextLines, 2);
}
srcText = string.Join('\n', newSrcTextLines);
break;
}
File.WriteAllText(filePath, srcText);
Console.WriteLine("Finished...");
}
/// <summary>
/// Inserts a C/C++ comment into its C# equivalent source text file.
/// </summary>
/// <param name="csDefinition">The C# definition of the C/C++ object.</param>
/// <param name="cComment">The C/C++ commentary.</param>
/// <param name="lineIndex">The line index that should be updated with new locations after comment got inserted.</param>
/// <param name="newScrLines">The source lines in which the comment should be inserted into.</param>
/// <param name="lineIntent">How many tab stops a comment should have before insertation.</param>
private static void InsertCppComment(SyntaxNode? csDefinition, CppComment cComment, ref int[] lineIndex, ref List<string> newScrLines, int lineIntent = 1)
{
if (cComment == null || cComment.Children.Count < 1)
return;
if (csDefinition == null)
return;
int originalLine = csDefinition.GetLocation().GetLineSpan().StartLinePosition.Line;
int lineStart = lineIndex[originalLine];
// build comment
List<string> comment = new();
string intent = "";
for (int i = 0; i < lineIntent; i++)
intent += " ";
comment.Add($"{intent}/// <summary>");
foreach (var rawCommLine in cComment.Children)
comment.Add($"{intent}/// {rawCommLine.ChildrenToString()}");
comment.Add($"{intent}/// </summary>");
newScrLines.InsertRange(lineStart, comment);
// update line index
for (int i = originalLine; i < lineIndex.Length; i++)
lineIndex[i] += comment.Count;
}
/// <summary>
/// Compiles a C# text in memory.
/// </summary>
/// <param name="srcText">The full source code to compile.</param>
/// <returns>The compile result.</returns>
private static CSharpCompilation CompileFromText(string srcText, out SyntaxTree parsedSyntax)
{
// try to compile file
SourceText codeString = SourceText.From(srcText);
CSharpParseOptions options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest);
parsedSyntax = SyntaxFactory.ParseSyntaxTree(codeString, options);
var references = new MetadataReference[]
{
MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Runtime.InteropServices.dll")),
MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Runtime.CompilerServices.Unsafe.dll")),
MetadataReference.CreateFromFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "System.Runtime.dll")),
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location),
};
return CSharpCompilation.Create("temp.dll",
new[] { parsedSyntax },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Release,
allowUnsafe: true,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default));
}
}
}