diff --git a/CMakeLists.txt b/CMakeLists.txt index 70dc6313a..0291fc159 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,8 @@ project(reframework CSharp ) +include(CSharpUtilities) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") diff --git a/csharp-api/.editorconfig b/csharp-api/.editorconfig new file mode 100644 index 000000000..81a4983ff --- /dev/null +++ b/csharp-api/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*.cs] +indent_style = space +indent_size = 4 +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = false +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_members_in_anonymous_types = false +csharp_new_line_between_query_expression_clauses = false \ No newline at end of file diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index ddbc9c325..d1489ff44 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -15,26 +15,25 @@ using System; using System.ComponentModel.DataAnnotations; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using REFrameworkNET.Attributes; + public class ClassGenerator { - public class PseudoProperty { + public class PseudoProperty(TypeDefinition type) { public REFrameworkNET.Method? getter; public REFrameworkNET.Method? setter; - public REFrameworkNET.TypeDefinition? type; - public bool indexer = false; + public REFrameworkNET.TypeDefinition type = type; public REFrameworkNET.TypeDefinition? indexType; }; private Dictionary pseudoProperties = []; - private string className; private REFrameworkNET.TypeDefinition t; private List methods = []; private List fields = []; - public List usingTypes = []; - private TypeDeclarationSyntax? typeDeclaration; - private bool addedNewKeyword = false; - - private List internalFieldDeclarations = []; + private InterfaceDeclarationSyntax typeDeclaration; + private bool generic = false; + public TypeDeclarationSyntax? TypeDeclaration { get { @@ -42,26 +41,19 @@ public TypeDeclarationSyntax? TypeDeclaration { } } - public bool AddedNewKeyword { - get { - return addedNewKeyword; - } - } - - public void Update(TypeDeclarationSyntax? typeDeclaration_) { - typeDeclaration = typeDeclaration_; - } - public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { - className = REFrameworkNET.AssemblyGenerator.FixBadChars(className_); + public ClassGenerator(REFrameworkNET.TypeDefinition t_, bool? pGeneric = null) { t = t_; + generic = pGeneric ?? t.IsGenericTypeDefinition(); + typeDeclaration = InterfaceDeclaration("type"); + foreach (var method in t_.Methods) { // Means we've entered the parent type if (method.DeclaringType != t_) { break; } - + if (method.Name == null) { continue; } @@ -76,21 +68,17 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { // Add the getter to the pseudo property (create if it doesn't exist) var propertyName = method.Name[4..]; if (!pseudoProperties.ContainsKey(propertyName)) { - pseudoProperties[propertyName] = new PseudoProperty(); + pseudoProperties[propertyName] = new PseudoProperty(method.ReturnType); } pseudoProperties[propertyName].getter = method; - pseudoProperties[propertyName].type = method.ReturnType; } else if (method.Parameters.Count == 1 && method.Name == "get_Item") { // This is an indexer property var propertyName = method.Name[4..]; if (!pseudoProperties.ContainsKey(propertyName)) { - pseudoProperties[propertyName] = new PseudoProperty(); + pseudoProperties[propertyName] = new PseudoProperty(method.ReturnType); } - pseudoProperties[propertyName].getter = method; - pseudoProperties[propertyName].type = method.ReturnType; - pseudoProperties[propertyName].indexer = true; pseudoProperties[propertyName].indexType = method.Parameters[0].Type; } } else if (method.Name.StartsWith("set_")) { @@ -98,21 +86,17 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { // Add the setter to the pseudo property (create if it doesn't exist) var propertyName = method.Name[4..]; if (!pseudoProperties.ContainsKey(propertyName)) { - pseudoProperties[propertyName] = new PseudoProperty(); + pseudoProperties[propertyName] = new PseudoProperty(method.Parameters[0].Type); } - pseudoProperties[propertyName].setter = method; - pseudoProperties[propertyName].type = method.Parameters[0].Type; } else if (method.Parameters.Count == 2 && method.Name == "set_Item") { // This is an indexer property var propertyName = method.Name[4..]; if (!pseudoProperties.ContainsKey(propertyName)) { - pseudoProperties[propertyName] = new PseudoProperty(); + pseudoProperties[propertyName] = new PseudoProperty(method.Parameters[1].Type); } pseudoProperties[propertyName].setter = method; - pseudoProperties[propertyName].type = method.Parameters[1].Type; - pseudoProperties[propertyName].indexer = true; pseudoProperties[propertyName].indexType = method.Parameters[0].Type; } } else { @@ -149,90 +133,10 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { pseudoProperties.Remove(fieldName); } - typeDeclaration = Generate(); + Generate(); } - private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetType, REFrameworkNET.TypeDefinition? containingType) { - TypeSyntax outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); - - string ogTargetTypename = targetType != null ? targetType.GetFullName() : ""; - string targetTypeName = REFrameworkNET.AssemblyGenerator.FixBadChars(ogTargetTypename); - - if (targetType == null || targetTypeName == "System.Void" || targetTypeName == "") { - return outSyntax; - } - - if (AssemblyGenerator.typeFullRenames.TryGetValue(targetType, out string? value)) { - targetTypeName = value; - } - // Check for easily convertible types like System.Single, System.Int32, etc. - switch (targetTypeName) { - case "System.Single": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.FloatKeyword)); - break; - case "System.Double": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)); - break; - case "System.Int32": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)); - break; - case "System.UInt32": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UIntKeyword)); - break; - case "System.Int16": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ShortKeyword)); - break; - case "System.UInt16": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UShortKeyword)); - break; - case "System.Byte": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ByteKeyword)); - break; - case "System.SByte": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.SByteKeyword)); - break; - case "System.Char": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.CharKeyword)); - break; - case "System.Int64": - case "System.IntPtr": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.LongKeyword)); - break; - case "System.UInt64": - case "System.UIntPtr": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ULongKeyword)); - break; - case "System.Boolean": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)); - break; - case "System.String": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)); - break; - case "via.clr.ManagedObject": - case "System.Object": - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - default: - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(ogTargetTypename)) { - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - - /*if (targetTypeName.Contains('<')) { - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - }*/ - - targetTypeName = "global::" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(targetTypeName); - - outSyntax = SyntaxFactory.ParseTypeName(targetTypeName); - break; - } - - return outSyntax; - } - static readonly SortedSet invalidMethodNames = [ "Finalize", //"MemberwiseClone", @@ -255,136 +159,150 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy ]; - private TypeDeclarationSyntax? Generate() { - usingTypes = []; - - var ogClassName = new string(className); - - // Pull out the last part of the class name (split '.' till last) - if (t.DeclaringType == null) { - className = className.Split('.').Last(); - } - - typeDeclaration = SyntaxFactory - .InterfaceDeclaration(REFrameworkNET.AssemblyGenerator.CorrectTypeName(className)) - .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}); + static string[] GenericNames = ["T", "U", "V", "W", "X", "Y", "Z", "P7", "P8", "P9"]; + private void Generate() { + var (name, count) = TypeHandler.BaseTypeName(t.Name); + typeDeclaration = InterfaceDeclaration(name).AddModifiers(Token(SyntaxKind.PublicKeyword)); - if (typeDeclaration == null) { - return null; - } - - // Check if we need to add the new keyword to this. - if (AssemblyGenerator.NestedTypeExistsInParent(t)) { - typeDeclaration = typeDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - addedNewKeyword = true; + if (generic) { + var arguments = t.GenericArguments ?? []; + var parentGenericCount = Math.Max(0, arguments.Count() - count); + var argumentList = new List(); + for (int i = parentGenericCount; i < arguments.Count(); ++i) { + argumentList.Add(TypeParameter(GenericNames[i])); + } + typeDeclaration = typeDeclaration.AddTypeParameterListParameters([.. argumentList]); } // Set up base types - List baseTypes = []; + BaseTypeSyntax[] baseTypes = TypeHandler.ParentTypes(t); + typeDeclaration = typeDeclaration.AddBaseListTypes(baseTypes); - for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { - // TODO: Fix this - if (!AssemblyGenerator.validTypes.Contains(parent.FullName)) { - continue; - } - - AssemblyGenerator.typeFullRenames.TryGetValue(parent, out string? parentName); - parentName = AssemblyGenerator.CorrectTypeName(parentName ?? parent.FullName ?? ""); - if (parentName == null) { - break; - } - - if (parentName == "") { - break; - } - - if (parentName.Contains('[')) { - break; - } + // Add a static field that holds a NativeProxy to the class (for static methods) + var refProxyVarDecl = VariableDeclaration(TypeHandler.ProperType(t)) + .AddVariables( + VariableDeclarator("REFProxy") + .WithInitializer(EqualsValueClause(ParseExpression("REFType.As<" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(t.FullName) + ">()")))); - // Forces compiler to start at the global namespace - parentName = "global::" + parentName; - baseTypes.Add(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(parentName))); - usingTypes.Add(parent); - break; - } + var refTypeName = (FieldDeclarationSyntax)ParseMemberDeclaration($"public static readonly string REFTypeName = {GenericTypeNameExpr()};")!; + typeDeclaration = typeDeclaration.AddMembers(refTypeName); // Add a static field to the class that holds the REFrameworkNET.TypeDefinition - var refTypeVarDecl = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.TypeDefinition")) - .AddVariables(SyntaxFactory.VariableDeclarator("REFType").WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("global::REFrameworkNET.TDB.Get().FindType(\"" + t.FullName + "\")")))); - - var refTypeFieldDecl = SyntaxFactory.FieldDeclaration(refTypeVarDecl).AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); - - // Add a static field that holds a NativeProxy to the class (for static methods) - var refProxyVarDecl = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName(REFrameworkNET.AssemblyGenerator.CorrectTypeName(className))) - .AddVariables(SyntaxFactory.VariableDeclarator("REFProxy").WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.As<" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(t.FullName) + ">()")))); - - var refProxyFieldDecl = SyntaxFactory.FieldDeclaration(refProxyVarDecl).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + var refTypeFieldDecl = ParseMemberDeclaration( + $"public static readonly global::REFrameworkNET.TypeDefinition REFType = global::REFrameworkNET.TDB.Get().FindType(REFTypeName);" + )!; + typeDeclaration = typeDeclaration.AddMembers(refTypeFieldDecl); - typeDeclaration = GenerateMethods(baseTypes); - typeDeclaration = GenerateFields(baseTypes); - typeDeclaration = GenerateProperties(baseTypes); - if (baseTypes.Count > 0 && typeDeclaration != null) { - refTypeFieldDecl = refTypeFieldDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - //fieldDeclaration2 = fieldDeclaration2.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + GenerateMethods(); + GenerateFields(); + GenerateProperties(); - typeDeclaration = (typeDeclaration as InterfaceDeclarationSyntax)?.AddBaseListTypes(baseTypes.ToArray()); + typeDeclaration = typeDeclaration.AddMembers(TypeHandler.GenerateNestedTypes(t)); + if (t.FullName == "System.Array") { + var decl = GenericArrayType(); + if (decl is not null) + typeDeclaration = typeDeclaration.AddMembers(decl); } - if (typeDeclaration != null) { - typeDeclaration = typeDeclaration.AddMembers(refTypeFieldDecl); - //typeDeclaration = typeDeclaration.AddMembers(refProxyFieldDecl); - } - - // Logically needs to come after the REFType field is added as they reference it - if (internalFieldDeclarations.Count > 0 && typeDeclaration != null) { - typeDeclaration = typeDeclaration.AddMembers(internalFieldDeclarations.ToArray()); - } + } - return GenerateNestedTypes(); + private StatementSyntax GenericStub(TypeSyntax? returnType, string[] paramNames, int index) { + var ret = returnType ?? TypeHandler.VoidType(); + var argumentList = string.Join(",", paramNames); + var stmt = ret switch { + var t when t.IsEquivalentTo(TypeHandler.VoidType()) => + $@"(this as REFrameworkNET.IObject) + .GetTypeDefinition() + .GetMethods()[{index}] + .Invoke(this, [{argumentList}]);", + _ => + $@"return ({ret.ToFullString()}) + (this as REFrameworkNET.IObject) + .GetTypeDefinition() + .GetMethods()[{index}] + .InvokeBoxed(typeof({ret.ToFullString()}), this, [{argumentList}]);", + }; + return ParseStatement(stmt); } - private TypeDeclarationSyntax GenerateProperties(List baseTypes) { - if (typeDeclaration == null) { - throw new Exception("Type declaration is null"); // This should never happen + // This is a fun one + private string GenericTypeNameExpr() { + if (!generic) + return $"\"{t.FullName}\""; + if (t.FullName == "!0[]") + return $"REFrameworkNET.TypeName.Get() + \"[]\""; + var hierarchy = TypeHandler.NameHierarchy(t); + int genericCount = 0; + var expr = "\"\""; + bool dot = false; + foreach (var elem in hierarchy) { + if (dot) expr += "+ \".\""; + dot = true; + + var (name, count) = TypeHandler.BaseTypeName(elem); + if (count == 0) { + expr += $" + \"{name}\""; + } + + if (count != 0) { + expr += $"+ \"{elem}<\""; + for (int i = 0; i < count; ++i) { + if (i > 0) expr += "+ \",\""; + var genericParamName = GenericNames[genericCount++]; + expr += $"+ REFrameworkNET.TypeName.Get<{genericParamName}>()"; + } + expr += $"+ \">\""; + } } + return expr; + } + private void GenerateProperties() { if (pseudoProperties.Count == 0) { - return typeDeclaration!; + return; } + List internalFieldDeclarations = []; + var matchingProperties = pseudoProperties .Select(property => { - var propertyType = MakeProperType(property.Value.type, t); + var propertyType = TypeHandler.ProperType(property.Value.type); var propertyName = new string(property.Key); BasePropertyDeclarationSyntax propertyDeclaration = SyntaxFactory.PropertyDeclaration(propertyType, propertyName) .AddModifiers([SyntaxFactory.Token(SyntaxKind.PublicKeyword)]); - if (property.Value.indexer) { - ParameterSyntax parameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier("index")).WithType(MakeProperType(property.Value.indexType, t)); + if (property.Value.indexType is not null) { + ParameterSyntax parameter = SyntaxFactory + .Parameter(SyntaxFactory.Identifier("index")) + .WithType(TypeHandler.ProperType(property.Value.indexType)); propertyDeclaration = SyntaxFactory.IndexerDeclaration(propertyType) .AddModifiers([SyntaxFactory.Token(SyntaxKind.PublicKeyword)]) .AddParameterListParameters(parameter); } - bool shouldAddNewKeyword = false; bool shouldAddStaticKeyword = false; if (property.Value.getter != null) { - var getter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) - .AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( - SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + property.Value.getter.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.None)")) + var getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration); + if (!generic) { + getter = getter.AddAttributeLists( + AttributeList() + .AddAttributes(Attribute( + ParseName("global::REFrameworkNET.Attributes.Method"), + ParseAttributeArgumentList($"({property.Value.getter.Index}, global::REFrameworkNET.FieldFacadeType.None)")) )); + } if (property.Value.getter.IsStatic()) { shouldAddStaticKeyword = true; + } + if (generic | property.Value.getter.IsStatic()) { // Now we must add a body to it that actually calls the method // We have our REFType field, so we can lookup the method and call it // Make a private static field to hold the REFrameworkNET.Method @@ -395,55 +313,33 @@ private TypeDeclarationSyntax GenerateProperties(List base var methodFieldDeclaration = SyntaxFactory.FieldDeclaration(methodVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); internalFieldDeclarations.Add(methodFieldDeclaration); - List bodyStatements = []; - bodyStatements.Add(SyntaxFactory.ParseStatement("return (" + propertyType.GetText().ToString() + ")" + internalFieldName + ".InvokeBoxed(typeof(" + propertyType.GetText().ToString() + "), null, null);")); - - getter = getter.AddBodyStatements(bodyStatements.ToArray()); + var instance = property.Value.getter.IsStatic() ? "null" : "this"; + var stmt = $"return ({propertyType.ToFullString()}) {internalFieldName}.InvokeBoxed(typeof({propertyType.ToFullString()}), {instance}, null);"; + getter = getter.AddBodyStatements(ParseStatement(stmt)); } else { getter = getter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); } propertyDeclaration = propertyDeclaration.AddAccessorListAccessors(getter); - - var getterExtension = Il2CppDump.GetMethodExtension(property.Value.getter); - - if (baseTypes.Count > 0 && getterExtension != null && getterExtension.Override != null && getterExtension.Override == true) { - var matchingParentMethods = getterExtension.MatchingParentMethods; - // Go through the parents, check if the parents are allowed to be generated - // and add the new keyword if the matching method is found in one allowed to be generated - foreach (var matchingMethod in matchingParentMethods) { - var parent = matchingMethod.DeclaringType; - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { - continue; - } - - shouldAddNewKeyword = true; - break; - } - } - - if (baseTypes.Count > 0 && !shouldAddNewKeyword) { - var declaringType = property.Value.getter.DeclaringType; - if (declaringType != null) { - var parent = declaringType.ParentType; - - if (parent != null && (parent.FindField(propertyName) != null || parent.FindField("<" + propertyName + ">k__BackingField") != null)) { - shouldAddNewKeyword = true; - } - } - } + var getterExtension = Il2CppDump.GetMethodExtension(property.Value.getter); } if (property.Value.setter != null) { - var setter = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) - .AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( - SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + property.Value.setter.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.None)")) + var setter = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration); + if (!generic) { + setter = setter.AddAttributeLists( + AttributeList() + .AddAttributes(Attribute( + ParseName("global::REFrameworkNET.Attributes.Method"), + ParseAttributeArgumentList($"({property.Value.setter.Index}, global::REFrameworkNET.FieldFacadeType.None)")) )); - + } + if (property.Value.setter.IsStatic()) { shouldAddStaticKeyword = true; + } + if (generic | property.Value.setter.IsStatic()) { // Now we must add a body to it that actually calls the method // We have our REFType field, so we can lookup the method and call it @@ -455,67 +351,33 @@ private TypeDeclarationSyntax GenerateProperties(List base var methodFieldDeclaration = SyntaxFactory.FieldDeclaration(methodVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); internalFieldDeclarations.Add(methodFieldDeclaration); - List bodyStatements = []; - bodyStatements.Add(SyntaxFactory.ParseStatement(internalFieldName + ".Invoke(null, new object[] {value});")); - - setter = setter.AddBodyStatements(bodyStatements.ToArray()); + var instance = property.Value.setter.IsStatic() ? "null" : "this"; + var stmt = $"{internalFieldName}.Invoke({instance}, [value]);"; + setter = setter.AddBodyStatements(ParseStatement(stmt)); } else { setter = setter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); } - + propertyDeclaration = propertyDeclaration.AddAccessorListAccessors(setter); var setterExtension = Il2CppDump.GetMethodExtension(property.Value.setter); - - if (baseTypes.Count > 0 && setterExtension != null && setterExtension.Override != null && setterExtension.Override == true) { - var matchingParentMethods = setterExtension.MatchingParentMethods; - - // Go through the parents, check if the parents are allowed to be generated - // and add the new keyword if the matching method is found in one allowed to be generated - foreach (var matchingMethod in matchingParentMethods) { - var parent = matchingMethod.DeclaringType; - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { - continue; - } - - shouldAddNewKeyword = true; - break; - } - } - - if (baseTypes.Count > 0 && !shouldAddNewKeyword) { - var declaringType = property.Value.setter.DeclaringType; - if (declaringType != null) { - var parent = declaringType.ParentType; - - if (parent != null && (parent.FindField(propertyName) != null || parent.FindField("<" + propertyName + ">k__BackingField") != null)) { - shouldAddNewKeyword = true; - } - } - } } if (shouldAddStaticKeyword) { propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); } - - if (shouldAddNewKeyword) { - propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - } - return propertyDeclaration; - }); + }) + .ToArray(); - return typeDeclaration.AddMembers(matchingProperties.ToArray()); + typeDeclaration = typeDeclaration + .AddMembers([.. internalFieldDeclarations]) + .AddMembers(matchingProperties); } - private TypeDeclarationSyntax GenerateFields(List baseTypes) { - if (typeDeclaration == null) { - throw new Exception("Type declaration is null"); // This should never happen - } - + private void GenerateFields() { if (fields.Count == 0) { - return typeDeclaration!; + return; } List validFields = []; @@ -548,7 +410,7 @@ private TypeDeclarationSyntax GenerateFields(List baseType System.Console.WriteLine("Skipping field with non-ASCII characters: " + field.Name + " " + field.Index); continue; } - + ++totalFields; validFields.Add(field); @@ -556,13 +418,14 @@ private TypeDeclarationSyntax GenerateFields(List baseType // Some kind of limitation in the runtime prevents too many methods in the class if (totalFields >= (ushort.MaxValue - 15) / 2) { System.Console.WriteLine("Skipping fields in " + t.FullName + " because it has too many fields (" + fields.Count + ")"); - break; + // break; + return; } } - + List internalFieldDeclarations = []; var matchingFields = validFields .Select(field => { - var fieldType = MakeProperType(field.Type, t); + var fieldType = TypeHandler.ProperType(field.Type); var fieldName = new string(field.Name); // Replace the k backingfield crap @@ -570,27 +433,31 @@ private TypeDeclarationSyntax GenerateFields(List baseType fieldName = fieldName[1..fieldName.IndexOf(">k__")]; } - // So this is actually going to be made a property with get/set instead of an actual field - // 1. Because interfaces can't have fields - // 2. Because we don't actually have a concrete reference to the field in our VM, so we'll be a facade for the field - var fieldFacadeGetter = SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( - SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.Getter)")) - ); - - var fieldFacadeSetter = SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( - SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.Setter)")) - ); - - AccessorDeclarationSyntax getter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).AddAttributeLists(fieldFacadeGetter); - AccessorDeclarationSyntax setter = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).AddAttributeLists(fieldFacadeSetter); + AccessorDeclarationSyntax getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration); + AccessorDeclarationSyntax setter = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration); + if (!generic) { + // So this is actually going to be made a property with get/set instead of an actual field + // 1. Because interfaces can't have fields + // 2. Because we don't actually have a concrete reference to the field in our VM, so we'll be a facade for the field + var fieldFacadeGetter = AttributeList() + .AddAttributes(Attribute( + ParseName("global::REFrameworkNET.Attributes.Method"), + ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.Getter)"))); + + var fieldFacadeSetter = AttributeList() + .AddAttributes(Attribute( + ParseName("global::REFrameworkNET.Attributes.Method"), + ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.Setter)"))); + getter = getter.AddAttributeLists(fieldFacadeGetter); + setter = setter.AddAttributeLists(fieldFacadeSetter); + } var propertyDeclaration = SyntaxFactory.PropertyDeclaration(fieldType, fieldName) .AddModifiers([SyntaxFactory.Token(SyntaxKind.PublicKeyword)]); - if (field.IsStatic()) { - propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + if (field.IsStatic() || generic) { + if (field.IsStatic()) + propertyDeclaration = propertyDeclaration.AddModifiers(Token(SyntaxKind.StaticKeyword)); // Now we must add a body to it that actually calls the method // We have our REFType field, so we can lookup the method and call it @@ -605,77 +472,28 @@ private TypeDeclarationSyntax GenerateFields(List baseType List bodyStatementsSetter = []; List bodyStatementsGetter = []; + var instance = field.IsStatic() + ? "0" + : "(this as REFrameworkNET.IObject).GetAddress()"; - bodyStatementsGetter.Add(SyntaxFactory.ParseStatement("return (" + fieldType.GetText().ToString() + ")" + internalFieldName + ".GetDataBoxed(typeof(" + fieldType.GetText().ToString() + "), 0, false);")); - bodyStatementsSetter.Add(SyntaxFactory.ParseStatement(internalFieldName + ".SetDataBoxed(0, new object[] {value}, false);")); + var getterStatement = ParseStatement(@$" return ({fieldType.ToFullString()}) {internalFieldName} .GetDataBoxed(typeof({fieldType.ToFullString()}), {instance}, false);"); + var setterStatement = ParseStatement($"{internalFieldName}.SetDataBoxed({instance}, new object[] {{value}}, false);"); - getter = getter.AddBodyStatements(bodyStatementsGetter.ToArray()); - setter = setter.AddBodyStatements(bodyStatementsSetter.ToArray()); + getter = getter.AddBodyStatements(getterStatement); + setter = setter.AddBodyStatements(setterStatement); } else { getter = getter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); setter = setter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); } propertyDeclaration = propertyDeclaration.AddAccessorListAccessors(getter, setter); - - // Search for k__BackingField version and the corrected version - if (this.t.ParentType != null) { - var matchingField = this.t.ParentType.FindField(fieldName); - matchingField ??= this.t.ParentType.FindField(field.Name); - var matchingMethod = this.t.ParentType.FindMethod("get_" + fieldName); - matchingMethod ??= this.t.ParentType.FindMethod("set_" + fieldName); - - bool added = false; - - if (matchingField != null) { - var parentT = matchingField.DeclaringType; - - if (parentT != null && REFrameworkNET.AssemblyGenerator.validTypes.Contains(parentT.FullName)) { - propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - added = true; - } - } - - if (!added && matchingMethod != null) { - var parentT = matchingMethod.DeclaringType; - - if (parentT != null && REFrameworkNET.AssemblyGenerator.validTypes.Contains(parentT.FullName)) { - propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - } - } - - /*if ((this.t.ParentType.FindField(field.Name) != null || this.t.ParentType.FindField(fieldName) != null) || - (this.t.ParentType.FindMethod("get_" + fieldName) != null || this.t.ParentType.FindMethod("set_" + fieldName) != null)) - { - //propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - }*/ - } - - /*var fieldExtension = Il2CppDump.GetFieldExtension(field); - - if (fieldExtension != null && fieldExtension.MatchingParentFields.Count > 0) { - var matchingParentFields = fieldExtension.MatchingParentFields; - - // Go through the parents, check if the parents are allowed to be generated - // and add the new keyword if the matching field is found in one allowed to be generated - foreach (var matchingField in matchingParentFields) { - var parent = matchingField.DeclaringType; - if (parent == null) { - continue; - } - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { - continue; - } - - propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - break; - } - }*/ - return propertyDeclaration; - }); + }) + .ToArray(); - return typeDeclaration.AddMembers(matchingFields.ToArray()); + typeDeclaration = typeDeclaration + .AddMembers([.. internalFieldDeclarations]) + .AddMembers(matchingFields); } private static readonly Dictionary operatorTokens = new() { @@ -705,21 +523,16 @@ private TypeDeclarationSyntax GenerateFields(List baseType ["op_Explicit"] = SyntaxFactory.Token(SyntaxKind.ExplicitKeyword), }; - private TypeDeclarationSyntax GenerateMethods(List baseTypes) { - if (typeDeclaration == null) { - throw new Exception("Type declaration is null"); // This should never happen - } - - if (methods.Count == 0) { - return typeDeclaration!; - } + private void GenerateMethods() { + if (methods.Count == 0) return; HashSet seenMethodSignatures = []; List validMethods = []; + List internalFieldDeclarations = []; try { - foreach(REFrameworkNET.Method m in methods) { + foreach (REFrameworkNET.Method m in methods) { if (m == null) { continue; } @@ -732,79 +545,78 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp continue; } - if (m.ReturnType.FullName.Contains('!')) { - continue; - } - validMethods.Add(m); } - } catch (Exception e) { + } + catch (Exception e) { Console.WriteLine("ASDF Error: " + e.Message); } var matchingMethods = validMethods - .Select(method => - { - var returnType = MakeProperType(method.ReturnType, t); - - //string simpleMethodSignature = returnType.GetText().ToString(); - string simpleMethodSignature = ""; // Return types are not part of the signature. Return types are not overloaded. + .Select(method => { - var methodName = new string(method.Name); - var methodExtension = Il2CppDump.GetMethodExtension(method); + var returnType = TypeHandler.ProperType(method.ReturnType); - // Hacky fix for MHR because parent classes have the same method names - // while we support that, we don't support constructed generic arguments yet, they are just "object" - if (methodName == "sortCountList") { - Console.WriteLine("Skipping sortCountList"); - return null; - } - var methodDeclaration = SyntaxFactory.MethodDeclaration(returnType, methodName ?? "UnknownMethod") - .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) - /*.AddBodyStatements(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();"))*/; - - if (operatorTokens.ContainsKey(methodName ?? "UnknownMethod")) { - // Add SpecialName attribute to the method - methodDeclaration = methodDeclaration.AddAttributeLists( - SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( - SyntaxFactory.ParseName("global::System.Runtime.CompilerServices.SpecialName")) - ) - ); - } + //string simpleMethodSignature = returnType.GetText().ToString(); + string simpleMethodSignature = ""; // Return types are not part of the signature. Return types are not overloaded. - simpleMethodSignature += methodName; + var methodName = new string(method.Name); + if (methodName.StartsWith("System.")) + methodName = "_" + methodName; + var methodExtension = Il2CppDump.GetMethodExtension(method); - // Add full method name as a MethodName attribute to the method - methodDeclaration = methodDeclaration.AddAttributeLists( - SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( - SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ", global::REFrameworkNET.FieldFacadeType.None)"))) - ); + // Hacky fix for MHR because parent classes have the same method names + // while we support that, we don't support constructed generic arguments yet, they are just "object" + if (methodName == "sortCountList") { + Console.WriteLine("Skipping sortCountList"); + return null; + } - bool anyOutParams = false; - System.Collections.Generic.List paramNames = []; + var methodDeclaration = MethodDeclaration(returnType, methodName ?? "UnknownMethod").AddModifiers(Token(SyntaxKind.PublicKeyword)) + /*.AddBodyStatements(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();"))*/; - if (method.Parameters.Count > 0) { - // If any of the params have ! in them, skip this method - if (method.Parameters.Any(param => param != null && (param.Type == null || (param.Type != null && param.Type.FullName.Contains('!'))))) { - return null; + if (operatorTokens.ContainsKey(methodName ?? "UnknownMethod")) { + // Add SpecialName attribute to the method + methodDeclaration = methodDeclaration.AddAttributeLists( + SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( + SyntaxFactory.ParseName("global::System.Runtime.CompilerServices.SpecialName")) + ) + ); } - var runtimeMethod = method.GetRuntimeMethod(); - - if (runtimeMethod == null) { - REFrameworkNET.API.LogWarning("Method " + method.DeclaringType.FullName + "." + method.Name + " has a null runtime method"); - return null; + simpleMethodSignature += methodName; + + + // Add full method name as a MethodName attribute to the method + if (!generic) { + methodDeclaration = methodDeclaration.AddAttributeLists( + AttributeList() + .AddAttributes(Attribute( + ParseName("global::REFrameworkNET.Attributes.Method"), + ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ", global::REFrameworkNET.FieldFacadeType.None)")))); } - var runtimeParams = runtimeMethod.Call("GetParameters") as REFrameworkNET.ManagedObject; + bool anyOutParams = false; + List paramNames = []; + + if (method.Parameters.Count > 0) { + var runtimeMethod = method.GetRuntimeMethod(); + + if (runtimeMethod == null) { + REFrameworkNET.API.LogWarning("Method " + method.DeclaringType.FullName + "." + method.Name + " has a null runtime method"); + return null; + } + + var runtimeParams = runtimeMethod.Call("GetParameters") as REFrameworkNET.ManagedObject; + if (runtimeParams is null) { + return null; + } + System.Collections.Generic.List parameters = []; - System.Collections.Generic.List parameters = []; + bool anyUnsafeParams = false; - bool anyUnsafeParams = false; - if (runtimeParams != null) { var methodActualRetval = method.GetReturnType(); UInt32 unknownArgCount = 0; @@ -835,7 +647,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp } var parsedParamName = new string(paramName as string); - + /*if (param.get_IsGenericParameter() == true) { return null; // no generic parameters. }*/ @@ -845,8 +657,8 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp var isOut = paramDef != null && paramDef.FindMethod("get_IsOut") != null ? param.get_IsOut() : false; var paramTypeDef = (REFrameworkNET.TypeDefinition)paramType.get_TypeHandle(); - var paramTypeSyntax = MakeProperType(paramTypeDef, t); - + var paramTypeSyntax = TypeHandler.ProperType(paramTypeDef); + System.Collections.Generic.List modifiers = []; if (isOut == true) { @@ -882,162 +694,81 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp if (anyUnsafeParams) { methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); } - } - } else { - simpleMethodSignature += "()"; - } - - if (method.IsStatic()) { - // lets see what happens if we just make it static - methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); - // Now we must add a body to it that actually calls the method - // We have our REFType field, so we can lookup the method and call it - // Make a private static field to hold the REFrameworkNET.Method - var internalFieldName = "INTERNAL_" + method.Name + method.GetIndex().ToString(); - var methodVariableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.Method")) - .AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetMethod(\"" + method.GetMethodSignature() + "\")")))); - - var methodFieldDeclaration = SyntaxFactory.FieldDeclaration(methodVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); - internalFieldDeclarations.Add(methodFieldDeclaration); - - List bodyStatements = []; - - if (method.ReturnType.FullName == "System.Void") { - if (method.Parameters.Count == 0) { - bodyStatements.Add(SyntaxFactory.ParseStatement(internalFieldName + ".Invoke(null, null);")); - } else if (!anyOutParams) { - bodyStatements.Add(SyntaxFactory.ParseStatement(internalFieldName + ".Invoke(null, new object[] {" + string.Join(", ", paramNames) + "});")); - } else { - bodyStatements.Add(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();")); // TODO: Implement this - } } else { - if (method.Parameters.Count == 0) { - bodyStatements.Add(SyntaxFactory.ParseStatement("return (" + returnType.GetText().ToString() + ")" + internalFieldName + ".InvokeBoxed(typeof(" + returnType.GetText().ToString() + "), null, null);")); - } else if (!anyOutParams) { - bodyStatements.Add(SyntaxFactory.ParseStatement("return (" + returnType.GetText().ToString() + ")" + internalFieldName + ".InvokeBoxed(typeof(" + returnType.GetText().ToString() + "), null, new object[] {" + string.Join(", ", paramNames) + "});")); - } else { - bodyStatements.Add(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();")); // TODO: Implement this - } + simpleMethodSignature += "()"; } - methodDeclaration = methodDeclaration.AddBodyStatements( - [.. bodyStatements] - ); - } - - if (seenMethodSignatures.Contains(simpleMethodSignature)) { - Console.WriteLine("Skipping duplicate method: " + methodDeclaration.GetText().ToString()); - return null; - } - - seenMethodSignatures.Add(simpleMethodSignature); + if (method.IsStatic() || generic) { - // Add the rest of the modifiers here that would mangle the signature check - if (baseTypes.Count > 0 && methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { - var matchingParentMethods = methodExtension.MatchingParentMethods; + // lets see what happens if we just make it static + if (method.IsStatic()) + methodDeclaration = methodDeclaration.AddModifiers(Token(SyntaxKind.StaticKeyword)); - // Go through the parents, check if the parents are allowed to be generated - // and add the new keyword if the matching method is found in one allowed to be generated - // TODO: We can get rid of this once we start properly generating generic classes. - // Since we just ignore any class that has '<' in it. - foreach (var matchingMethod in matchingParentMethods) { - var parent = matchingMethod.DeclaringType; - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { - continue; + // Now we must add a body to it that actually calls the method + // We have our REFType field, so we can lookup the method and call it + // Make a private static field to hold the REFrameworkNET.Method + var index = t.Methods.IndexOf(method); + var internalFieldName = "INTERNAL_" + method.Name + method.GetIndex().ToString(); + internalFieldName = internalFieldName.Replace(".", "_"); + var methodVariableDeclaration = VariableDeclaration( + ParseTypeName("global::REFrameworkNET.Method")) + .AddVariables(VariableDeclarator(internalFieldName) + .WithInitializer( + EqualsValueClause(ParseExpression($"REFType.GetMethods()[{index}]")))); + + var methodFieldDeclaration = FieldDeclaration(methodVariableDeclaration) + .AddModifiers( + Token(SyntaxKind.PrivateKeyword), + Token(SyntaxKind.StaticKeyword), + Token(SyntaxKind.ReadOnlyKeyword)); + internalFieldDeclarations.Add(methodFieldDeclaration); + + List bodyStatements = []; + + var instance = "this"; + if (method.IsStatic()) { + instance = "null"; } - methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - break; + var statement = (method.ReturnType.FullName, method.Parameters) switch { + _ when anyOutParams => "throw new System.NotImplementedException();", + ("System.Void", []) => $"{internalFieldName}.Invoke({instance}, null);", + ("System.Void", [..]) => $"{internalFieldName}.Invoke({instance}, [{string.Join(",", paramNames)}]);", + (_, []) => $"return ({returnType.ToFullString()}) {internalFieldName}.InvokeBoxed(typeof({returnType.ToFullString()}), {instance}, null);", + (_, [..]) => $"return ({returnType.ToFullString()}) {internalFieldName}.InvokeBoxed(typeof({returnType.ToFullString()}), {instance}, [{string.Join(",", paramNames)}]);" + }; + methodDeclaration = methodDeclaration.AddBodyStatements(ParseStatement(statement)); + } else { + methodDeclaration = methodDeclaration.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); } - } - - return methodDeclaration; - }).Where(method => method != null).Select(method => method!); - - if (matchingMethods == null) { - return typeDeclaration; - } - - return typeDeclaration.AddMembers(matchingMethods.ToArray()); - } - - private TypeDeclarationSyntax? GenerateNestedTypes() { - if (this.typeDeclaration == null) { - return null; - } - - HashSet? nestedTypes = Il2CppDump.GetTypeExtension(t)?.NestedTypes; - - foreach (var nestedT in nestedTypes ?? []) { - var nestedTypeName = nestedT.FullName ?? ""; - - //System.Console.WriteLine("Nested type: " + nestedTypeName); - if (nestedTypeName == "") { - continue; - } - - if (nestedTypeName.Contains("[") || nestedTypeName.Contains("]") || nestedTypeName.Contains('<')) { - continue; - } - - if (nestedTypeName.Split('.').Last() == "file") { - nestedTypeName = nestedTypeName.Replace("file", "@file"); - } - - // Enum - if (nestedT.IsEnum()) { - var nestedEnumGenerator = new EnumGenerator(nestedTypeName.Split('.').Last(), nestedT); - - AssemblyGenerator.ForEachArrayType(nestedT, (arrayType) => { - var arrayTypeName = AssemblyGenerator.typeRenames[arrayType]; - - var arrayClassGenerator = new ClassGenerator( - arrayTypeName, - arrayType - ); - - if (arrayClassGenerator.TypeDeclaration != null) { - this.Update(this.typeDeclaration.AddMembers(arrayClassGenerator.TypeDeclaration)); - } - }); - - if (nestedEnumGenerator.EnumDeclaration != null) { - this.Update(this.typeDeclaration.AddMembers(nestedEnumGenerator.EnumDeclaration)); + if (seenMethodSignatures.Contains(simpleMethodSignature)) { + Console.WriteLine("Skipping duplicate method: " + methodDeclaration.NormalizeWhitespace().GetText().ToString()); + return null; } - continue; - } - - var nestedGenerator = new ClassGenerator( - nestedTypeName.Split('.').Last(), - nestedT - ); - - if (nestedGenerator.TypeDeclaration == null) { - continue; - } + seenMethodSignatures.Add(simpleMethodSignature); + return methodDeclaration; + }) + .Where(method => method != null) + .Select(method => method!) + .ToArray(); - AssemblyGenerator.ForEachArrayType(nestedT, (arrayType) => { - var arrayTypeName = AssemblyGenerator.typeRenames[arrayType]; + typeDeclaration = typeDeclaration + .AddMembers([.. internalFieldDeclarations]) + .AddMembers(matchingMethods); + } - var arrayClassGenerator = new ClassGenerator( - arrayTypeName, - arrayType - ); + private static InterfaceDeclarationSyntax? GenericArrayType() { + var array_generic = TDB.Get().GetType("!0[]"); + if (array_generic is null) return null; - if (arrayClassGenerator.TypeDeclaration != null) { - //this.Update(this.typeDeclaration.AddMembers(arrayClassGenerator.TypeDeclaration)); - this.Update(this.typeDeclaration.AddMembers(arrayClassGenerator.TypeDeclaration)); - } - }); + var decl = new ClassGenerator(array_generic, true).typeDeclaration; + return decl + .WithIdentifier(Identifier("Impl")) + .WithTypeParameterList(TypeParameterList(SingletonSeparatedList(TypeParameter("T")))); + } - if (nestedGenerator.TypeDeclaration != null) { - this.Update(this.typeDeclaration.AddMembers(nestedGenerator.TypeDeclaration)); - } - } - return typeDeclaration; - } -} \ No newline at end of file +} diff --git a/csharp-api/AssemblyGenerator/EnumGenerator.cs b/csharp-api/AssemblyGenerator/EnumGenerator.cs index 558ae2e91..c71baf346 100644 --- a/csharp-api/AssemblyGenerator/EnumGenerator.cs +++ b/csharp-api/AssemblyGenerator/EnumGenerator.cs @@ -47,21 +47,6 @@ public void Update(EnumDeclarationSyntax? typeDeclaration) { enumDeclaration = SyntaxFactory.EnumDeclaration(enumName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); - // Check if we need to add the new keyword to this. - if (AssemblyGenerator.NestedTypeExistsInParent(t)) { - enumDeclaration = enumDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - } else { - var declaringType = t.DeclaringType; - - if (declaringType != null) { - var existingField = declaringType.FindField(t.Name); - - if (existingField != null && AssemblyGenerator.validTypes.Contains(existingField.DeclaringType.FullName)) { - enumDeclaration = enumDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - } - } - } - if (t.HasAttribute(s_FlagsAttribute, true)) { enumDeclaration = enumDeclaration.AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.ParseName("System.FlagsAttribute")))); } @@ -75,7 +60,10 @@ public void Update(EnumDeclarationSyntax? typeDeclaration) { continue; } - var underlyingType = field.Type.GetUnderlyingType(); + var underlyingType = field.Type?.GetUnderlyingType(); + if (underlyingType is null) { + continue; + } SyntaxToken literalToken; bool foundRightType = true; diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 42fc3b0cc..cc6d52def 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -15,6 +15,12 @@ using System.Threading.Tasks; using System.Collections.Concurrent; using System.Reflection.Metadata; +using REFrameworkNET.Attributes; +using REFrameworkNET; +using REFrameworkNET.Callbacks; +using System.Threading; +using System.Security.Cryptography.X509Certificates; + public class Il2CppDump { public class Field { @@ -133,6 +139,8 @@ public static void FillTypeExtensions(REFrameworkNET.TDB context) { continue; } + if (t.IsGenericType() && !t.IsGenericTypeDefinition()) continue; + var tDeclaringType = t.DeclaringType; if (tDeclaringType != null) { var ext = GetOrAddTypeExtension(tDeclaringType); @@ -210,7 +218,9 @@ public static void FillTypeExtensions(REFrameworkNET.TDB context) { } } } - } + } + API.LocalFrameGC(); + API.LogInfo($"Loaded {typeExtensions.Count} types into db"); } } @@ -218,80 +228,10 @@ namespace REFrameworkNET { public class AssemblyGenerator { static Dictionary namespaces = []; - // Start with an empty CompilationUnitSyntax (represents an empty file) - //static CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit(); - - static readonly char[] invalidChars = [ - '<', - '>', - ',', - '!', - ' ', - ]; - - static readonly char[] invalidGenericChars = [ - '<', - '>', - ',', - '!', - ' ', - '`', - '\'' - ]; - public static string FixBadChars(string name) { - // Find the first <, and the last >, replace any dots in between with underscores - /*int first = name.IndexOf('<'); - int last = name.LastIndexOf('>'); - - if (first != -1 && last != -1) { - name = name.Substring(0, first) + name.Substring(first, last - first).Replace('.', '_') + name.Substring(last); - } - - // Replace any invalid characters with underscores - foreach (var c in invalidGenericChars) { - name = name.Replace(c, '_'); - }*/ - return name; } - public static string FixBadChars_Internal(string name) { - int first = name.IndexOf('<'); - int last = name.LastIndexOf('>'); - - if (first != -1 && last != -1) { - name = name.Substring(0, first) + name.Substring(first, last - first).Replace('.', '_') + name.Substring(last); - } - - // Replace any invalid characters with underscores - foreach (var c in invalidGenericChars) { - name = name.Replace(c, '_'); - } - - // Replace any "[[", "]]" with "_" - name = name.Replace("[[", "_").Replace("]]", "_"); - - return name; - } - - /*public static string FixBadCharsForGeneric(string name) { - // Find the first <, and the last >, replace any dots in between with underscores - int first = name.IndexOf('<'); - int last = name.LastIndexOf('>'); - - if (first != -1 && last != -1) { - name = name.Substring(0, first) + name.Substring(first, last - first).Replace('.', '_') + name.Substring(last); - } - - // Replace any invalid characters with underscores - foreach (var c in invalidGenericChars) { - name = name.Replace(c, '_'); - } - - return name; - }*/ - public static string CorrectTypeName(string fullName) { if (fullName.StartsWith("System.") || fullName.StartsWith("Internal.")) { return "_" + fullName; @@ -300,121 +240,23 @@ public static string CorrectTypeName(string fullName) { return FixBadChars(fullName); } - static public NamespaceDeclarationSyntax? ExtractNamespaceFromType(REFrameworkNET.TypeDefinition t) { + static public NamespaceDeclarationSyntax ExtractNamespaceFromType(REFrameworkNET.TypeDefinition t) { var ns = t.GetNamespace(); - if (ns != null && ns.Length > 0) { - if (ns.StartsWith("System.") || ns == "System" || ns.StartsWith("Internal.") || ns == "Internal") { - ns = "_" + ns; - } - - if (!namespaces.TryGetValue(ns, out NamespaceDeclarationSyntax? value)) { - //ns = Regex.Replace(ns, @"[^a-zA-Z0-9.]", "_"); - Console.WriteLine("Creating namespace " + ns); - value = SyntaxTreeBuilder.CreateNamespace(ns); - namespaces[ns] = value; - } - - return value; - } - - //Console.WriteLine("Failed to extract namespace from " + t.GetFullName()); - if (!namespaces.TryGetValue("_", out NamespaceDeclarationSyntax? value2)) { - value2 = SyntaxTreeBuilder.CreateNamespace("_"); - namespaces["_"] = value2; + if (ns is null || !ns.Any()) { + return SyntaxTreeBuilder.CreateNamespace("_"); } - return value2; + if (ns.StartsWith("System") || ns.StartsWith("Internal")) + ns = "_" + ns; + return SyntaxTreeBuilder.CreateNamespace(ns); } - public static SortedSet validTypes = []; - public static SortedSet generatedTypes = []; - - // Array of System.Array derived types - public static List arrayTypes = []; - public static HashSet typesWithArrayTypes = []; - private static Dictionary> elementTypesToArrayTypes = []; - - public static Dictionary typeRenames = []; - public static Dictionary typeFullRenames = []; - public static readonly REFrameworkNET.TypeDefinition SystemArrayT = REFrameworkNET.API.GetTDB().GetType("System.Array"); - - public static void ForEachArrayType( - TypeDefinition elementType, Action action, - HashSet? visited = null, - HashSet? visitedArrayTypes = null - ) - { - if (visited == null) { - visited = new HashSet(); - } - - if (visitedArrayTypes == null) { - visitedArrayTypes = new HashSet(); - } - - if (visited.Contains(elementType)) { - return; - } - - visited.Add(elementType); - - if (!elementTypesToArrayTypes.TryGetValue(elementType, out List? arrayTypes)) { - return; - } - - foreach (var arrayType in arrayTypes) { - if (visitedArrayTypes.Contains(arrayType)) { - continue; - } - - action(arrayType); - visitedArrayTypes.Add(arrayType); - - // Array types can have array types themselves. - ForEachArrayType(arrayType, action, visited); - } - } + public static ConcurrentDictionary generatedTypes = []; public static REFrameworkNET.TypeDefinition? GetEquivalentNestedTypeInParent(REFrameworkNET.TypeDefinition nestedT) { var isolatedNestedName = nestedT.FullName?.Split('.').Last(); - if (nestedT.DeclaringType == null && nestedT.IsDerivedFrom(SystemArrayT)) { - // Types derived from System.Array do not have a proper declaring type - // so we need to get the element type and find the declaring type of that - TypeDefinition? elementType = nestedT.GetElementType(); - - while (elementType != null && elementType.IsDerivedFrom(SystemArrayT)) { - elementType = elementType.GetElementType(); - } - - if (elementType != null) { - var equivalentElementType = GetEquivalentNestedTypeInParent(elementType); - - if (equivalentElementType != null) { - // Now go through all possible array types for that equivalent type - TypeDefinition? equivalentArray = null; - ForEachArrayType(equivalentElementType, (arrayType) => { - if (equivalentArray != null) { - return; - } - - var isolatedArrayTypeName = arrayType.FullName?.Split('.').Last(); - - if (isolatedArrayTypeName == isolatedNestedName) { - System.Console.WriteLine("Found equivalent array type for " + nestedT.FullName); - equivalentArray = arrayType; - return; - } - }); - - if (equivalentArray != null) { - return equivalentArray; - } - } - } - } - var t = nestedT.DeclaringType; if (t == null) { @@ -423,11 +265,6 @@ public static void ForEachArrayType( // Add the "new" keyword if this nested type is anywhere in the hierarchy for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { - // TODO: Fix this - if (!validTypes.Contains(parent.FullName)) { - continue; - } - var parentNestedTypes = Il2CppDump.GetTypeExtension(parent)?.NestedTypes; // Look for same named nested types @@ -448,282 +285,27 @@ public static bool NestedTypeExistsInParent(REFrameworkNET.TypeDefinition nested return GetEquivalentNestedTypeInParent(nestedT) != null; } - private static string? GetOptionalPrefix(REFrameworkNET.TypeDefinition t) { - if (t.Namespace == null || t.Namespace.Length == 0) { - if (t.DeclaringType == null) { - return "_."; - } else { - var lastDeclaringType = t.DeclaringType; - - while (lastDeclaringType.DeclaringType != null) { - lastDeclaringType = lastDeclaringType.DeclaringType; - } - - if (lastDeclaringType.Namespace == null || lastDeclaringType.Namespace.Length == 0) { - return "_."; - } - } - } - - return null; - } - - private static string MakePrefixedTypeName(REFrameworkNET.TypeDefinition t) { - var prefix = GetOptionalPrefix(t); - - if (prefix != null) { - return prefix + t.GetFullName(); - } - - return t.GetFullName(); - } - - private static bool HandleArrayType(REFrameworkNET.TypeDefinition t) { - var elementTypeDef = t.GetElementType(); - - if (elementTypeDef == null || !t.IsDerivedFrom(SystemArrayT)) { - System.Console.WriteLine("Failed to get element type for " + t.FullName); - return false; - } - - string arrayDims = "1D"; - - // Look for the last "[]" in the type name - // however we can have stuff like "[,]" or "[,,]" etc - var tFullName = t.GetFullName(); - var lastDims = tFullName.LastIndexOf('['); - var lastDimsEnd = tFullName.LastIndexOf(']'); - - if (lastDims != -1 && lastDimsEnd != -1) { - // Count how many , there are - var dimCount = 0; - - for (int i = lastDims+1; i < lastDimsEnd; i++) { - if (tFullName[i] == ',') { - dimCount++; - } - } - - arrayDims = (dimCount + 1).ToString() + "D"; - } - - typesWithArrayTypes.Add(elementTypeDef); - - if (!elementTypesToArrayTypes.ContainsKey(elementTypeDef)) { - elementTypesToArrayTypes[elementTypeDef] = []; - } - - elementTypesToArrayTypes[elementTypeDef].Add(t); - - // Check if the element type is a System.Array derived type - if (elementTypeDef.IsDerivedFrom(SystemArrayT)) { - if (HandleArrayType(elementTypeDef)) { - typeRenames[t] = typeRenames[elementTypeDef] + "_Array" + arrayDims; - } else { - typeRenames[t] = elementTypeDef.Name + "_Array" + arrayDims; - } - - if (typeFullRenames.ContainsKey(elementTypeDef)) { - typeFullRenames[t] = typeFullRenames[elementTypeDef] + "_Array" + arrayDims; - } - } else { - typeRenames[t] = elementTypeDef.Name + "_Array" + arrayDims; - typeFullRenames[t] = MakePrefixedTypeName(elementTypeDef) + "_Array" + arrayDims; - - if (typeFullRenames.ContainsKey(elementTypeDef)) { - typeFullRenames[t] = typeFullRenames[elementTypeDef] + "_Array" + arrayDims; - } - } - - return true; - } - - static void FillValidEntries(REFrameworkNET.TDB context) { - if (validTypes.Count > 0) { - return; - } - - context.GetType(0).GetFullName(); // initialize the types - - foreach (REFrameworkNET.TypeDefinition t in context.Types) { - //var t = context.GetType((uint)i); - var typeName = t.GetFullName(); - - if (typeName.Length == 0) { - Console.WriteLine("Bad type name"); - continue; - } - - // Generics and arrays not yet supported - if (typeName.Contains("[[") || typeName.Contains('!')) { - continue; - } - - if (typeName.Contains('<') || typeName.Contains('`')) { - continue; - } - - if (typeName.Any(c => c > 127)) { - System.Console.WriteLine("Skipping type with non-ascii characters " + typeName); - continue; - } - - // Check if abstract type and skip - /*var runtimeType = t.GetRuntimeType(); - - if (runtimeType != null && (runtimeType as dynamic).get_IsInterface()) { - System.Console.WriteLine("Skipping interface " + typeName); - continue; - } - - var friendlyTypeName = FixBadChars_Internal(typeName);*/ - - if (t.Namespace == null || t.Namespace.Length == 0) { - if (typeName.Length == 0) { - continue; - } - - var optionalPrefix = GetOptionalPrefix(t); - - if (optionalPrefix != null) { - typeFullRenames[t] = optionalPrefix + typeName; - } - } - - if (t.IsDerivedFrom(SystemArrayT)) { - arrayTypes.Add(t); - - HandleArrayType(t); - } - - /*if (t.IsGenericType() && !t.IsGenericTypeDefinition()) { - continue; - }*/ - - validTypes.Add(typeName); - } - } - - static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, string typeName, REFrameworkNET.TypeDefinition? t) { - var compilationUnit = SyntaxFactory.CompilationUnit(); - - if (!validTypes.Contains(typeName)) { - return compilationUnit; - } + static CompilationUnitSyntax? MakeFromTypeEntry(REFrameworkNET.TDB context, REFrameworkNET.TypeDefinition? t) { if (t == null) { Console.WriteLine("Failed to find type"); - return compilationUnit; - } - - if (t.DeclaringType != null) { - //MakeFromTypeEntry(context, t.DeclaringType.Name ?? "", t.DeclaringType); - return compilationUnit; // We want to define it inside of its declaring type, not a second time - } - - if (generatedTypes.Contains(typeName)) { - //Console.WriteLine("Skipping already generated type " + typeName); - return compilationUnit; - } - - generatedTypes.Add(typeName); - - // do not generate array types directly, we do it manually per element type - if (typeName.Contains("[]")) { - Console.WriteLine("Skipping array type " + typeName); - return compilationUnit; - } - - if (typeFullRenames.TryGetValue(t, out string? renamedTypeName)) { - typeName = renamedTypeName; + return null; } - if (t.IsEnum()) { - var generator = new EnumGenerator(typeName, t); - - if (generator.EnumDeclaration == 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); - } - - ForEachArrayType(t, (arrayType) => { - var arrayTypeName = typeFullRenames[arrayType]; - - var arrayClassGenerator = new ClassGenerator( - arrayTypeName, - arrayType - ); - - if (arrayClassGenerator.TypeDeclaration == null) { - return; - } - - // We can re-use the namespace from the original type - if (generatedNamespace != null) { - var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, arrayClassGenerator.TypeDeclaration); - compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); - } - }); - } else { - // Generate starting from topmost parent first - if (t.ParentType != null) { - compilationUnit = MakeFromTypeEntry(context, t.ParentType.FullName ?? "", t.ParentType); - } - - var generator = new ClassGenerator( - typeName.Split('.').Last() == "file" ? typeName.Replace("file", "@file") : typeName, - t - ); - - if (generator.TypeDeclaration == null) { - return compilationUnit; - } - - var generatedNamespace = ExtractNamespaceFromType(t); - - if (generatedNamespace != null) { - var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, generator.TypeDeclaration); - compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); - } else { - Console.WriteLine("Failed to create namespace for " + typeName); - } - - ForEachArrayType(t, (arrayType) => { - var arrayTypeName = typeFullRenames[arrayType]; - - System.Console.WriteLine("Generating array type " + arrayTypeName + " from " + t.FullName); - - if (arrayTypeName == "_.System.Array[]") { - typeFullRenames[arrayType] = "System.Array_Array1D"; - arrayTypeName = "System.Array_Array1D"; - } - - var arrayClassGenerator = new ClassGenerator( - arrayTypeName, - arrayType - ); - - if (arrayClassGenerator.TypeDeclaration == null) { - return; - } - - // We can re-use the namespace from the original type - if (generatedNamespace != null) { - var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, arrayClassGenerator.TypeDeclaration); - compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); - } - }); + if (t.DeclaringType != null) return null; + if (!generatedTypes.TryAdd(t.Index, true)) return null; + // if (generatedTypes.Contains(t.Index)) return null; + // generatedTypes.Add(t.Index); + var genNamespace = ExtractNamespaceFromType(t); + if (genNamespace is null) { + API.LogInfo($"Failed to create namespace for {t.FullName}"); + return null; } - return compilationUnit; + var member = TypeHandler.GenerateType(t); + if (member is null) return null; + genNamespace = genNamespace.AddMembers(member); + return SyntaxFactory.CompilationUnit().AddMembers(genNamespace); } [REFrameworkNET.Attributes.PluginEntryPoint] @@ -731,14 +313,15 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin try { return MainImpl(); } catch (Exception e) { - Console.WriteLine("Exception: " + e); + API.LogError("Exception: " + e); var ex = e; while (ex.InnerException != null) { ex = ex.InnerException; - Console.WriteLine("Inner Exception: " + ex); + API.LogError("Inner Exception: " + ex); } } + Environment.Exit(0); return []; } @@ -766,10 +349,12 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin REFrameworkNET.API.LogInfo("Generating assembly " + strippedAssemblyName); - List compilationUnits = []; var tdb = REFrameworkNET.API.GetTDB(); - List typeList = []; + List typeList = []; + + const string DEBUG_PATH = "reframework/debug-src"; + bool debugOut = Directory.Exists(DEBUG_PATH); foreach (var tIndex in assembly.Types) { var t = tdb.GetType(tIndex); @@ -780,60 +365,84 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin } dynamic runtimeType = t.GetRuntimeType(); - - if (runtimeType == null) { - Console.WriteLine("Failed to get runtime type for " + t.GetFullName()); - continue; + if (runtimeType is not null) { + // We don't want array types, pointers, etc + // Assembly.GetTypes usually filters this out but we have to manually do it + if (runtimeType.IsPointerImpl() || runtimeType.IsByRefImpl()) + continue; } + typeList.Add(t); + } + + HashSet delegateList = new(); + // Special case System.Action and System.Func, some of the generic implementations are dangling outside of modules + if (strippedAssemblyName.StartsWith("_System")) { + foreach (dynamic t in tdb.Types) { + if (t is not TypeDefinition type) continue; + var name = type.FullName ?? ""; + if (!(name.StartsWith("System.Action") || name.StartsWith("System.Func"))) + continue; + if (!type.IsGenericType()) continue; + var def = type.GetGenericTypeDefinition(); + if (delegateList.Add(def)) { + API.LogInfo($"Added delegate {def.FullName}({def.Name}):{def.Index} ({def.GetNamespace()})"); + API.LogInfo($"Name: {TypeHandler.ProperType(def)}"); + } - // We don't want array types, pointers, etc - // Assembly.GetTypes usually filters this out but we have to manually do it - if (runtimeType.IsPointerImpl() == false && runtimeType.IsByRefImpl() == false) { - typeList.Add(runtimeType); } } + typeList.AddRange(delegateList); // Clean up all the local objects // Mainly because some of the older games don't play well with a ton of objects on the thread local heap REFrameworkNET.API.LocalFrameGC(); - // Is this parallelizable? - foreach (dynamic reEngineT in typeList) { - var th = reEngineT.get_TypeHandle(); - - if (th == null) { - Console.WriteLine("Failed to get type handle for " + reEngineT.get_FullName()); - continue; - } - - var t = th as REFrameworkNET.TypeDefinition; - - if (t == null) { - Console.WriteLine("Failed to convert type handle for " + reEngineT.get_FullName()); - continue; - } + int count = typeList.Count; + var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); - var typeName = t.GetFullName(); - var compilationUnit = MakeFromTypeEntry(tdb, typeName, t); - compilationUnits.Add(compilationUnit); + var syntaxTrees = typeList + // .AsParallel() /// Causes memory violations for now, not exactly sure why + .Select((t) => { + var thisCount = Interlocked.Decrement(ref count); + if (thisCount % 1000 == 0) Console.WriteLine($"{thisCount} remaining"); + if (t == null) return null; + + var properType = TypeHandler.ProperType(t); + var sanitizedTypeName = properType + .ToFullString() + .Replace('<', '_') + .Replace('>', '_') + .Replace(':', '_'); + if (sanitizedTypeName.Count() > 50) { + sanitizedTypeName = sanitizedTypeName[..50]; + } + var compilationUnit = MakeFromTypeEntry(tdb, t)?.NormalizeWhitespace(); + if (compilationUnit is null) return null; - // Clean up all the local objects - // Mainly because some of the older games don't play well with a ton of objects on the thread local heap - REFrameworkNET.API.LocalFrameGC(); - } + if (debugOut && t.IsGenericType()) { + File.WriteAllText($"{DEBUG_PATH}/{sanitizedTypeName}.cs", compilationUnit.ToFullString()); + } + // Clean up all the local objects + // Mainly because some of the older games don't play well with a ton of objects on the thread local heap + REFrameworkNET.API.LocalFrameGC(); + return SyntaxFactory.SyntaxTree( + compilationUnit.NormalizeWhitespace(), + syntaxTreeParseOption, + $"{sanitizedTypeName}.cs" + ); + }) + .Where(s => s is not null) + .Select(s => s!) + .ToList(); - System.Console.WriteLine("Compiling " + strippedAssemblyName + " with " + compilationUnits.Count + " compilation units..."); - List syntaxTrees = new List(); - var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); - - foreach (var cu in compilationUnits) { - syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu, syntaxTreeParseOption)); - } string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); - var references = REFrameworkNET.Compiler.GenerateExhaustiveMetadataReferences(typeof(REFrameworkNET.API).Assembly, new List()); + var references = REFrameworkNET.Compiler.GenerateExhaustiveMetadataReferences( + typeof(REFrameworkNET.API).Assembly, + [typeof(REFrameworkNET.TypeName).Assembly] + ); // Add the previous compilations as references foreach (var compilationbc in previousCompilations) { @@ -841,15 +450,18 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin references.Add(MetadataReference.CreateFromStream(ms)); } - //compilationUnit = compilationUnit.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))); - System.Console.WriteLine("Compiling " + strippedAssemblyName + " with " + syntaxTrees.Count + " syntax trees..."); var csoptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release, + specificDiagnosticOptions: new Dictionary{ + // Ignore missing 'new' keyword, because it makes things much easier and we do want shadowing + ["CS0108"]=ReportDiagnostic.Suppress, + }, assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default, platform: Platform.X64, - allowUnsafe: true); + allowUnsafe: true + ); // Create a compilation CSharpCompilation compilation = CSharpCompilation.Create(strippedAssemblyName) .WithOptions(csoptions) @@ -864,23 +476,28 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin if (!result.Success) { //var textLines = syntaxTrees.GetText().Lines; - List sortedDiagnostics = result.Diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToList(); - sortedDiagnostics.Reverse(); - - foreach (Diagnostic diagnostic in sortedDiagnostics) - { - var textLines = diagnostic.Location.SourceTree?.GetText().Lines; - Console.WriteLine($"{diagnostic.Id}: {diagnostic.GetMessage()}"); - - var lineSpan = diagnostic.Location.GetLineSpan(); - var errorLineNumber = lineSpan.StartLinePosition.Line; - var errorLineText = textLines?[errorLineNumber].ToString(); - Console.WriteLine($"Error in line {errorLineNumber + 1}: {errorLineText}"); - //Console.WriteLine(diagnostic.Location.SourceTree?.GetText()); - //Console.WriteLine( - //$"Error in line {errorLineNumber + 1}: {lineSpan.StartLinePosition.Character + 1} - {lineSpan.EndLinePosition.Character + 1}"); - } + List<(SyntaxTree, List)> sortedDiagnostics = result.Diagnostics + .OrderBy(d => (d.Location.SourceTree?.FilePath, d.Location.SourceSpan.Start)) + .GroupBy(d => d.Location.SourceTree) + .Select(g => (g.Key!, g.ToList())) + .ToList(); + foreach (var (tree, diags) in sortedDiagnostics) { + var textLines = tree.GetText().Lines; + var errors = "\n"; + + foreach (var diagnostic in diags) { + var lineSpan = diagnostic.Location.GetLineSpan(); + var errorLineNumber = lineSpan.StartLinePosition.Line; + var errorLineText = textLines?[errorLineNumber].ToString(); + API.LogError($"{diagnostic.Id}: {diagnostic.GetMessage()}"); + API.LogError($"Error in {tree.FilePath}:{errorLineNumber + 1}: {errorLineText}"); + errors += $"/* at {errorLineNumber + 1}: {diagnostic.Id}: {diagnostic.GetMessage()} */\n"; + } + if (debugOut) { + File.WriteAllText($"{DEBUG_PATH}/{tree.FilePath}", tree.GetText().ToString() + errors); + } + } REFrameworkNET.API.LogError("Failed to compile " + strippedAssemblyName); } else @@ -907,11 +524,15 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin public static List MainImpl() { var tdb = REFrameworkNET.API.GetTDB(); - Il2CppDump.FillTypeExtensions(tdb); - FillValidEntries(tdb); + + // // var test = tdb.GetType("snow.enemy.EnemyCarryManagerBase`1.CarryInfo[[T, application, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"); + // var test = tdb.GetType("snow.enemy.EnemyCarryManagerBase`1.CarryInfo"); + + // API.LogInfo($"{test.GenericArguments?.Count().ToString() ?? "NOPARAMS"} {test.IsGenericType()} {test.IsGenericTypeDefinition()} {test.RuntimeType?.ToString() ?? "NORUNTIME"}"); + // throw new Exception("TestDone"); + API.LogInfo($"Start generating assemblies"); List modules = []; - // First module is an invalid module for (uint i = 0; i < tdb.GetNumModules(); i++) { var module = tdb.GetModule(i); @@ -919,10 +540,10 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin if (module == null) { continue; } - modules.Add(module); } - + Il2CppDump.FillTypeExtensions(tdb); + TypeHandler.BuildProperTypes(); List bytecodes = []; foreach (Module module in modules) { @@ -934,9 +555,9 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin continue; } - if (assemblyName == "") { - continue; - } + if (assemblyName == "") continue; + // if (assemblyName.Contains("application")) continue; + // if (assemblyName.Contains("viacore")) continue; REFrameworkNET.API.LogInfo("Assembly: " + assemblyName); REFrameworkNET.API.LogInfo("Location: " + location); @@ -944,16 +565,14 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin var bytecode = GenerateForAssembly(module, bytecodes); - if (bytecode != null) { - bytecodes.Add(bytecode); - } + if (bytecode is null) throw new Exception("Failed"); + bytecodes.Add(bytecode); // Clean up all the local objects // Mainly because some of the older games don't play well with a ton of objects on the thread local heap REFrameworkNET.API.LocalFrameGC(); } - return bytecodes; } -}; -} \ No newline at end of file +} +} diff --git a/csharp-api/AssemblyGenerator/TypeHandler.cs b/csharp-api/AssemblyGenerator/TypeHandler.cs new file mode 100644 index 000000000..bf48886c2 --- /dev/null +++ b/csharp-api/AssemblyGenerator/TypeHandler.cs @@ -0,0 +1,208 @@ +#nullable enable + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Linq; +using REFrameworkNET; + +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using System; + +class TypeHandler { + public static TypeSyntax VoidType() => PredefinedType(Token(SyntaxKind.VoidKeyword)); + public static TypeSyntax ObjType() => PredefinedType(Token(SyntaxKind.ObjectKeyword)); + public static Dictionary Predefined = new() { + ["System.Single"] = ParseTypeName("float"), + ["System.Double"] = ParseTypeName("double"), + ["System.Int32"] = ParseTypeName("int"), + ["System.UInt32"] = ParseTypeName("uint"), + ["System.Int16"] = ParseTypeName("short"), + ["System.UInt16"] = ParseTypeName("ushort"), + ["System.Byte"] = ParseTypeName("byte"), + ["System.SByte"] = ParseTypeName("sbyte"), + ["System.Char"] = ParseTypeName("char"), + ["System.Int64"] = ParseTypeName("long"), + ["System.IntPtr"] = ParseTypeName("long"), + ["System.UInt64"] = ParseTypeName("ulong"), + ["System.UIntPtr"] = ParseTypeName("ulong"), + ["System.Boolean"] = ParseTypeName("bool"), + ["System.String"] = ParseTypeName("string"), + ["via.clr.ManagedObject"] = ObjType(), + ["System.Object"] = ObjType(), + ["System.Void"] = VoidType(), + ["!0"] = ParseTypeName("T"), + ["!1"] = ParseTypeName("U"), + ["!2"] = ParseTypeName("V"), + ["!3"] = ParseTypeName("W"), + ["!4"] = ParseTypeName("X"), + ["!5"] = ParseTypeName("Y"), + ["!6"] = ParseTypeName("Z"), + ["!7"] = ParseTypeName("P7"), + ["!8"] = ParseTypeName("P8"), + ["!9"] = ParseTypeName("P9"), + }; + + public static Dictionary Cache = new(); + + public static (string, int) BaseTypeName(string baseName) { + if (baseName == "file") { + return ("_file", 0); + } + if (baseName.Split('`').ToArray() is [var name, var count]) + return (name, int.Parse(count)); + + return (baseName, 0); + + } + public static bool SkipType(TypeDefinition type) { + if (type.Name == "") return true; + if (type.Name.StartsWith("<")) return true; + if (type.Name.Contains("!!")) return true; + + if (!type.IsGenericType()) { + if (NameHierarchy(type).Any(name => name.Contains('`'))) return true; + } + return false; + } + + public static BaseTypeSyntax[] ParentTypes(TypeDefinition type) { + List parents = new(); + var parentType = type.ParentType; + while (parentType != null) { + if (parentType.Name == "") break; + if (parentType.FullName == "System.Object") { + parents.Insert(0, SimpleBaseType(ParseTypeName("global::_System.Object"))); + break; + } + + var properType = ProperType(parentType); + if (!properType.IsEquivalentTo(ObjType())) { + parents.Insert(0, SimpleBaseType(properType)); + } + parentType = parentType.ParentType; + } + return parents.ToArray(); + } + + + public static TypeSyntax ProperType(TypeDefinition type) { + if (Predefined.ContainsKey(type.FullName)) + return Predefined[type.FullName]; + if (Cache.ContainsKey(type.Index)) + return Cache[type.Index]; + return ObjType(); + } + + public static string[] NameHierarchy(TypeDefinition type) { + var typeList = new List(); + while (true) { + typeList.Insert(0, type.Name); + if (type.DeclaringType is null || type.DeclaringType == type) + break; + type = type.DeclaringType; + } + if (type.Namespace is not null && type.Namespace.Any()) { + typeList.Insert(0, type.Namespace); + } else { + typeList.Insert(0, "_"); + } + return [.. typeList]; + } + + static TypeSyntax BuildProperType(REFrameworkNET.TypeDefinition? targetType) { + if (targetType is null) return VoidType(); + if (Predefined.ContainsKey(targetType.FullName)) + return Predefined[targetType.FullName]; + if (Cache.ContainsKey(targetType.Index)) + return Cache[targetType.Index]; + + Cache[targetType.Index] = ObjType(); + if (targetType.GetElementType() is TypeDefinition elemType) { + Cache[targetType.Index] = ObjType(); + var elem = BuildProperType(elemType); + var arraySyntax = QualifiedName( + ParseName("global::_System.Array"), + GenericName("Impl") + .AddTypeArgumentListArguments([elem]) + ); + Cache[targetType.Index] = arraySyntax; + return arraySyntax; + } + if (SkipType(targetType)) return ObjType(); + + var typeList = NameHierarchy(targetType); + int genericIndex = 0; + var generics = targetType.GenericArguments ?? []; + var toParse = string.Join(".", typeList.Select(tName => { + var (name, count) = BaseTypeName(tName); + if (count == 0) return name; + name += "<"; + for (int i = 0; i < count; ++i) { + if (i > 0) name += ","; + if (i + genericIndex >= generics.Count()) { + name += "UNKN"; + continue; + } + var generic = generics[i + genericIndex]; + if (generic is null) + name += "object"; + else + name += ProperType(generics[i + genericIndex]).ToFullString(); + } + name += ">"; + genericIndex += count; + return name; + } + )); + if (toParse.StartsWith("System")) + toParse = "_" + toParse; + var parsed = ParseTypeName($"global::{toParse}"); + Cache[targetType.Index] = parsed; + return parsed; + } + + public static void BuildProperTypes() { + foreach (TypeDefinition type in API.GetTDB().Types) { + BuildProperType(type); + // Special case delegates again + if (!(type.FullName.StartsWith("System.Action") || type.FullName.StartsWith("System.Func"))) { + if (type.IsGenericType()) BuildProperType(type.GetGenericTypeDefinition()); + } + } + API.LogInfo($"Built {Cache.Count} proper types"); + } + + public static MemberDeclarationSyntax? GenerateType(TypeDefinition t) { + if (t.Name == "") return null; + if (t.FullName.EndsWith("[]")) return null; + if (SkipType(t)) return null; + if (t.IsGenericType() && !t.IsGenericTypeDefinition()) return null; + + try { + + // Enum + if (t.IsEnum()) { + var (baseName, _) = TypeHandler.BaseTypeName(t.Name); + var nestedEnumGenerator = new EnumGenerator(baseName, t); + return nestedEnumGenerator.EnumDeclaration; + } + var nestedGenerator = new ClassGenerator(t); + return nestedGenerator.TypeDeclaration; + } + catch (Exception e) { + throw new Exception($"{e.Message}\nWhile handling {t.FullName}", e); + + } + } + + public static MemberDeclarationSyntax[] GenerateNestedTypes(TypeDefinition t) { + var nestedTypes = Il2CppDump.GetTypeExtension(t)?.NestedTypes; + return nestedTypes? + .Select(GenerateType) + .Where(t => t is not null) + .Select(t => t!) + .ToArray() ?? []; + } +} diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 8440a7a18..2ae6e2019 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -156,6 +156,7 @@ set(REFCoreDeps_SOURCES "REFCoreDeps/Compiler.cs" "REFCoreDeps/GarbageCollectionDisplay.cs" "REFCoreDeps/HashHelper.cs" + "REFCoreDeps/TypeName.cs" cmake.toml ) @@ -332,6 +333,7 @@ set(AssemblyGenerator_SOURCES "AssemblyGenerator/EnumGenerator.cs" "AssemblyGenerator/Generator.cs" "AssemblyGenerator/SyntaxTreeBuilder.cs" + "AssemblyGenerator/TypeHandler.cs" cmake.toml ) diff --git a/csharp-api/REFCoreDeps/Compiler.cs b/csharp-api/REFCoreDeps/Compiler.cs index 867e5a807..42ed56700 100644 --- a/csharp-api/REFCoreDeps/Compiler.cs +++ b/csharp-api/REFCoreDeps/Compiler.cs @@ -140,7 +140,6 @@ public static List GenerateExhaustiveMetadataRefere return false; }); - return referencesStr.Select(r => MetadataReference.CreateFromFile(r)).ToList(); } diff --git a/csharp-api/REFCoreDeps/TypeName.cs b/csharp-api/REFCoreDeps/TypeName.cs new file mode 100644 index 000000000..67f556ec2 --- /dev/null +++ b/csharp-api/REFCoreDeps/TypeName.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace REFrameworkNET; + +public static class TypeName { + static Dictionary Predefined = new() { + [typeof(float)] = "System.Single", + [typeof(double)] = "System.Double", + [typeof(int)] = "System.Int32", + [typeof(uint)] = "System.UInt32", + [typeof(short)] = "System.Int16", + [typeof(ushort)] = "System.UInt16", + [typeof(byte)] = "System.Byte", + [typeof(sbyte)] = "System.SByte", + [typeof(char)] = "System.Char", + [typeof(long)] = "System.Int64", + [typeof(long)] = "System.IntPtr", + [typeof(ulong)] = "System.UInt64", + [typeof(ulong)] = "System.UIntPtr", + [typeof(bool)] = "System.Boolean", + [typeof(string)] = "System.String", + [typeof(object)] = "System.Object", + }; + public static string Get() { + if (Predefined.ContainsKey(typeof(T))) { + return Predefined[typeof(T)]; + } + return typeof(T).GetField("REFTypeName")?.GetValue(null) as string ?? ""; + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 8c48cd30d..fad9e1428 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -251,6 +251,19 @@ namespace REFrameworkNET { return (bool)runtimeType->Call("get_IsGenericType"); } + TypeDefinition ^ TypeDefinition::GetGenericTypeDefinition() { + auto runtimeType = this->GetRuntimeType(); + + if (runtimeType == nullptr) + return nullptr; + + auto genericTypeDefinition = (ManagedObject ^) runtimeType->Call("GetGenericTypeDefinition"); + if (genericTypeDefinition == nullptr) + return nullptr; + + return (TypeDefinition ^) genericTypeDefinition->Call("get_TypeHandle"); + } + array^ TypeDefinition::GetGenericArguments() { auto runtimeType = this->GetRuntimeType(); @@ -264,7 +277,7 @@ namespace REFrameworkNET { return nullptr; } - auto result = gcnew array((int)arguments->Call("get_Length", gcnew System::Int32(0))); + auto result = gcnew array((int)arguments->Call("get_Length")); for (int i = 0; i < result->Length; i++) { auto runtimeType = (ManagedObject^)arguments->Call("get_Item", gcnew System::Int32(i)); diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 280500b5f..eb8d2a2f4 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -394,6 +394,7 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, bool HasAttribute(REFrameworkNET::ManagedObject^ runtimeAttribute, bool inherit); bool IsGenericTypeDefinition(); bool IsGenericType(); + TypeDefinition ^ GetGenericTypeDefinition(); array^ GetGenericArguments(); property array^ GenericArguments {