Files
BlenderSharp/CodeGenerator/Program.cs

424 lines
18 KiB
C#

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<string> _customTypes;
private static readonly string[] ListLenghtStr = {"count", "length", "size"};
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("DNAAttribute")
.SetAttributeUsage(new (new CodeSnippetExpression("AttributeTargets.All")))
.AddAutoProperty(typeof(int), "OriginalIndex")
.AddAutoProperty(typeof(string), "OriginalName")
.AddPropertiesConstructor()
.Build(),
attributeBuilder.New().SetName("DNAFieldAttribute")
.SetAttributeUsage(new (new CodeSnippetExpression("AttributeTargets.Field")))
.AddAutoProperty(typeof(int), "Size")
.AddAutoProperty(typeof(string), "OriginalType")
.AddAutoProperty(typeof(int), "OriginalIndex")
.AddAutoProperty(typeof(string), "OriginalName")
.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), "Size")
.AddAutoProperty(typeof(int), "OriginalIndex")
.AddAutoProperty(typeof(string), "OriginalName")
.AddPropertiesConstructor()
.Build(),
attributeBuilder.New().SetName("DNAListAttribute")
.SetAttributeUsage(new (new CodeSnippetExpression("AttributeTargets.Property | AttributeTargets.Field")))
.AddAutoProperty(typeof(int), "Size")
.AddAutoProperty(typeof(string), "OriginalType")
.AddAutoProperty(typeof(string), "OriginalName")
.AddAutoProperty(typeof(int), "OriginalIndex")
.AddAutoProperty(typeof(string), "UnderlyingType")
.AddAutoProperty(typeof(string), "CountFieldName")
.AddAutoProperty(typeof(int), "CountFieldIndex")
.AddAutoProperty(typeof(int), "MemoryOffset")
.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<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");
}
}
}