Files
BlenderSharp/CodeGenerator/Program.cs

641 lines
26 KiB
C#

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
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<string> 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) {
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);
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 size = 0;
var attributes = GenerateDNAFieldAttribute(index, field, field.M_Parent.M_Parent, totalSize, out 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 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);
cmp = new CodeMemberProperty() {
Name = "UnderlyingType",
Type = new CodeTypeReference(typeof(string)),
Attributes = MemberAttributes.Public,
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CodeSnippetExpression("_originalType"))
},
HasSet = true,
SetStatements = {
new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_originalType"), new CArgRefExp("value"))
}
};
ctd.Members.Add(cmp);
cmf = new CodeMemberField(typeof(bool), "_isPointer") {
Attributes = MemberAttributes.Private
};
ctd.Members.Add(cmf);
cmp = new CodeMemberProperty() {
Name = "IsPointer",
Type = new CodeTypeReference(typeof(bool)),
Attributes = MemberAttributes.Public,
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CodeSnippetExpression("_isPointer"))
},
HasSet = true,
SetStatements = {
new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_isPointer"), new CArgRefExp("value"))
}
};
ctd.Members.Add(cmp);
cmf = new CodeMemberField(typeof(int), "_memoryOffset") {
Attributes = MemberAttributes.Private
};
cmp = new CodeMemberProperty() {
Name = "MemoryOffset",
Type = new CodeTypeReference(typeof(int)),
Attributes = MemberAttributes.Public,
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CodeSnippetExpression("0"))
},
HasSet = true,
SetStatements = {
new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_memoryOffset"), new CArgRefExp("value"))
}
};
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(string), "underlyingType"),
new(typeof(int), "size"),
new(typeof(bool), "isPointer"),
new(typeof(int), "memoryOffset")
});
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(), "OriginalType"), new CArgRefExp("underlyingType")),
new(new CFieldRefExp(new CThisRefExp(), "Size"), new CArgRefExp("size")),
new(new CFieldRefExp(new CThisRefExp(), "IsPointer"), new CArgRefExp("isPointer")),
new(new CFieldRefExp(new CThisRefExp(), "MemoryOffset"), new CArgRefExp("memoryOffset"))
});
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);
cmf = new CodeMemberField(typeof(int), "_size") {
Attributes = MemberAttributes.Private
};
ctd.Members.Add(cmf);
cmp = new CodeMemberProperty() {
Name = "Size",
Type = new CodeTypeReference(typeof(int)),
Attributes = MemberAttributes.Public,
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CodeSnippetExpression("_size"))
},
HasSet = true,
SetStatements = {
new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_size"), new CArgRefExp("value"))
}
};
ctd.Members.Add(cmp);
var cc = new CodeConstructor() { Attributes = MemberAttributes.Public };
cc.Parameters.AddRange(new CParamDeclExp[] {
new(typeof(int), "originalIndex"),
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(), "OriginalName"), new CArgRefExp("originalName")),
new(new CFieldRefExp(new CThisRefExp(), "Size"), new CArgRefExp("size"))
});
ctd.Members.Add(cc);
return ctd;
}
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<amf.Type.ArrayRank; i++) {
sb.Append(",");
}
sb.Append("]");
t = sb.ToString();
var dimensions = GetArrayDimensions(field.Name);
foreach(var dim in dimensions) {
size *= dim;
}
} else
{
t = field.Type;
}
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(t)),
new(new CodePrimitiveExpression(size)),
new(new CodePrimitiveExpression(isPointer)),
new(new CodePrimitiveExpression(offset))
});
return cad;
}
private static CodeMemberField CreateMemberField(BlendFile.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(BlendFile.DnaField field) {
Type t = Type.GetType(field.Type.ParseFType());
CodeMemberField cmf;
// Parse all array dimensions
var name = field.Name.ParseFName();
var dimensions = GetArrayDimensions(name);
// 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;
}
private static List<int> GetArrayDimensions(string name)
{
var dimensions = new List<int>();
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<int> 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<CodeMemberField>()
.Where(f => f.Type.ArrayRank > 0)
.Select(f =>
{
var dims = new List<int>();
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<CodeStatement>());
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<CodeMemberField>()
.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<CodeMemberField>()
.Select(f => new CodeAssignStatement(
new CFieldRefExp(new CThisRefExp(), f.Name),
new CArgRefExp(f.Name))
).ToArray<CodeStatement>());
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<CodeMemberField>()
.Select(f => new CodeAssignStatement(
new CFieldRefExp(new CThisRefExp(), f.Name),
new CodeSnippetExpression("default"))
).ToArray<CodeStatement>());
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<CodeNamespaceImport>().ToArray());
ccu.Namespaces.Add(tempNs);
foreach (var type in ns.Types.OfType<CodeTypeDeclaration>()) {
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<CodeTypeDeclaration>().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");
}
}
}