using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Kaitai; using Microsoft.CSharp; 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 = @"GeneratedOutput\"; private const string Namespace = "BlendFile"; private static readonly string[] AdaptedTypes = new[] { "uchar" }; private static HashSet customTypes; public static void Log(string message) { sb.AppendLine(message); Console.WriteLine(message); } public static void Main(string[] args) { Log("Reading blend file"); ReadBlendFile(); Log("Generating C# code..."); Log("Pass 1: Generating types"); 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()); } private static void ReadBlendFile() { Log("Reading empty.blend file"); blendfile = BlendFile.FromFile("empty.blend"); Log($"Header: Blender v{blendfile.Hdr.Version} {blendfile.Hdr.Endian}\n" + $"DataBlocks: {blendfile.Blocks.Count}\n" + $"DNA1: {blendfile.SdnaStructs.Count} structures\n"); } 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) { Log($"Generating struct {type.Type}"); bool referenceSelf = false; bool referencePointer = false; //Add the type to the custom types list customTypes.Add(type.Type); //Create a new type declaration var ctd = new CodeTypeDeclaration(type.Type); ctd.CustomAttributes.Add(new CodeAttributeDeclaration("DNAClassAttribute", new CodeAttributeArgument(new CodePrimitiveExpression(type.IdxType)), new CodeAttributeArgument(new CodePrimitiveExpression(type.Type)) )); foreach (var field in type.Fields) { if (field.Name.Contains("*")) { referencePointer = true; } if (field.Type.Contains(type.Type)) { referenceSelf = true; } } if (referenceSelf || referencePointer) { Log("Struct contains references"); ctd.IsClass = true; } else { ctd.IsStruct = true; } //Add the class to the namespace ns.Types.Add(ctd); //Add the fields to the class Log($"Fields: {type.Fields.Count}"); for (var index = 0; index < type.Fields.Count; index++) { var field = type.Fields[index]; CodeMemberField cmf; string name = field.Name; if (name.Contains("()")) continue; if (name.Contains("[")) { Log($"Generating array field {field.Name}"); cmf = CreateArrayMemberField(field); } else { Log($"Generating field {field.Name}"); cmf = CreateMemberField(field); } cmf.CustomAttributes.Add(GenerateDNAFieldAttribute(index, field, field.M_Parent.M_Parent)); ctd.Members.Add(cmf); } Log("Generating constructor"); ctd.Members.Add(GenerateConstructor(type, ctd)); 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.BaseTypes.Add(new CodeTypeReference(typeof(Attribute))); ctd.CustomAttributes.Add(new("AttributeUsage", new CodeAttributeArgument(new CodeSnippetExpression("AttributeTargets.Class | AttributeTargets.Struct")))); 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; //Check if the type is a built-in type or a custom type if (t != null) cmf = new(t, field.Name.ParseFName()); //Built-in type else { cmf = new(new CodeTypeReference(field.Type), field.Name.ParseFName()); //Custom type customTypes.Add(field.Type); } cmf.Attributes = MemberAttributes.Public; return cmf; } private static CodeMemberField CreateArrayMemberField(BlenderBlend.DnaField field) { Type t = Type.GetType(field.Type.ParseFType()); CodeMemberField cmf; // Parse all array dimensions var dimensions = new List(); var name = field.Name.ParseFName(); int startIndex = 0; // Get all array dimensions while ((startIndex = name.IndexOf('[', startIndex)) != -1) { int endIndex = name.IndexOf(']', startIndex); string sizeStr = name.Substring(startIndex + 1, endIndex - startIndex - 1); if (int.TryParse(sizeStr, out int size)) { dimensions.Add(size); } startIndex = endIndex + 1; } // Get clean field name (without array brackets) name = field.Name.ParseFName().Substring(0, field.Name.IndexOf('[')); //Check if the type is a built-in type or a custom type if (t != null) cmf = new(t, name); //Built-in type else { cmf = new(field.Type, name); //Custom type customTypes.Add(field.Type); } //Set the field attributes cmf.Attributes = MemberAttributes.Public; //Define the array type cmf.Type.ArrayElementType = new(field.Type.ParseFType() ?? field.Type); cmf.Type.ArrayRank = dimensions.Count; //Define the array initialization expression cmf.InitExpression = GenerateArrayInitExpression(cmf.Type, dimensions); return cmf; } 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(); return new CodeSnippetExpression($"new {type.BaseType}[{dims}]"); } private static CodeTypeConstructor GenerateStaticConstructor(CodeTypeDeclaration ctd) { CodeTypeConstructor ctc = new CodeTypeConstructor(); ctc.Attributes = MemberAttributes.Static; ctc.Statements.AddRange(ctd.Members .OfType() .Where(f => f.Type.ArrayRank > 0) .Select(f => { var dims = new List(); for (int i = 0; i < f.Type.ArrayRank; i++) { dims.Add(0); } return new CodeAssignStatement( new CFieldRefExp(new CThisRefExp(), f.Name), GenerateArrayInitExpression(f.Type, dims) ); }).ToArray()); return ctc; } private static CodeConstructor GenerateConstructor(BlenderBlend.DnaStruct type, CodeTypeDeclaration ctd) { //Create a normal constructor CodeConstructor cc = new CodeConstructor { Name = type.Type, Attributes = MemberAttributes.Public, ReturnType = new(type.Type) }; //Add the parameters to the constructor cc.Parameters.AddRange(ctd.Members .OfType() .Select(f => { var cpde = new CParamDeclExp(f.Type, f.Name); cpde.Direction = FieldDirection.In; return cpde; }).ToArray()); //Assign the parameters to the respective fields cc.Statements.AddRange(ctd.Members .OfType() .Select(f => new CodeAssignStatement( new CFieldRefExp(new CThisRefExp(), f.Name), new CArgRefExp(f.Name)) ).ToArray()); return cc; } private static void SetupCCU(out CodeGeneratorOptions genOpts, out CSharpCodeProvider provider, out CodeCompileUnit ccu) { genOpts = new() { BlankLinesBetweenMembers = false, BracingStyle = "Block", ElseOnClosing = true, IndentString = " ", VerbatimOrder = true }; provider = new(); //var date = DateTime.Now.ToString(CultureInfo.InvariantCulture); CodeNamespace globalNs = new CodeNamespace(); //CodeComment comment = new CodeComment("Automatically generated by BlenderSharp at " + date, false); //globalNs.Comments.Add(new(comment)); globalNs.Imports.Add(new("System")); ccu = new(); ccu.Namespaces.Add(globalNs); } 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(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(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}"); var ctd = new CodeTypeDeclaration(type) { IsStruct = true, Attributes = MemberAttributes.Public }; tempNs.Types.Add(ctd); } 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"; } } }