From 162f88860042d9ab4c86b42e28c1b9388fd28221 Mon Sep 17 00:00:00 2001 From: Samuele Lorefice Date: Wed, 22 Jan 2025 20:24:25 +0100 Subject: [PATCH] Added runtime generation of the attribute classes and marking of field metadata --- .idea/.idea.BlenderSharp/.idea/statistic.xml | 14 ++ CodeGenerator/Program.cs | 245 +++++++++++++++++-- 2 files changed, 240 insertions(+), 19 deletions(-) create mode 100644 .idea/.idea.BlenderSharp/.idea/statistic.xml diff --git a/.idea/.idea.BlenderSharp/.idea/statistic.xml b/.idea/.idea.BlenderSharp/.idea/statistic.xml new file mode 100644 index 0000000..fe71fe1 --- /dev/null +++ b/.idea/.idea.BlenderSharp/.idea/statistic.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/CodeGenerator/Program.cs b/CodeGenerator/Program.cs index 2c41cfd..be3660c 100644 --- a/CodeGenerator/Program.cs +++ b/CodeGenerator/Program.cs @@ -17,11 +17,16 @@ using BlendFile = Kaitai.BlenderBlend; // ReSharper disable BitwiseOperatorOnEnumWithoutFlags namespace CodeGenerator { + using CParamDeclExp = CodeParameterDeclarationExpression; + using CArgRefExp = CodeArgumentReferenceExpression; + using CThisRefExp = CodeThisReferenceExpression; + using CFieldRefExp = CodeFieldReferenceExpression; + public class Program { public static BlendFile blendfile; private static StringBuilder sb = new(); - private const string OutPath = @"Blendfile\DNA"; - private const string Namespace = "BlendFile.DNA"; + private const string OutPath = @"GeneratedOutput\"; + private const string Namespace = "BlendFile"; private static readonly string[] AdaptedTypes = new[] { "uchar" }; private static HashSet customTypes; @@ -37,10 +42,12 @@ namespace CodeGenerator { Log("Generating C# code..."); Log("Pass 1: Generating types"); - CodeNamespace ns = GenerateTypes(); + CodeNamespace rootNs; + CodeNamespace ns = GenerateTypes(out rootNs); Log("Pass 2: Writing out code"); OutputCodeFiles(ns); + OutputCodeFiles(rootNs, false); Log("Finished generating C# code!"); File.AppendAllText("Log.txt", sb.ToString()); @@ -54,10 +61,14 @@ namespace CodeGenerator { $"DataBlocks: {blendfile.Blocks.Count}\n" + $"DNA1: {blendfile.SdnaStructs.Count} structures\n"); } - - private static CodeNamespace GenerateTypes() { - CodeNamespace ns = new CodeNamespace(Namespace); - + + private static CodeNamespace GenerateTypes(out CodeNamespace additionalNs) { + CodeNamespace rootNs = new CodeNamespace(Namespace); + rootNs.Types.Add(GenerateDNAFieldAttributeType()); + rootNs.Types.Add(GenerateDNAClassAttributeType()); + CodeNamespace ns = new CodeNamespace(Namespace+".DNA"); + ns.Imports.Add(new(rootNs.Name)); + customTypes = new(); foreach (var type in blendfile.SdnaStructs) { @@ -93,7 +104,8 @@ namespace CodeGenerator { //Add the fields to the class Log($"Fields: {type.Fields.Count}"); - foreach (var field in type.Fields) { + for (var index = 0; index < type.Fields.Count; index++) { + var field = type.Fields[index]; CodeMemberField cmf; string name = field.Name; if (name.Contains("()")) continue; @@ -105,7 +117,7 @@ namespace CodeGenerator { Log($"Generating field {field.Name}"); cmf = CreateMemberField(field); } - + cmf.CustomAttributes.Add(GenerateDNAFieldAttribute(index, field, field.M_Parent.M_Parent)); ctd.Members.Add(cmf); } @@ -114,9 +126,196 @@ namespace CodeGenerator { Log("Finished generating struct"); } + additionalNs = rootNs; return ns; } + private static CodeTypeDeclaration GenerateDNAFieldAttributeType() { + var ctd = new CodeTypeDeclaration("DNAFieldAttribute") { + IsClass = true, + Attributes = MemberAttributes.Public + }; + ctd.BaseTypes.Add(new CodeTypeReference(typeof(Attribute))); + ctd.CustomAttributes.Add(new("AttributeUsage", + new CodeAttributeArgument(new CodeSnippetExpression("AttributeTargets.Field")))); + + var cmf = new CodeMemberField(typeof(int), "_size") { + Attributes = MemberAttributes.Private + }; + ctd.Members.Add(cmf); + + var cmp = new CodeMemberProperty() { + Name = "Size", + Type = new CodeTypeReference(typeof(int)), + Attributes = MemberAttributes.Public, + HasSet = true, + SetStatements = { + new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_size"), new CArgRefExp("value")) + }, + HasGet = true, + GetStatements = { + new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_size")) + } + }; + ctd.Members.Add(cmp); + + cmf = new CodeMemberField(typeof(string), "_originalType") { + Attributes = MemberAttributes.Private + }; + ctd.Members.Add(cmf); + + cmp = new CodeMemberProperty() { + Name = "OriginalType", + Type = new CodeTypeReference(typeof(string)), + Attributes = MemberAttributes.Public, + HasSet = true, + SetStatements = { + new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_originalType"), new CArgRefExp("value")) + }, + HasGet = true, + GetStatements = { + new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalType")) + } + }; + ctd.Members.Add(cmp); + + cmf = new CodeMemberField(typeof(string), "_originalName") { + Attributes = MemberAttributes.Private + }; + ctd.Members.Add(cmf); + + cmp = new CodeMemberProperty() { + Name = "OriginalName", + Type = new CodeTypeReference(typeof(string)), + Attributes = MemberAttributes.Public, + HasSet = true, + SetStatements = { + new CodeAssignStatement( + new CFieldRefExp(new CThisRefExp(), "_originalName"), new CArgRefExp("value")) + }, + HasGet = true, + GetStatements = { + new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalName")) + } + }; + ctd.Members.Add(cmp); + + cmf = new CodeMemberField(typeof(int), "_originalIndex") { + Attributes = MemberAttributes.Private + }; + ctd.Members.Add(cmf); + + cmp = new CodeMemberProperty() { + Name = "OriginalIndex", + Type = new CodeTypeReference(typeof(int)), + Attributes = MemberAttributes.Public, + HasSet = true, + SetStatements = { + new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_originalIndex"), new CArgRefExp("value")) + }, + HasGet = true, + GetStatements = { + new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalIndex")) + } + }; + ctd.Members.Add(cmp); + + var cc = new CodeConstructor() { Attributes = MemberAttributes.Public }; + + cc.Parameters.AddRange(new CParamDeclExp[] { + new(typeof(int), "originalIndex"), + new(typeof(string), "originalType"), + new(typeof(string), "originalName"), + new(typeof(int), "size") + }); + + cc.Statements.AddRange(new CodeAssignStatement[] { + new(new CFieldRefExp(new CThisRefExp(), "OriginalIndex"), new CArgRefExp("originalIndex")), + new(new CFieldRefExp(new CThisRefExp(), "OriginalType"), new CArgRefExp("originalType")), + new(new CFieldRefExp(new CThisRefExp(), "OriginalName"), new CArgRefExp("originalName")), + new(new CFieldRefExp(new CThisRefExp(), "Size"), new CArgRefExp("size")) + }); + + ctd.Members.Add(cc); + return ctd; + } + + private static CodeTypeDeclaration GenerateDNAClassAttributeType() { + var ctd = new CodeTypeDeclaration("DNAClassAttribute") { + IsClass = true, + Attributes = MemberAttributes.Public + }; + ctd.CustomAttributes.Add(new("AttributeUsage", + new CodeAttributeArgument(new CodeSnippetExpression("AttributeTargets.Class")))); + + var cmf = new CodeMemberField(typeof(int), "_originalIndex") { + Attributes = MemberAttributes.Private + }; + ctd.Members.Add(cmf); + + var cmp = new CodeMemberProperty() { + Name = "OriginalIndex", + Type = new CodeTypeReference(typeof(int)), + Attributes = MemberAttributes.Public, + HasSet = true, + SetStatements = { + new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_originalIndex"), new CArgRefExp("value")) + }, + HasGet = true, + GetStatements = { + new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalIndex")) + } + }; + ctd.Members.Add(cmp); + + cmf = new CodeMemberField(typeof(string), "_originalName") { + Attributes = MemberAttributes.Private + }; + ctd.Members.Add(cmf); + + cmp = new CodeMemberProperty() { + Name = "OriginalName", + Type = new CodeTypeReference(typeof(string)), + Attributes = MemberAttributes.Public, + HasSet = true, + SetStatements = { + new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_originalName"), new CArgRefExp("value")) + }, + HasGet = true, + GetStatements = { + new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalName")) + } + }; + ctd.Members.Add(cmp); + + var cc = new CodeConstructor() { Attributes = MemberAttributes.Public }; + + cc.Parameters.AddRange(new CParamDeclExp[] { + new(typeof(int), "originalIndex"), + new(typeof(string), "originalName") + }); + + cc.Statements.AddRange(new CodeAssignStatement[] { + new(new CFieldRefExp(new CThisRefExp(), "OriginalIndex"), new CArgRefExp("originalIndex")), + new(new CFieldRefExp(new CThisRefExp(), "OriginalName"), new CArgRefExp("originalName")) + }); + + ctd.Members.Add(cc); + return ctd; + } + + private static CodeAttributeDeclaration GenerateDNAFieldAttribute(int index, BlendFile.DnaField field, + BlendFile.Dna1Body body) { + CodeAttributeDeclaration cad = new("DNAFieldAttribute"); + cad.Arguments.AddRange(new CodeAttributeArgumentCollection() { + new(new CodePrimitiveExpression(index)), + new(new CodePrimitiveExpression(field.Type)), + new(new CodePrimitiveExpression(field.Name)), + new(new CodePrimitiveExpression((int)body.Lengths[field.IdxType])) + }); + return cad; + } + private static CodeMemberField CreateMemberField(BlenderBlend.DnaField field) { Type t = Type.GetType(field.Type.ParseFType()); CodeMemberField cmf; @@ -173,7 +372,7 @@ namespace CodeGenerator { public static CodeExpression GenerateArrayInitExpression(CodeTypeReference type, IEnumerable dimensions) { var dimValues = dimensions as int[] ?? dimensions.ToArray(); string dims = string.Concat(dimValues.Take(dimValues.Count() - 1).Select(d => $"{d},")); - dims+= dimValues.Last(); + dims += dimValues.Last(); return new CodeSnippetExpression($"new {type.BaseType}[{dims}]"); } @@ -190,7 +389,7 @@ namespace CodeGenerator { dims.Add(0); } return new CodeAssignStatement( - new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), f.Name), + new CFieldRefExp(new CThisRefExp(), f.Name), GenerateArrayInitExpression(f.Type, dims) ); }).ToArray()); @@ -210,7 +409,7 @@ namespace CodeGenerator { .OfType() .Select(f => { - var cpde = new CodeParameterDeclarationExpression(f.Type, f.Name); + var cpde = new CParamDeclExp(f.Type, f.Name); cpde.Direction = FieldDirection.In; return cpde; }).ToArray()); @@ -219,8 +418,8 @@ namespace CodeGenerator { cc.Statements.AddRange(ctd.Members .OfType() .Select(f => new CodeAssignStatement( - new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), f.Name), - new CodeArgumentReferenceExpression(f.Name)) + new CFieldRefExp(new CThisRefExp(), f.Name), + new CArgRefExp(f.Name)) ).ToArray()); return cc; @@ -248,18 +447,22 @@ namespace CodeGenerator { ccu.Namespaces.Add(globalNs); } - private static void OutputCodeFiles(CodeNamespace ns) { - if (!Path.Exists(OutPath)) Directory.CreateDirectory(OutPath); + private static void OutputCodeFiles(CodeNamespace ns, bool outputExtraTypes = true) { + string rootPath = Path.GetDirectoryName(GetOutputPath(ns, "")); + if (!Path.Exists(rootPath)) Directory.CreateDirectory(rootPath!); SetupCCU(out var codeGeneratorOptions, out var provider, out var ccu); - CodeNamespace tempNs = new CodeNamespace(Namespace); + CodeNamespace tempNs = new CodeNamespace(ns.Name); + tempNs.Imports.AddRange(ns.Imports.Cast().ToArray()); ccu.Namespaces.Add(tempNs); foreach (var type in ns.Types.OfType()) { tempNs.Types.Add(type); Log($"Writing out {(type.IsStruct ? "struct" : "class")} {type.Name}"); - using var sw = new StreamWriter($"{OutPath}\\{type.Name}.cs"); + using var sw = new StreamWriter(GetOutputPath(ns, type.Name)); provider.GenerateCodeFromCompileUnit(ccu, sw, codeGeneratorOptions); tempNs.Types.Remove(type); } + + if (!outputExtraTypes) return; customTypes.ExceptWith(ns.Types.OfType().Select(t => t.Name)); foreach (var type in customTypes) { Log($"Creating empty struct for missing {type}"); @@ -269,8 +472,12 @@ namespace CodeGenerator { }; tempNs.Types.Add(ctd); } - using var finalsw = new StreamWriter($"{OutPath}\\_ExtraTypes.cs"); + using var finalsw = new StreamWriter(GetOutputPath(ns, "_ExtraTypes")); provider.GenerateCodeFromCompileUnit(ccu, finalsw, codeGeneratorOptions); } + + private static string GetOutputPath(CodeNamespace ns, string typeName) { + return $"{OutPath}\\{string.Concat(ns.Name.Split('.').Skip(1))}\\{typeName}.cs"; + } } } \ No newline at end of file