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; // ReSharper disable BitwiseOperatorOnEnumWithoutFlags namespace CodeGenerator { using CParamDeclExp = CodeParameterDeclarationExpression; using CArgRefExp = CodeArgumentReferenceExpression; using CThisRefExp = CodeThisReferenceExpression; using CFieldRefExp = CodeFieldReferenceExpression; public static class Program { private static BlendFile _blendfile; private static readonly StringBuilder Sb = new(); private const string OutPath = @"GeneratedOutput"; private const string Namespace = "BlendFile"; private static HashSet _customTypes; private 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 ns = GenerateTypes(out var 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) { //Initialize the namespaces CodeNamespace rootNs = new CodeNamespace(Namespace); CodeNamespace ns = new CodeNamespace(Namespace+".DNA"); //Fill the attribute types then add them to the namespaces rootNs.Types.AddRange(GenerateTypeDeclarations()); 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 ("DNAClassAttribute", new CodeAttributeArgument(new CodePrimitiveExpression(type.IdxType)), new CodeAttributeArgument(new CodePrimitiveExpression(type.Type)) )); //TODO: when encountering a list, run trough the fields to find a count/lenght or similar data. 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); var totalSize = 0; //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); } var attributes = GenerateDnaFieldAttribute(index, field, field.M_Parent.M_Parent, totalSize, out int size); totalSize += size; cmf.CustomAttributes.Add(attributes); ctd.Members.Add(cmf); } ctd.CustomAttributes[0].Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(totalSize))); Log("Generating Parameterless constructor"); if(ctd.Members.Count > 0) ctd.Members.Add(GenerateParameterlessConstructor(type, ctd)); Log("Generating Default Constructor"); ctd.Members.Add(GenerateConstructor(type, ctd)); Log("Finished generating struct"); } additionalNs = rootNs; return ns; } private static CodeTypeDeclaration[] GenerateTypeDeclarations() { var attributeBuilder = new AttributeBuilder(); var typeDeclarations = new CodeTypeDeclaration[] { attributeBuilder.New().SetName("DNAFieldAttribute") .SetAttributeUsage(new (new CodeSnippetExpression("AttributeTargets.Field"))) .AddAutoProperty(typeof(int), "Size") .AddAutoProperty(typeof(string), "OriginalType") .AddAutoProperty(typeof(string), "OriginalName") .AddAutoProperty(typeof(int), "OriginalIndex") .AddAutoProperty(typeof(string), "UnderlyingType") .AddAutoProperty(typeof(bool), "IsPointer") .AddAutoProperty(typeof(int), "MemoryOffset") .AddPropertiesConstructor() .Build(), attributeBuilder.New().SetName("DNAClassAttribute") .SetAttributeUsage(new (new CodeSnippetExpression("AttributeTargets.Class | AttributeTargets.Struct"))) .AddAutoProperty(typeof(int), "OriginalIndex") .AddAutoProperty(typeof(string), "OriginalName") .AddAutoProperty(typeof(int), "Size") .AddPropertiesConstructor() .Build() }; return typeDeclarations; } private static CodeAttributeDeclaration GenerateDnaFieldAttribute(int index, BlendFile.DnaField field, BlendFile.Dna1Body body, int offset, out int size) { string t; size = body.Lengths[field.IdxType]; bool isPointer = false; if (field.Name.StartsWith('*')) { size = 8; isPointer = true; } if (field.Name.Contains('[')) { CodeMemberField amf = CreateArrayMemberField(field); var sb = new StringBuilder(); sb.Append(amf.Type.BaseType); sb.Append("["); for(int i=1; i GetArrayDimensions(string name) { var dimensions = new List(); 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; } return dimensions; } private 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 { 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(BlendFile.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) { 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 CodeConstructor GenerateParameterlessConstructor(BlendFile.DnaStruct type, CodeTypeDeclaration ctd) { //Create a normal constructor CodeConstructor cc = new CodeConstructor { Name = type.Type, Attributes = MemberAttributes.Public, ReturnType = new(type.Type) }; //Assign the parameters to the respective fields cc.Statements.AddRange(ctd.Members .OfType() .Select(f => new CodeAssignStatement( new CFieldRefExp(new CThisRefExp(), f.Name), new CodeSnippetExpression("default")) ).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 Path.Combine(OutPath, ns.Name, typeName + ".cs"); } } }