mirror of
https://github.com/aaru-dps/Aaru.git
synced 2025-12-16 19:24:25 +00:00
Add big endian structure swapping source generator.
This commit is contained in:
39
Aaru.CommonTypes/Attributes/SwapEndianAttribute.cs
Normal file
39
Aaru.CommonTypes/Attributes/SwapEndianAttribute.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : SwapEndianAttribute.cs
|
||||||
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
|
//
|
||||||
|
// Component : Aaru.Generators.
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2.1 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2025 Natalia Portillo
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
// ReSharper disable once CheckNamespace
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Aaru.CommonTypes.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a struct for automatic generation of endianness swapping methods.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Struct)]
|
||||||
|
public sealed class SwapEndianAttribute : Attribute {}
|
||||||
@@ -14,4 +14,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
|
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="..\LICENSE.LGPL">
|
||||||
|
<Link>LICENSE.LGPL</Link>
|
||||||
|
</EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,3 +1,31 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : PluginRegisterGenerator.cs
|
||||||
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
|
//
|
||||||
|
// Component : Aaru.Generators.
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2.1 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2025 Natalia Portillo
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -33,7 +61,7 @@ public class PluginRegisterGenerator : ISourceGenerator
|
|||||||
|
|
||||||
if(pluginRegister == null) return;
|
if(pluginRegister == null) return;
|
||||||
|
|
||||||
string @namespace =
|
var @namespace =
|
||||||
(pluginRegister.Ancestors().FirstOrDefault(x => x is FileScopedNamespaceDeclarationSyntax) as
|
(pluginRegister.Ancestors().FirstOrDefault(x => x is FileScopedNamespaceDeclarationSyntax) as
|
||||||
FileScopedNamespaceDeclarationSyntax)?.Name.ToString();
|
FileScopedNamespaceDeclarationSyntax)?.Name.ToString();
|
||||||
|
|
||||||
@@ -339,49 +367,43 @@ public class PluginRegisterGenerator : ISourceGenerator
|
|||||||
.ValueText ==
|
.ValueText ==
|
||||||
"IArchive") ==
|
"IArchive") ==
|
||||||
true)
|
true)
|
||||||
{
|
if(!Archives.Contains(plugin.Identifier.Text))
|
||||||
if(!Archives.Contains(plugin.Identifier.Text)) Archives.Add(plugin.Identifier.Text);
|
Archives.Add(plugin.Identifier.Text);
|
||||||
}
|
|
||||||
|
|
||||||
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
||||||
.ValueText ==
|
.ValueText ==
|
||||||
"IChecksum") ==
|
"IChecksum") ==
|
||||||
true)
|
true)
|
||||||
{
|
if(!Checksums.Contains(plugin.Identifier.Text))
|
||||||
if(!Checksums.Contains(plugin.Identifier.Text)) Checksums.Add(plugin.Identifier.Text);
|
Checksums.Add(plugin.Identifier.Text);
|
||||||
}
|
|
||||||
|
|
||||||
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
||||||
.ValueText ==
|
.ValueText ==
|
||||||
"IFilesystem") ==
|
"IFilesystem") ==
|
||||||
true)
|
true)
|
||||||
{
|
if(!FileSystems.Contains(plugin.Identifier.Text))
|
||||||
if(!FileSystems.Contains(plugin.Identifier.Text)) FileSystems.Add(plugin.Identifier.Text);
|
FileSystems.Add(plugin.Identifier.Text);
|
||||||
}
|
|
||||||
|
|
||||||
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
||||||
.ValueText ==
|
.ValueText ==
|
||||||
"IFilter") ==
|
"IFilter") ==
|
||||||
true)
|
true)
|
||||||
{
|
if(!Filters.Contains(plugin.Identifier.Text))
|
||||||
if(!Filters.Contains(plugin.Identifier.Text)) Filters.Add(plugin.Identifier.Text);
|
Filters.Add(plugin.Identifier.Text);
|
||||||
}
|
|
||||||
|
|
||||||
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
||||||
.ValueText ==
|
.ValueText ==
|
||||||
"IFloppyImage") ==
|
"IFloppyImage") ==
|
||||||
true)
|
true)
|
||||||
{
|
if(!FloppyImagePlugins.Contains(plugin.Identifier.Text))
|
||||||
if(!FloppyImagePlugins.Contains(plugin.Identifier.Text)) FloppyImagePlugins.Add(plugin.Identifier.Text);
|
FloppyImagePlugins.Add(plugin.Identifier.Text);
|
||||||
}
|
|
||||||
|
|
||||||
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
||||||
.ValueText ==
|
.ValueText ==
|
||||||
"IFluxImage") ==
|
"IFluxImage") ==
|
||||||
true)
|
true)
|
||||||
{
|
if(!FluxImagePlugins.Contains(plugin.Identifier.Text))
|
||||||
if(!FluxImagePlugins.Contains(plugin.Identifier.Text)) FluxImagePlugins.Add(plugin.Identifier.Text);
|
FluxImagePlugins.Add(plugin.Identifier.Text);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
||||||
@@ -391,17 +413,15 @@ public class PluginRegisterGenerator : ISourceGenerator
|
|||||||
or "ITapeImage"
|
or "ITapeImage"
|
||||||
or "IFluxImage") ==
|
or "IFluxImage") ==
|
||||||
true)
|
true)
|
||||||
{
|
if(!MediaImagePlugins.Contains(plugin.Identifier.Text))
|
||||||
if(!MediaImagePlugins.Contains(plugin.Identifier.Text)) MediaImagePlugins.Add(plugin.Identifier.Text);
|
MediaImagePlugins.Add(plugin.Identifier.Text);
|
||||||
}
|
|
||||||
|
|
||||||
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
||||||
.ValueText ==
|
.ValueText ==
|
||||||
"IPartition") ==
|
"IPartition") ==
|
||||||
true)
|
true)
|
||||||
{
|
if(!PartitionPlugins.Contains(plugin.Identifier.Text))
|
||||||
if(!PartitionPlugins.Contains(plugin.Identifier.Text)) PartitionPlugins.Add(plugin.Identifier.Text);
|
PartitionPlugins.Add(plugin.Identifier.Text);
|
||||||
}
|
|
||||||
|
|
||||||
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
if(plugin.BaseList?.Types.Any(t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
|
||||||
.ValueText ==
|
.ValueText ==
|
||||||
|
|||||||
388
Aaru.Generators/SwapEndianGenerator.cs
Normal file
388
Aaru.Generators/SwapEndianGenerator.cs
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : SwapEndianGenerator.cs
|
||||||
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
|
//
|
||||||
|
// Component : Aaru.Generators.
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2.1 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2025 Natalia Portillo
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
|
||||||
|
namespace Aaru.Generators;
|
||||||
|
|
||||||
|
[Generator]
|
||||||
|
public class SwapEndianGenerator : IIncrementalGenerator
|
||||||
|
{
|
||||||
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
// Find all structs with the [SwapEndian] attribute and collect semantic info
|
||||||
|
// The attribute is defined in Aaru.CommonTypes.Attributes namespace
|
||||||
|
IncrementalValuesProvider<(StructDeclarationSyntax structDecl,
|
||||||
|
List<(string fieldName, ITypeSymbol typeSymbol, string typeName)> fieldTypes)?> structDeclarations =
|
||||||
|
context.SyntaxProvider
|
||||||
|
.CreateSyntaxProvider(static (s, _) => IsSyntaxTargetForGeneration(s),
|
||||||
|
static (ctx, _) => GetSemanticTargetForGeneration(ctx))
|
||||||
|
.Where(static m => m.HasValue);
|
||||||
|
|
||||||
|
context.RegisterSourceOutput(structDeclarations, static (spc, source) => Execute(source.Value, spc));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSyntaxTargetForGeneration(SyntaxNode node) =>
|
||||||
|
node is StructDeclarationSyntax { AttributeLists.Count: > 0 };
|
||||||
|
|
||||||
|
private static (StructDeclarationSyntax structDecl,
|
||||||
|
List<(string fieldName, ITypeSymbol typeSymbol, string typeName)> fieldTypes)? GetSemanticTargetForGeneration(
|
||||||
|
GeneratorSyntaxContext context)
|
||||||
|
{
|
||||||
|
var structDeclaration = (StructDeclarationSyntax)context.Node;
|
||||||
|
|
||||||
|
foreach(AttributeListSyntax attributeList in structDeclaration.AttributeLists)
|
||||||
|
{
|
||||||
|
foreach(AttributeSyntax attribute in attributeList.Attributes)
|
||||||
|
{
|
||||||
|
ISymbol symbol = context.SemanticModel.GetSymbolInfo(attribute).Symbol;
|
||||||
|
|
||||||
|
if(symbol is not IMethodSymbol attributeSymbol) continue;
|
||||||
|
|
||||||
|
INamedTypeSymbol attributeContainingType = attributeSymbol.ContainingType;
|
||||||
|
string fullName = attributeContainingType.ToDisplayString();
|
||||||
|
|
||||||
|
if(fullName != "Aaru.CommonTypes.Attributes.SwapEndianAttribute") continue;
|
||||||
|
|
||||||
|
// Collect field type information here where we have access to semantic model
|
||||||
|
|
||||||
|
IEnumerable<FieldDeclarationSyntax> fields = structDeclaration.Members.OfType<FieldDeclarationSyntax>();
|
||||||
|
|
||||||
|
var fieldTypes = (from field in fields
|
||||||
|
let typeInfo = context.SemanticModel.GetTypeInfo(field.Declaration.Type)
|
||||||
|
let typeSymbol = typeInfo.Type
|
||||||
|
let typeName = field.Declaration.Type.ToString()
|
||||||
|
from variable in field.Declaration.Variables
|
||||||
|
select (variable.Identifier.Text, typeSymbol, typeName)).ToList();
|
||||||
|
|
||||||
|
return (structDeclaration, fieldTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Execute(
|
||||||
|
(StructDeclarationSyntax structDeclaration, List<(string fieldName, ITypeSymbol typeSymbol, string typeName)>
|
||||||
|
fieldTypes) source, SourceProductionContext context)
|
||||||
|
{
|
||||||
|
if(source.structDeclaration == null) return;
|
||||||
|
|
||||||
|
string structName = source.structDeclaration.Identifier.Text;
|
||||||
|
string namespaceName = GetNamespace(source.structDeclaration);
|
||||||
|
List<(string Name, string Keyword)> containingTypes = GetContainingTypes(source.structDeclaration);
|
||||||
|
|
||||||
|
string generatedSource =
|
||||||
|
GenerateSwapEndianMethod(structName, namespaceName, containingTypes, source.fieldTypes);
|
||||||
|
|
||||||
|
context.AddSource($"{structName}_SwapEndian.g.cs", SourceText.From(generatedSource, Encoding.UTF8));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetNamespace(SyntaxNode syntax)
|
||||||
|
{
|
||||||
|
// Try file-scoped namespace first
|
||||||
|
FileScopedNamespaceDeclarationSyntax fileScopedNamespace = syntax.Ancestors()
|
||||||
|
.OfType<FileScopedNamespaceDeclarationSyntax>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if(fileScopedNamespace != null) return fileScopedNamespace.Name.ToString();
|
||||||
|
|
||||||
|
// Try regular namespace
|
||||||
|
NamespaceDeclarationSyntax namespaceDeclaration = syntax.Ancestors()
|
||||||
|
.OfType<NamespaceDeclarationSyntax>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return namespaceDeclaration?.Name.ToString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<(string Name, string Keyword)> GetContainingTypes(SyntaxNode syntax)
|
||||||
|
{
|
||||||
|
var containingTypes = new List<(string Name, string Keyword)>();
|
||||||
|
|
||||||
|
foreach(SyntaxNode ancestor in syntax.Ancestors())
|
||||||
|
{
|
||||||
|
switch(ancestor)
|
||||||
|
{
|
||||||
|
case ClassDeclarationSyntax classDecl:
|
||||||
|
string classModifiers = GetValidPartialModifiers(classDecl.Modifiers);
|
||||||
|
containingTypes.Insert(0, (classDecl.Identifier.Text, $"{classModifiers}partial class".Trim()));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case StructDeclarationSyntax structDecl:
|
||||||
|
string structModifiers = GetValidPartialModifiers(structDecl.Modifiers);
|
||||||
|
containingTypes.Insert(0, (structDecl.Identifier.Text, $"{structModifiers}partial struct".Trim()));
|
||||||
|
|
||||||
|
break;
|
||||||
|
case RecordDeclarationSyntax recordDecl:
|
||||||
|
string recordModifiers = GetValidPartialModifiers(recordDecl.Modifiers);
|
||||||
|
containingTypes.Insert(0, (recordDecl.Identifier.Text, $"{recordModifiers}partial record".Trim()));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return containingTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetValidPartialModifiers(SyntaxTokenList modifiers)
|
||||||
|
{
|
||||||
|
var validModifiers = (from modifier in modifiers
|
||||||
|
select modifier.Text
|
||||||
|
into modifierText
|
||||||
|
where modifierText != "partial"
|
||||||
|
where modifierText is "public" or "internal" or "private" or "protected" or "unsafe"
|
||||||
|
select modifierText).ToList();
|
||||||
|
|
||||||
|
return validModifiers.Count > 0 ? string.Join(" ", validModifiers) + " " : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateSwapEndianMethod(string structName, string namespaceName,
|
||||||
|
List<(string Name, string Keyword)> containingTypes,
|
||||||
|
List<(string fieldName, ITypeSymbol typeSymbol, string typeName)>
|
||||||
|
fieldTypes)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.AppendLine("// <auto-generated/>");
|
||||||
|
sb.AppendLine("#nullable enable");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("using System;");
|
||||||
|
sb.AppendLine("using System.Runtime.CompilerServices;");
|
||||||
|
sb.AppendLine("using Aaru.Helpers;");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
if(!string.IsNullOrEmpty(namespaceName))
|
||||||
|
{
|
||||||
|
sb.AppendLine($"namespace {namespaceName};");
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate containing type declarations
|
||||||
|
foreach((string name, string keyword) in containingTypes)
|
||||||
|
{
|
||||||
|
sb.AppendLine($"{keyword} {name}");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasEnums = false;
|
||||||
|
sb.AppendLine($" partial struct {structName} : ISwapEndian<{structName}>");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" /// <summary>Swaps the endianness of all numeric fields in this structure</summary>");
|
||||||
|
sb.AppendLine(" /// <returns>A new structure with swapped endianness</returns>");
|
||||||
|
sb.AppendLine(" [MethodImpl(MethodImplOptions.AggressiveInlining)]");
|
||||||
|
sb.AppendLine($" public {structName} SwapEndian()");
|
||||||
|
sb.AppendLine(" {");
|
||||||
|
sb.AppendLine(" var result = this;");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// Process each field
|
||||||
|
foreach((string fieldName, ITypeSymbol typeSymbol, string typeName) in fieldTypes)
|
||||||
|
{
|
||||||
|
(string swapCode, bool usesEnumHelper) = GenerateSwapCode(typeName, fieldName, typeSymbol);
|
||||||
|
|
||||||
|
if(string.IsNullOrEmpty(swapCode)) continue;
|
||||||
|
sb.AppendLine(swapCode);
|
||||||
|
if(usesEnumHelper) hasEnums = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine(" return result;");
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
|
||||||
|
// Add the SwapEnumValue helper method only if we have enums
|
||||||
|
if(hasEnums) sb.Append(GenerateSwapEnumValueHelper());
|
||||||
|
|
||||||
|
sb.AppendLine(" }");
|
||||||
|
|
||||||
|
// Close containing type declarations
|
||||||
|
for(var i = 0; i < containingTypes.Count; i++) sb.AppendLine("}");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (string code, bool usesEnumHelper) GenerateSwapCode(string typeName, string fieldName,
|
||||||
|
ITypeSymbol typeSymbol)
|
||||||
|
{
|
||||||
|
// Remove array brackets and modifiers for type checking
|
||||||
|
string cleanTypeName = typeName.TrimEnd('[', ']', '?').Trim();
|
||||||
|
|
||||||
|
string code = cleanTypeName switch
|
||||||
|
{
|
||||||
|
"short" or "Int16" =>
|
||||||
|
$" result.{fieldName} = (short)((result.{fieldName} << 8) | ((result.{fieldName} >> 8) & 0xFF));",
|
||||||
|
|
||||||
|
"ushort" or "UInt16" =>
|
||||||
|
$" result.{fieldName} = (ushort)((result.{fieldName} << 8) | (result.{fieldName} >> 8));",
|
||||||
|
|
||||||
|
"int" or "Int32" => $$"""
|
||||||
|
var {{fieldName}}_temp = result.{{fieldName}};
|
||||||
|
{{fieldName}}_temp = (int)(({{fieldName}}_temp << 8) & 0xFF00FF00 | ((uint){{fieldName}}_temp >> 8) & 0xFF00FF);
|
||||||
|
result.{{fieldName}} = (int)(((uint){{fieldName}}_temp << 16) | ((uint){{fieldName}}_temp >> 16) & 0xFFFF);
|
||||||
|
""",
|
||||||
|
|
||||||
|
"uint" or "UInt32" => $$"""
|
||||||
|
var {{fieldName}}_temp = result.{{fieldName}};
|
||||||
|
{{fieldName}}_temp = ({{fieldName}}_temp << 8) & 0xFF00FF00 | ({{fieldName}}_temp >> 8) & 0xFF00FF;
|
||||||
|
result.{{fieldName}} = ({{fieldName}}_temp << 16) | ({{fieldName}}_temp >> 16);
|
||||||
|
""",
|
||||||
|
|
||||||
|
"long" or "Int64" => $$"""
|
||||||
|
var {{fieldName}}_temp = result.{{fieldName}};
|
||||||
|
{{fieldName}}_temp = ({{fieldName}}_temp & 0x00000000FFFFFFFF) << 32 | (long)(((ulong){{fieldName}}_temp & 0xFFFFFFFF00000000) >> 32);
|
||||||
|
{{fieldName}}_temp = ({{fieldName}}_temp & 0x0000FFFF0000FFFF) << 16 | (long)(((ulong){{fieldName}}_temp & 0xFFFF0000FFFF0000) >> 16);
|
||||||
|
result.{{fieldName}} = ({{fieldName}}_temp & 0x00FF00FF00FF00FF) << 8 | (long)(((ulong){{fieldName}}_temp & 0xFF00FF00FF00FF00) >> 8);
|
||||||
|
""",
|
||||||
|
|
||||||
|
"ulong" or "UInt64" => $$"""
|
||||||
|
var {{fieldName}}_temp = result.{{fieldName}};
|
||||||
|
{{fieldName}}_temp = ({{fieldName}}_temp & 0x00000000FFFFFFFF) << 32 | ({{fieldName}}_temp & 0xFFFFFFFF00000000) >> 32;
|
||||||
|
{{fieldName}}_temp = ({{fieldName}}_temp & 0x0000FFFF0000FFFF) << 16 | ({{fieldName}}_temp & 0xFFFF0000FFFF0000) >> 16;
|
||||||
|
result.{{fieldName}} = ({{fieldName}}_temp & 0x00FF00FF00FF00FF) << 8 | ({{fieldName}}_temp & 0xFF00FF00FF00FF00) >> 8;
|
||||||
|
""",
|
||||||
|
|
||||||
|
"float" or "Single" => $$"""
|
||||||
|
{
|
||||||
|
var bytes = BitConverter.GetBytes(result.{{fieldName}});
|
||||||
|
Array.Reverse(bytes);
|
||||||
|
result.{{fieldName}} = BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
|
||||||
|
"double" or "Double" => $$"""
|
||||||
|
{
|
||||||
|
var bytes = BitConverter.GetBytes(result.{{fieldName}});
|
||||||
|
Array.Reverse(bytes);
|
||||||
|
result.{{fieldName}} = BitConverter.ToDouble(bytes, 0);
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
|
||||||
|
"byte" or "Byte" or "sbyte" or "SByte" =>
|
||||||
|
$" // {fieldName} - no swap needed for byte types",
|
||||||
|
|
||||||
|
"Guid" => $" // TODO: Implement GUID swap for {fieldName}",
|
||||||
|
|
||||||
|
_ when typeName.Contains("[") => $" // TODO: Implement array swap for {fieldName}",
|
||||||
|
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if(code != null) return (code, false);
|
||||||
|
|
||||||
|
// Use semantic information to determine if it's an enum or struct
|
||||||
|
if(typeSymbol == null) return ($" result.{fieldName} = result.{fieldName}.SwapEndian();", false);
|
||||||
|
|
||||||
|
if(typeSymbol.TypeKind == TypeKind.Enum)
|
||||||
|
{
|
||||||
|
// It's an enum - swap using runtime helper that will determine underlying type
|
||||||
|
return ($$"""
|
||||||
|
result.{{fieldName}} = SwapEnumValue(result.{{fieldName}});
|
||||||
|
""", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeSymbol.TypeKind == TypeKind.Struct
|
||||||
|
?
|
||||||
|
|
||||||
|
// It's a struct - call .SwapEndian()
|
||||||
|
($" result.{fieldName} = result.{fieldName}.SwapEndian();", false)
|
||||||
|
:
|
||||||
|
|
||||||
|
// Fallback to assuming it's a nested struct
|
||||||
|
($" result.{fieldName} = result.{fieldName}.SwapEndian();", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to generate at the struct level for enum swapping only
|
||||||
|
private static string GenerateSwapEnumValueHelper() => """
|
||||||
|
|
||||||
|
private static T SwapEnumValue<T>(T value) where T : struct
|
||||||
|
{
|
||||||
|
var type = typeof(T);
|
||||||
|
|
||||||
|
// Handle enums by converting to underlying type
|
||||||
|
if (type.IsEnum)
|
||||||
|
{
|
||||||
|
var underlyingType = Enum.GetUnderlyingType(type);
|
||||||
|
|
||||||
|
if (underlyingType == typeof(short))
|
||||||
|
{
|
||||||
|
var v = (short)(object)value;
|
||||||
|
v = (short)((v << 8) | ((v >> 8) & 0xFF));
|
||||||
|
return (T)(object)v;
|
||||||
|
}
|
||||||
|
if (underlyingType == typeof(ushort))
|
||||||
|
{
|
||||||
|
var v = (ushort)(object)value;
|
||||||
|
v = (ushort)((v << 8) | (v >> 8));
|
||||||
|
return (T)(object)v;
|
||||||
|
}
|
||||||
|
if (underlyingType == typeof(int))
|
||||||
|
{
|
||||||
|
var v = (int)(object)value;
|
||||||
|
v = (int)((v << 8) & 0xFF00FF00 | ((uint)v >> 8) & 0xFF00FF);
|
||||||
|
v = (int)(((uint)v << 16) | ((uint)v >> 16) & 0xFFFF);
|
||||||
|
return (T)(object)v;
|
||||||
|
}
|
||||||
|
if (underlyingType == typeof(uint))
|
||||||
|
{
|
||||||
|
var v = (uint)(object)value;
|
||||||
|
v = (v << 8) & 0xFF00FF00 | (v >> 8) & 0xFF00FF;
|
||||||
|
v = (v << 16) | (v >> 16);
|
||||||
|
return (T)(object)v;
|
||||||
|
}
|
||||||
|
if (underlyingType == typeof(long))
|
||||||
|
{
|
||||||
|
var v = (long)(object)value;
|
||||||
|
v = (v & 0x00000000FFFFFFFF) << 32 | (long)(((ulong)v & 0xFFFFFFFF00000000) >> 32);
|
||||||
|
v = (v & 0x0000FFFF0000FFFF) << 16 | (long)(((ulong)v & 0xFFFF0000FFFF0000) >> 16);
|
||||||
|
v = (v & 0x00FF00FF00FF00FF) << 8 | (long)(((ulong)v & 0xFF00FF00FF00FF00) >> 8);
|
||||||
|
return (T)(object)v;
|
||||||
|
}
|
||||||
|
if (underlyingType == typeof(ulong))
|
||||||
|
{
|
||||||
|
var v = (ulong)(object)value;
|
||||||
|
v = (v & 0x00000000FFFFFFFF) << 32 | (v & 0xFFFFFFFF00000000) >> 32;
|
||||||
|
v = (v & 0x0000FFFF0000FFFF) << 16 | (v & 0xFFFF0000FFFF0000) >> 16;
|
||||||
|
v = (v & 0x00FF00FF00FF00FF) << 8 | (v & 0xFF00FF00FF00FF00) >> 8;
|
||||||
|
return (T)(object)v;
|
||||||
|
}
|
||||||
|
if (underlyingType == typeof(byte) || underlyingType == typeof(sbyte))
|
||||||
|
{
|
||||||
|
return value; // No swap needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not an enum or unknown underlying type, return unchanged
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
42
Aaru.Helpers/ISwapEndian.cs
Normal file
42
Aaru.Helpers/ISwapEndian.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// /***************************************************************************
|
||||||
|
// Aaru Data Preservation Suite
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Filename : ISwapEndian.cs
|
||||||
|
// Author(s) : Natalia Portillo <claunia@claunia.com>
|
||||||
|
//
|
||||||
|
// Component : Helpers.
|
||||||
|
//
|
||||||
|
// --[ Description ] ----------------------------------------------------------
|
||||||
|
//
|
||||||
|
// Interface for structs that support endianness swapping.
|
||||||
|
//
|
||||||
|
// --[ License ] --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This library is free software; you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as
|
||||||
|
// published by the Free Software Foundation; either version 2.1 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This library is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public
|
||||||
|
// License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Copyright © 2011-2025 Natalia Portillo
|
||||||
|
// ****************************************************************************/
|
||||||
|
|
||||||
|
namespace Aaru.Helpers;
|
||||||
|
|
||||||
|
/// <summary>Interface for structs that support compile-time endianness swapping</summary>
|
||||||
|
/// <typeparam name="T">The struct type that implements this interface</typeparam>
|
||||||
|
public interface ISwapEndian<T> where T : struct
|
||||||
|
{
|
||||||
|
/// <summary>Swaps the endianness of all numeric fields in this structure</summary>
|
||||||
|
/// <returns>A new structure with swapped endianness</returns>
|
||||||
|
T SwapEndian();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user