Skip to content

Commit

Permalink
.NET: Initial support for generating enums in ref assemblies
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Apr 5, 2024
1 parent 4a1c9e9 commit 4ee4010
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 53 deletions.
96 changes: 96 additions & 0 deletions csharp-api/AssemblyGenerator/EnumGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#nullable enable

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Reflection;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Emit;
using System.IO;
using System.Dynamic;
using System.Security.Cryptography;
using System.Linq;
using Microsoft.CodeAnalysis.Operations;
using REFrameworkNET;

public class EnumGenerator {
private string enumName;
private REFrameworkNET.TypeDefinition t;
private EnumDeclarationSyntax? enumDeclaration;

public EnumDeclarationSyntax? EnumDeclaration {
get {
return enumDeclaration;
}
}

public EnumGenerator(string enumName, REFrameworkNET.TypeDefinition t) {
this.enumName = enumName;
this.t = t;

enumDeclaration = Generate();
}

public void Update(EnumDeclarationSyntax? typeDeclaration) {
this.enumDeclaration = typeDeclaration;
}
static readonly REFrameworkNET.ManagedObject s_FlagsAttribute = REFrameworkNET.TDB.Get().FindType("System.FlagsAttribute").GetRuntimeType();

public EnumDeclarationSyntax? Generate() {
var ogEnumName = new string(enumName);

// Pull out the last part of the class name (split '.' till last)
if (t.DeclaringType == null) {
enumName = enumName.Split('.').Last();
}

enumDeclaration = SyntaxFactory.EnumDeclaration(enumName)
.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword));

if (t.HasAttribute(s_FlagsAttribute, true)) {
enumDeclaration = enumDeclaration.AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.ParseName("System.FlagsAttribute"))));
}

foreach (REFrameworkNET.Field field in t.Fields) {
if (!field.IsStatic() || !field.IsLiteral()) {
continue;
}

if (field.GetDeclaringType() != t) {
continue;
}

var underlyingType = field.Type.GetUnderlyingType();

SyntaxToken literalToken;

switch (underlyingType.GetValueTypeSize()) {
case 1:
enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("byte")));
literalToken = SyntaxFactory.Literal(field.GetDataT<byte>(0, false));
break;
case 2:
enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("short")));
literalToken = SyntaxFactory.Literal(field.GetDataT<short>(0, false));
break;
case 4:
enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("int")));
literalToken = SyntaxFactory.Literal(field.GetDataT<int>(0, false));
break;
case 8:
enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("long")));
literalToken = SyntaxFactory.Literal(field.GetDataT<long>(0, false));
break;
default:
throw new System.Exception("Unknown enum underlying type size");
}

var fieldDeclaration = SyntaxFactory.EnumMemberDeclaration(field.Name);
var valueExpr = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, literalToken);
fieldDeclaration = fieldDeclaration.WithEqualsValue(SyntaxFactory.EqualsValueClause(valueExpr));
enumDeclaration = enumDeclaration.AddMembers(fieldDeclaration);
}

return enumDeclaration;
}
}
93 changes: 40 additions & 53 deletions csharp-api/AssemblyGenerator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,70 +277,57 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin

generatedTypes.Add(typeName);

// Generate starting from topmost parent first
if (t.ParentType != null) {
compilationUnit = MakeFromTypeEntry(context, t.ParentType.FullName ?? "", t.ParentType);
}
if (t.IsEnum()) {
var generator = new EnumGenerator(typeName, t);

/*var methods = t.Methods;
var fixedMethods = methods?
.Select(methodPair => {
var method = methodPair.Value;
var methodName = Il2CppDump.StripMethodName(method);
return (methodName, method);
})
.GroupBy(pair => pair.methodName)
.Select(group => group.First()) // Selects the first method of each group
.ToDictionary(pair => pair.methodName, pair => pair.method);*/

// Make methods a SortedSet of method names
HashSet<REFrameworkNET.Method> methods = [];

foreach (var method in t.Methods) {
//methods.Add(method);
if (!methods.Select(m => m.Name).Contains(method.Name)) {
if (method.DeclaringType == t) { // really important
methods.Add(method);
}
if (generator.EnumDeclaration == null) {
return compilationUnit;
}
}

var generator = new ClassGenerator(
typeName.Split('.').Last() == "file" ? typeName.Replace("file", "@file") : typeName,
t,
[.. methods]
);
var generatedNamespace = ExtractNamespaceFromType(t);

if (generator.TypeDeclaration == null) {
return compilationUnit;
}

var generatedNamespace = ExtractNamespaceFromType(t);
if (generatedNamespace != null) {
var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, generator.EnumDeclaration);
compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace);
} else {
Console.WriteLine("Failed to create namespace for " + typeName);
}
} else {
// Generate starting from topmost parent first
if (t.ParentType != null) {
compilationUnit = MakeFromTypeEntry(context, t.ParentType.FullName ?? "", t.ParentType);
}

if (generatedNamespace != null) {
// Split the using types by their namespace
/*foreach(var ut in usingTypes) {
var ns = ExtractNamespaceFromTypeName(context, ut.Name ?? "");
// Make methods a SortedSet of method names
HashSet<REFrameworkNET.Method> methods = [];

if (ns != null) {
generatedNamespace = generatedNamespace.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ut.Name ?? "")));
foreach (var method in t.Methods) {
//methods.Add(method);
if (!methods.Select(m => m.Name).Contains(method.Name)) {
if (method.DeclaringType == t) { // really important
methods.Add(method);
}
}
}*/
}

var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, generator.TypeDeclaration);
var generator = new ClassGenerator(
typeName.Split('.').Last() == "file" ? typeName.Replace("file", "@file") : typeName,
t,
[.. methods]
);

/*compilationUnit = compilationUnit.AddUsings(usingTypes.Select(
type => {
var ret = SyntaxFactory.UsingDirective (SyntaxFactory.ParseName(type.Name ?? ""));
System.Console.WriteLine(ret.GetText());
if (generator.TypeDeclaration == null) {
return compilationUnit;
}

return ret;
}
).ToArray());*/
var generatedNamespace = ExtractNamespaceFromType(t);

compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace);
} else {
Console.WriteLine("Failed to create namespace for " + typeName);
if (generatedNamespace != null) {
var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, generator.TypeDeclaration);
compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace);
} else {
Console.WriteLine("Failed to create namespace for " + typeName);
}
}

return compilationUnit;
Expand Down
1 change: 1 addition & 0 deletions csharp-api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ set_target_properties(csharp-api PROPERTIES VS_PACKAGE_REFERENCES "REFCSharpComp
# Target: AssemblyGenerator
set(AssemblyGenerator_SOURCES
"AssemblyGenerator/ClassGenerator.cs"
"AssemblyGenerator/EnumGenerator.cs"
"AssemblyGenerator/Generator.cs"
"AssemblyGenerator/SyntaxTreeBuilder.cs"
cmake.toml
Expand Down

0 comments on commit 4ee4010

Please sign in to comment.