diff --git a/Brovan.Generators/StructSerializerGenerator.cs b/Brovan.Generators/StructSerializerGenerator.cs index 69b4216..d9c8d39 100644 --- a/Brovan.Generators/StructSerializerGenerator.cs +++ b/Brovan.Generators/StructSerializerGenerator.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using Microsoft.CodeAnalysis; @@ -12,6 +13,24 @@ namespace Brovan.Generators [Generator(LanguageNames.CSharp)] public sealed class StructSerializerGenerator : IIncrementalGenerator { + private const int MaxNestingDepth = 8; + + private static readonly DiagnosticDescriptor UnsupportedFieldDiagnostic = new DiagnosticDescriptor( + id: "BROVANGEN001", + title: "Unsupported field in [GenerateStructSerializer] struct", + messageFormat: "Field '{0}' on struct '{1}' has a shape the struct serializer generator cannot marshal", + category: "Brovan.Generators", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + private static readonly DiagnosticDescriptor NestingTooDeepDiagnostic = new DiagnosticDescriptor( + id: "BROVANGEN002", + title: "Struct nesting too deep for [GenerateStructSerializer]", + messageFormat: "Struct '{0}' nests more than {1} levels deep (possible cyclic struct reference)", + category: "Brovan.Generators", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + public void Initialize(IncrementalGeneratorInitializationContext context) { var structs = context.SyntaxProvider.CreateSyntaxProvider(IsStructCandidate, GetStructSymbol); @@ -29,16 +48,45 @@ namespace Brovan.Generators return ctx.SemanticModel.GetDeclaredSymbol(ctx.Node) as INamedTypeSymbol; } + private sealed class GenContext + { + public readonly INamedTypeSymbol? InlineAttr; + public readonly INamedTypeSymbol? PtrAttr; + public readonly SourceProductionContext Ctx; + + public GenContext(INamedTypeSymbol? inlineAttr, INamedTypeSymbol? ptrAttr, SourceProductionContext ctx) + { + InlineAttr = inlineAttr; + PtrAttr = ptrAttr; + Ctx = ctx; + } + } + + private readonly struct FieldSlot + { + public readonly IFieldSymbol Field; + public readonly int Abs64; + public readonly int Abs32; + + public FieldSlot(IFieldSymbol field, int abs64, int abs32) + { + Field = field; + Abs64 = abs64; + Abs32 = abs32; + } + } + private static void Execute(SourceProductionContext ctx, (Compilation comp, ImmutableArray structs) source) { var (compilation, structSymbols) = source; - var emulatedInlineAttr = compilation.GetTypeByMetadataName("Brovan.Core.Emulation.OS.EmulatedInlineAttribute"); - var emulatedPointerAttr = compilation.GetTypeByMetadataName("Brovan.Core.Emulation.OS.EmulatedPointerAttribute"); - - if (emulatedInlineAttr == null && emulatedPointerAttr == null) + var markerAttr = compilation.GetTypeByMetadataName("Brovan.Core.Emulation.OS.GenerateStructSerializerAttribute"); + if (markerAttr == null) return; + var inlineAttr = compilation.GetTypeByMetadataName("Brovan.Core.Emulation.OS.EmulatedInlineAttribute"); + var ptrAttr = compilation.GetTypeByMetadataName("Brovan.Core.Emulation.OS.EmulatedPointerAttribute"); + var candidates = new List(); var seen = new HashSet(); @@ -46,34 +94,22 @@ namespace Brovan.Generators { if (sym == null) continue; if (!seen.Add(sym.ToDisplayString())) continue; - - bool hasSpecial = false; - foreach (var field in sym.GetMembers().OfType()) - { - foreach (var attr in field.GetAttributes()) - { - if (SymbolEquals(attr.AttributeClass, emulatedInlineAttr) || - SymbolEquals(attr.AttributeClass, emulatedPointerAttr)) - { - hasSpecial = true; - break; - } - } - if (hasSpecial) break; - } - if (hasSpecial) + if (sym.GetAttributes().Any(a => SymbolEquals(a.AttributeClass, markerAttr))) candidates.Add(sym); } if (candidates.Count == 0) return; + var gc = new GenContext(inlineAttr, ptrAttr, ctx); + var sb = new StringBuilder(); sb.AppendLine("// "); sb.AppendLine("using System;"); sb.AppendLine("using System.Text;"); sb.AppendLine("using System.Buffers;"); sb.AppendLine("using System.Buffers.Binary;"); + sb.AppendLine("using System.Runtime.CompilerServices;"); sb.AppendLine("using System.Runtime.InteropServices;"); sb.AppendLine("using Brovan.Core.Helpers;"); sb.AppendLine("using static Brovan.Core.Helpers.BinaryHelpers;"); @@ -89,379 +125,628 @@ namespace Brovan.Generators string typeName = sym.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); string safeName = sym.Name; - GenerateGetSize(sb, sym, typeName, safeName, emulatedInlineAttr, emulatedPointerAttr, compilation); - GenerateWriteStruct(sb, sym, typeName, safeName, emulatedInlineAttr, emulatedPointerAttr, compilation); - GenerateParseStruct(sb, sym, typeName, safeName, emulatedInlineAttr, emulatedPointerAttr, compilation); + GenerateGetSize(sb, gc, sym, safeName); + GenerateWriteStruct(sb, gc, sym, typeName, safeName); + GenerateParseStruct(sb, gc, sym, typeName, safeName); - if (i < candidates.Count - 1) - sb.AppendLine(); + sb.AppendLine(); } + GenerateRegistration(sb, candidates); + sb.AppendLine(" }"); sb.AppendLine("}"); ctx.AddSource("StructSerializer.Generated.cs", sb.ToString()); } - private static void GenerateGetSize(StringBuilder sb, INamedTypeSymbol sym, string typeName, string safeName, - INamedTypeSymbol? inlineAttr, INamedTypeSymbol? ptrAttr, Compilation comp) + private static void GenerateRegistration(StringBuilder sb, List candidates) { - bool is64 = true; - int size64 = ComputeSize(sym, true, inlineAttr, ptrAttr, comp); - int size32 = ComputeSize(sym, false, inlineAttr, ptrAttr, comp); - - sb.AppendLine($" public static int GetStructSizeDirect_{safeName}(bool Is64) => Is64 ? {size64} : {size32};"); + sb.AppendLine(" [ModuleInitializer]"); + sb.AppendLine(" internal static void RegisterGeneratedStructSerializers()"); + sb.AppendLine(" {"); + foreach (var sym in candidates) + { + string typeName = sym.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + string safeName = sym.Name; + sb.AppendLine($" RegisterDirect<{typeName}>("); + sb.AppendLine($" WriteStructDirect_{safeName},"); + sb.AppendLine($" ParseStructDirect_{safeName},"); + sb.AppendLine($" ParseStructDirect_{safeName},"); + sb.AppendLine($" GetStructSizeDirect_{safeName},"); + sb.AppendLine($" GetStructSizeDirect_{safeName});"); + } + sb.AppendLine(" }"); } - private static void GenerateWriteStruct(StringBuilder sb, INamedTypeSymbol sym, string typeName, string safeName, - INamedTypeSymbol? inlineAttr, INamedTypeSymbol? ptrAttr, Compilation comp) + // --- Top-level method generation --- + + private static void GenerateGetSize(StringBuilder sb, GenContext gc, INamedTypeSymbol sym, string safeName) { - sb.AppendLine($" public static WriteStructResult WriteStructDirect_{safeName}(BinaryEmulator Emulator, ulong Address, {typeName} Value)"); + int size64 = ComputeSize(sym, true, gc); + int size32 = ComputeSize(sym, false, gc); + + sb.AppendLine($" internal static int GetStructSizeDirect_{safeName}(bool Is64) => Is64 ? {size64} : {size32};"); + sb.AppendLine($" internal static int GetStructSizeDirect_{safeName}(BinaryEmulator Emulator) => GetStructSizeDirect_{safeName}(Emulator._binary.Architecture == BinaryArchitecture.x64);"); + } + + private static void GenerateWriteStruct(StringBuilder sb, GenContext gc, INamedTypeSymbol sym, string typeName, string safeName) + { + sb.AppendLine($" internal static WriteStructResult WriteStructDirect_{safeName}(BinaryEmulator Emulator, ulong Address, {typeName} Value)"); sb.AppendLine(" {"); sb.AppendLine(" bool Is64 = Emulator._binary.Architecture == BinaryArchitecture.x64;"); - sb.AppendLine(" int Size = Is64 ? GetStructSizeDirect_" + safeName + "(true) : GetStructSizeDirect_" + safeName + "(false);"); - sb.AppendLine(" if (Size <= 0) return WriteStructResult.Fail(WriteStructError.InvalidStructLayout, \"" + safeName + " has zero size\");"); + sb.AppendLine($" int Size = GetStructSizeDirect_{safeName}(Is64);"); + sb.AppendLine($" if (Size <= 0) return WriteStructResult.Fail(WriteStructError.InvalidStructLayout, \"{safeName} has zero size\");"); sb.AppendLine(" if (!Emulator.IsRegionMapped(Address, (ulong)Size))"); - sb.AppendLine(" return WriteStructResult.Fail(WriteStructError.DestinationNotMapped, $\"" + safeName + " (0x{Size:X} bytes) at 0x{Address:X} is not fully mapped\");"); + sb.AppendLine($" return WriteStructResult.Fail(WriteStructError.DestinationNotMapped, $\"{safeName} (0x{{Size:X}} bytes) at 0x{{Address:X}} is not fully mapped\");"); sb.AppendLine(" byte[] Buf = ArrayPool.Shared.Rent(Size);"); sb.AppendLine(" try"); sb.AppendLine(" {"); sb.AppendLine(" Span Dst = Buf.AsSpan(0, Size);"); sb.AppendLine(" Dst.Clear();"); - sb.AppendLine(" int Off = 0;"); + sb.AppendLine(" int Off = 0;"); - int offset64 = 0, offset32 = 0; - bool isExplicit = IsExplicitLayout(sym); - if (isExplicit) - { - sb.AppendLine(" int Off = Is64 ? " + offset64 + " : " + offset32 + ";"); - } - - foreach (var field in GetOrderedFields(sym, isExplicit)) - { - string fieldName = field.Name; - string fieldType = field.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - var (inline, ascii, ptrContent) = GetFieldAttrs(field, inlineAttr, ptrAttr); - - if (isExplicit) - { - var fieldOffsetAttr = field.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "FieldOffsetAttribute"); - int explicitOff = fieldOffsetAttr != null ? (int)fieldOffsetAttr.ConstructorArguments[0].Value! : 0; - sb.AppendLine($" Off = {explicitOff};"); - WriteFieldSerialize(sb, fieldName, fieldType, field, inline, ascii, ptrContent, "Off", comp); - } - else - { - sb.AppendLine($" Off = Is64 ? {offset64} : {offset32};"); - WriteFieldSerialize(sb, fieldName, fieldType, field, inline, ascii, ptrContent, "Off", comp); - int sz64 = GetFieldSize(field, true, inlineAttr, ptrAttr, comp); - int sz32 = GetFieldSize(field, false, inlineAttr, ptrAttr, comp); - offset64 += sz64; - offset32 += sz32; - } - } + EmitFieldsWrite(sb, gc, sym, "Value", 0, 0, 0); sb.AppendLine(" if (!Emulator.WriteMemory(Address, Dst))"); - sb.AppendLine(" return WriteStructResult.Fail(WriteStructError.DestinationNotMapped, $\"WriteMemory failed for " + safeName + " at 0x{Address:X}\");"); + sb.AppendLine($" return WriteStructResult.Fail(WriteStructError.DestinationNotMapped, $\"WriteMemory failed for {safeName} at 0x{{Address:X}}\");"); sb.AppendLine(" return WriteStructResult.Ok;"); sb.AppendLine(" }"); sb.AppendLine(" finally { ArrayPool.Shared.Return(Buf); }"); sb.AppendLine(" }"); } - private static void GenerateParseStruct(StringBuilder sb, INamedTypeSymbol sym, string typeName, string safeName, - INamedTypeSymbol? inlineAttr, INamedTypeSymbol? ptrAttr, Compilation comp) + private static void GenerateParseStruct(StringBuilder sb, GenContext gc, INamedTypeSymbol sym, string typeName, string safeName) { - sb.AppendLine($" public static bool ParseStructDirect_{safeName}(BinaryEmulator Emulator, ulong Address, out {typeName} Value)"); + sb.AppendLine($" internal static bool ParseStructDirect_{safeName}(BinaryEmulator Emulator, ulong Address, out {typeName} Value)"); sb.AppendLine(" {"); sb.AppendLine(" Value = default;"); sb.AppendLine(" bool Is64 = Emulator._binary.Architecture == BinaryArchitecture.x64;"); - sb.AppendLine(" int Size = Is64 ? GetStructSizeDirect_" + safeName + "(true) : GetStructSizeDirect_" + safeName + "(false);"); + sb.AppendLine($" int Size = GetStructSizeDirect_{safeName}(Is64);"); sb.AppendLine(" if (Size <= 0) return false;"); sb.AppendLine(" byte[] Buf = ArrayPool.Shared.Rent(Size);"); sb.AppendLine(" try"); sb.AppendLine(" {"); sb.AppendLine(" Span Span = Buf.AsSpan(0, Size);"); sb.AppendLine(" if (!Emulator.ReadMemory(Address, Span)) return false;"); - sb.AppendLine(" return ParseStructDirect_" + safeName + "(Emulator, Buf, out Value);"); + sb.AppendLine($" return ParseStructDirect_{safeName}(Emulator, Buf, out Value);"); sb.AppendLine(" }"); sb.AppendLine(" finally { ArrayPool.Shared.Return(Buf); }"); sb.AppendLine(" }"); - sb.AppendLine($" public static bool ParseStructDirect_{safeName}(BinaryEmulator Emulator, byte[] Raw, out {typeName} Value)"); + sb.AppendLine($" internal static bool ParseStructDirect_{safeName}(BinaryEmulator Emulator, byte[] Raw, out {typeName} Value)"); sb.AppendLine(" {"); sb.AppendLine(" Value = default;"); sb.AppendLine(" bool Is64 = Emulator._binary.Architecture == BinaryArchitecture.x64;"); + sb.AppendLine($" int Size = GetStructSizeDirect_{safeName}(Is64);"); + sb.AppendLine(" if (Raw == null || Raw.Length < Size) return false;"); sb.AppendLine(" int Off = 0;"); - int offset64 = 0, offset32 = 0; - bool isExplicit = IsExplicitLayout(sym); - - foreach (var field in GetOrderedFields(sym, isExplicit)) - { - string fieldName = field.Name; - string fieldType = field.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); - var (inline, ascii, ptrContent) = GetFieldAttrs(field, inlineAttr, ptrAttr); - - if (isExplicit) - { - var fieldOffsetAttr = field.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "FieldOffsetAttribute"); - int explicitOff = fieldOffsetAttr != null ? (int)fieldOffsetAttr.ConstructorArguments[0].Value! : 0; - sb.AppendLine($" int Off = {explicitOff};"); - WriteFieldDeserialize(sb, fieldName, fieldType, field, inline, ascii, ptrContent, "Off", comp); - } - else - { - sb.AppendLine($" Off = Is64 ? {offset64} : {offset32};"); - WriteFieldDeserialize(sb, fieldName, fieldType, field, inline, ascii, ptrContent, "Off", comp); - int sz64 = GetFieldSize(field, true, inlineAttr, ptrAttr, comp); - int sz32 = GetFieldSize(field, false, inlineAttr, ptrAttr, comp); - offset64 += sz64; - offset32 += sz32; - } - } + EmitFieldsRead(sb, gc, sym, "Value", 0, 0, 0); sb.AppendLine(" return true;"); sb.AppendLine(" }"); } - private static void WriteFieldSerialize(StringBuilder sb, string fieldName, string fieldType, IFieldSymbol field, - int inlineSize, bool ascii, object? ptrContent, string offVar, Compilation comp) + // --- Recursive field offset walking --- + + private static IEnumerable EnumerateFieldSlots(INamedTypeSymbol structType, int base64, int base32, GenContext gc) { - if (field.Type.SpecialType != SpecialType.None && field.Type.SpecialType != SpecialType.System_Boolean) - { - WritePrimitiveSerialize(sb, fieldName, fieldType, offVar); - return; - } + bool isExplicit = IsExplicitLayout(structType); + int local64 = 0, local32 = 0; - if (field.Type.TypeKind == TypeKind.Enum) + foreach (var field in GetOrderedFields(structType, isExplicit)) { - var underlying = ((INamedTypeSymbol)field.Type).EnumUnderlyingType!; - WriteEnumSerialize(sb, fieldName, underlying.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), offVar); - return; - } - - if (fieldType == "bool") - { - sb.AppendLine($" Dst[{offVar}] = (byte)(Value.{fieldName} ? 1 : 0);"); - return; - } - - if (fieldType == "string") - { - if (inlineSize > 0) + int rel64, rel32; + if (isExplicit) { - string enc = ascii ? "Encoding.ASCII" : "Encoding.Unicode"; - sb.AppendLine($" {{"); - sb.AppendLine($" string S = Value.{fieldName} ?? \"\";"); - sb.AppendLine($" byte[] Enc = {enc}.GetBytes(S);"); - sb.AppendLine($" int Copy = Math.Min(Enc.Length, {inlineSize});"); - sb.AppendLine($" Enc.AsSpan(0, Copy).CopyTo(Dst.Slice({offVar}, Copy));"); - sb.AppendLine($" }}"); - } - else if (ptrContent != null) - { - bool isAscii = ptrContent.ToString() == "AsciiString"; - string enc = isAscii ? "Encoding.ASCII" : "Encoding.Unicode"; - sb.AppendLine($" {{"); - sb.AppendLine($" ulong StrPtr = 0;"); - sb.AppendLine($" string? S = Value.{fieldName};"); - sb.AppendLine($" if (S != null)"); - sb.AppendLine($" {{"); - sb.AppendLine($" byte[] Enc = {enc}.GetBytes(S);"); - sb.AppendLine($" byte[] Full = new byte[Enc.Length + ({(isAscii ? "1" : "2")})];"); - sb.AppendLine($" Buffer.BlockCopy(Enc, 0, Full, 0, Enc.Length);"); - sb.AppendLine($" StrPtr = Emulator.MapUniqueAddress((ulong)Full.Length, MemoryProtection.ReadWrite);"); - sb.AppendLine($" if (StrPtr == 0) return WriteStructResult.Fail(WriteStructError.StringAllocationFailed, $\"Alloc failed for {fieldName}\");"); - sb.AppendLine($" if (!Emulator.WriteMemory(StrPtr, Full)) return WriteStructResult.Fail(WriteStructError.PointerDestinationNotMapped, $\"WriteMemory failed for {fieldName}\");"); - sb.AppendLine($" }}"); - sb.AppendLine($" if (Is64) BinaryPrimitives.WriteUInt64LittleEndian(Dst.Slice({offVar}, 8), StrPtr);"); - sb.AppendLine($" else BinaryPrimitives.WriteUInt32LittleEndian(Dst.Slice({offVar}, 4), (uint)StrPtr);"); - sb.AppendLine($" }}"); - } - return; - } - - if (fieldType == "byte[]") - { - if (inlineSize > 0) - { - sb.AppendLine($" {{"); - sb.AppendLine($" byte[]? Src = Value.{fieldName};"); - sb.AppendLine($" if (Src != null) {{ int Copy = Math.Min(Src.Length, {inlineSize}); Src.AsSpan(0, Copy).CopyTo(Dst.Slice({offVar}, Copy)); }}"); - sb.AppendLine($" }}"); + int off = GetExplicitFieldOffset(field); + rel64 = off; + rel32 = off; } else { - sb.AppendLine($" {{"); - sb.AppendLine($" ulong BytePtr = 0;"); - sb.AppendLine($" byte[]? RawB = Value.{fieldName};"); - sb.AppendLine($" if (RawB != null && RawB.Length > 0)"); - sb.AppendLine($" {{"); - sb.AppendLine($" BytePtr = Emulator.MapUniqueAddress((ulong)RawB.Length, MemoryProtection.ReadWrite);"); - sb.AppendLine($" if (BytePtr == 0) return WriteStructResult.Fail(WriteStructError.PointerAllocationFailed, $\"Alloc failed for {fieldName}\");"); - sb.AppendLine($" if (!Emulator.WriteMemory(BytePtr, RawB)) return WriteStructResult.Fail(WriteStructError.PointerDestinationNotMapped, $\"WriteMemory failed for {fieldName}\");"); - sb.AppendLine($" }}"); - sb.AppendLine($" if (Is64) BinaryPrimitives.WriteUInt64LittleEndian(Dst.Slice({offVar}, 8), BytePtr);"); - sb.AppendLine($" else BinaryPrimitives.WriteUInt32LittleEndian(Dst.Slice({offVar}, 4), (uint)BytePtr);"); - sb.AppendLine($" }}"); + rel64 = local64; + rel32 = local32; } - return; - } - if (field.Type.IsValueType && !IsPrimitiveType(field.Type)) - { - sb.AppendLine($" {{"); - sb.AppendLine($" {fieldType} Nested = Value.{fieldName};"); - sb.AppendLine($" int NestedSize = Is64 ? GetStructSizeDirect_{((INamedTypeSymbol)field.Type).Name}(true) : GetStructSizeDirect_{((INamedTypeSymbol)field.Type).Name}(false);"); - sb.AppendLine($" Span NestedSpan = Dst.Slice({offVar}, NestedSize);"); - sb.AppendLine($" if (!WriteStructNested_{((INamedTypeSymbol)field.Type).Name}(NestedSpan, Nested, Is64))"); - sb.AppendLine($" return WriteStructResult.Fail(WriteStructError.UnsupportedFieldType, $\"Failed to serialize nested {fieldName}\");"); - sb.AppendLine($" }}"); - return; + yield return new FieldSlot(field, base64 + rel64, base32 + rel32); + + if (!isExplicit) + { + local64 += GetFieldSize(field, true, gc); + local32 += GetFieldSize(field, false, gc); + } } } - private static void WriteFieldDeserialize(StringBuilder sb, string fieldName, string fieldType, IFieldSymbol field, - int inlineSize, bool ascii, object? ptrContent, string offVar, Compilation comp) + private static void EmitFieldsWrite(StringBuilder sb, GenContext gc, INamedTypeSymbol structType, string valuePath, int base64, int base32, int depth) { - if (field.Type.SpecialType != SpecialType.None && field.Type.SpecialType != SpecialType.System_Boolean) + if (depth > MaxNestingDepth) { - WritePrimitiveDeserialize(sb, fieldName, fieldType, offVar); + ReportNestingTooDeep(gc, structType); return; } - if (field.Type.TypeKind == TypeKind.Enum) + foreach (var slot in EnumerateFieldSlots(structType, base64, base32, gc)) + EmitFieldWrite(sb, gc, slot.Field, $"{valuePath}.{slot.Field.Name}", slot.Abs64, slot.Abs32, depth); + } + + private static void EmitFieldsRead(StringBuilder sb, GenContext gc, INamedTypeSymbol structType, string valuePath, int base64, int base32, int depth) + { + if (depth > MaxNestingDepth) { - var underlying = ((INamedTypeSymbol)field.Type).EnumUnderlyingType!; - WriteEnumDeserialize(sb, fieldName, fieldType, underlying.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), offVar); + ReportNestingTooDeep(gc, structType); return; } - if (fieldType == "bool") + foreach (var slot in EnumerateFieldSlots(structType, base64, base32, gc)) + EmitFieldRead(sb, gc, slot.Field, $"{valuePath}.{slot.Field.Name}", slot.Abs64, slot.Abs32, depth); + } + + // --- Per-field dispatch --- + + private static void EmitOffsetAssign(StringBuilder sb, int abs64, int abs32) + { + sb.AppendLine(abs64 == abs32 + ? $" Off = {abs64};" + : $" Off = Is64 ? {abs64} : {abs32};"); + } + + private static void EmitFieldWrite(StringBuilder sb, GenContext gc, IFieldSymbol field, string accessExpr, int abs64, int abs32, int depth) + { + var ft = field.Type; + var (inlineSize, ascii, ptrContent) = GetFieldAttrs(field, gc); + + if (ft is IArrayTypeSymbol arrSym) { - sb.AppendLine($" Value.{fieldName} = Raw[{offVar}] != 0;"); + EmitArrayFieldWrite(sb, gc, field, arrSym, accessExpr, abs64, abs32, inlineSize, depth); return; } - if (fieldType == "string") + if (ft.TypeKind == TypeKind.Enum) { + EmitOffsetAssign(sb, abs64, abs32); + var underlying = ((INamedTypeSymbol)ft).EnumUnderlyingType!; + EmitEnumLeafWrite(sb, accessExpr, underlying.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "Off"); + return; + } + + if (ft.SpecialType == SpecialType.System_Boolean) + { + EmitOffsetAssign(sb, abs64, abs32); + sb.AppendLine($" Dst[Off] = (byte)({accessExpr} ? 1 : 0);"); + return; + } + + if (ft.SpecialType == SpecialType.System_String) + { + if (inlineSize <= 0 && ptrContent == null) + { + ReportUnsupported(gc, field); + return; + } + EmitOffsetAssign(sb, abs64, abs32); + EmitStringLeafWrite(sb, accessExpr, inlineSize, ascii, ptrContent, "Off"); + return; + } + + if (IsNumericPrimitive(ft)) + { + EmitOffsetAssign(sb, abs64, abs32); + EmitPrimitiveLeafWrite(sb, accessExpr, ft.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "Off"); + return; + } + + if (ft.IsValueType && !IsPrimitiveType(ft)) + { + if (ptrContent?.ToString() == "Struct") + { + ReportUnsupported(gc, field); + return; + } + EmitFieldsWrite(sb, gc, (INamedTypeSymbol)ft, accessExpr, abs64, abs32, depth + 1); + return; + } + + ReportUnsupported(gc, field); + } + + private static void EmitFieldRead(StringBuilder sb, GenContext gc, IFieldSymbol field, string accessExpr, int abs64, int abs32, int depth) + { + var ft = field.Type; + var (inlineSize, ascii, ptrContent) = GetFieldAttrs(field, gc); + + if (ft is IArrayTypeSymbol arrSym) + { + EmitArrayFieldRead(sb, gc, field, arrSym, accessExpr, abs64, abs32, inlineSize, depth); + return; + } + + if (ft.TypeKind == TypeKind.Enum) + { + EmitOffsetAssign(sb, abs64, abs32); + var underlying = ((INamedTypeSymbol)ft).EnumUnderlyingType!; + EmitEnumLeafRead(sb, accessExpr, ft.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), underlying.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "Off"); + return; + } + + if (ft.SpecialType == SpecialType.System_Boolean) + { + EmitOffsetAssign(sb, abs64, abs32); + sb.AppendLine($" {accessExpr} = Raw[Off] != 0;"); + return; + } + + if (ft.SpecialType == SpecialType.System_String) + { + if (inlineSize <= 0 && ptrContent == null) + { + ReportUnsupported(gc, field); + return; + } + EmitOffsetAssign(sb, abs64, abs32); + EmitStringLeafRead(sb, accessExpr, inlineSize, ascii, ptrContent, "Off"); + return; + } + + if (IsNumericPrimitive(ft)) + { + EmitOffsetAssign(sb, abs64, abs32); + EmitPrimitiveLeafRead(sb, accessExpr, ft.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "Off"); + return; + } + + if (ft.IsValueType && !IsPrimitiveType(ft)) + { + if (ptrContent?.ToString() == "Struct") + { + ReportUnsupported(gc, field); + return; + } + EmitFieldsRead(sb, gc, (INamedTypeSymbol)ft, accessExpr, abs64, abs32, depth + 1); + return; + } + + ReportUnsupported(gc, field); + } + + // --- Inline arrays (byte[] pointer/inline, and inline arrays of primitives/enums/structs) --- + + private static void EmitArrayFieldWrite(StringBuilder sb, GenContext gc, IFieldSymbol field, IArrayTypeSymbol arrSym, string accessExpr, int abs64, int abs32, int inlineSize, int depth) + { + var elem = arrSym.ElementType; + + if (elem.SpecialType == SpecialType.System_Byte) + { + EmitOffsetAssign(sb, abs64, abs32); if (inlineSize > 0) { - string enc = ascii ? "Encoding.ASCII" : "Encoding.Unicode"; - int step = ascii ? 1 : 2; - sb.AppendLine($" {{"); - sb.AppendLine($" int Len = {inlineSize};"); - sb.AppendLine($" while (Len >= {step} && Raw[{offVar} + Len - {step}] == 0{(step == 2 ? " && Raw[" + offVar + " + Len - 1] == 0" : "")}) Len -= {step};"); - sb.AppendLine($" Value.{fieldName} = {enc}.GetString(Raw, {offVar}, Len);"); - sb.AppendLine($" }}"); + sb.AppendLine(" {"); + sb.AppendLine($" byte[]? Src = {accessExpr};"); + sb.AppendLine($" if (Src != null) {{ int Copy = Math.Min(Src.Length, {inlineSize}); Src.AsSpan(0, Copy).CopyTo(Dst.Slice(Off, Copy)); }}"); + sb.AppendLine(" }"); } - else if (ptrContent != null) + else { - bool isAscii = ptrContent.ToString() == "AsciiString"; - string enc = isAscii ? "Encoding.ASCII" : "Encoding.Unicode"; - sb.AppendLine($" {{"); - sb.AppendLine($" ulong StrP = Is64 ? BitConverter.ToUInt64(Raw, {offVar}) : BitConverter.ToUInt32(Raw, {offVar});"); - sb.AppendLine($" Value.{fieldName} = StrP != 0 ? ReadNullTerminatedString(Emulator, StrP, {enc}) : null;"); - sb.AppendLine($" }}"); + sb.AppendLine(" {"); + sb.AppendLine(" ulong BytePtr = 0;"); + sb.AppendLine($" byte[]? RawB = {accessExpr};"); + sb.AppendLine(" if (RawB != null && RawB.Length > 0)"); + sb.AppendLine(" {"); + sb.AppendLine(" BytePtr = Emulator.MapUniqueAddress((ulong)RawB.Length, MemoryProtection.ReadWrite);"); + sb.AppendLine($" if (BytePtr == 0) return WriteStructResult.Fail(WriteStructError.PointerAllocationFailed, \"Alloc failed for {field.Name}\");"); + sb.AppendLine($" if (!Emulator.WriteMemory(BytePtr, RawB)) return WriteStructResult.Fail(WriteStructError.PointerDestinationNotMapped, \"WriteMemory failed for {field.Name}\");"); + sb.AppendLine(" }"); + sb.AppendLine(" if (Is64) BinaryPrimitives.WriteUInt64LittleEndian(Dst.Slice(Off, 8), BytePtr);"); + sb.AppendLine(" else BinaryPrimitives.WriteUInt32LittleEndian(Dst.Slice(Off, 4), (uint)BytePtr);"); + sb.AppendLine(" }"); } return; } - if (fieldType == "byte[]") + if (inlineSize <= 0) { + ReportUnsupported(gc, field); + return; + } + + int elemSize64 = GetElementTypeSize(elem, true, gc); + int elemSize32 = GetElementTypeSize(elem, false, gc); + if (elemSize64 <= 0 || elemSize32 <= 0) + { + ReportUnsupported(gc, field); + return; + } + + string elemTypeDisplay = elem.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + for (int i = 0; i < inlineSize; i++) + { + int e64 = abs64 + i * elemSize64; + int e32 = abs32 + i * elemSize32; + string local = $"Elem_{field.Name}_{depth}_{i}"; + + sb.AppendLine($" {elemTypeDisplay} {local} = ({accessExpr} != null && {accessExpr}.Length > {i}) ? {accessExpr}[{i}] : default;"); + + if (elem.TypeKind == TypeKind.Enum) + { + EmitOffsetAssign(sb, e64, e32); + var underlying = ((INamedTypeSymbol)elem).EnumUnderlyingType!; + EmitEnumLeafWrite(sb, local, underlying.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "Off"); + } + else if (elem.SpecialType == SpecialType.System_Boolean) + { + EmitOffsetAssign(sb, e64, e32); + sb.AppendLine($" Dst[Off] = (byte)({local} ? 1 : 0);"); + } + else if (IsNumericPrimitive(elem)) + { + EmitOffsetAssign(sb, e64, e32); + EmitPrimitiveLeafWrite(sb, local, elem.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "Off"); + } + else if (elem.IsValueType && !IsPrimitiveType(elem)) + { + EmitFieldsWrite(sb, gc, (INamedTypeSymbol)elem, local, e64, e32, depth + 1); + } + else + { + ReportUnsupported(gc, field); + } + } + } + + private static void EmitArrayFieldRead(StringBuilder sb, GenContext gc, IFieldSymbol field, IArrayTypeSymbol arrSym, string accessExpr, int abs64, int abs32, int inlineSize, int depth) + { + var elem = arrSym.ElementType; + + if (elem.SpecialType == SpecialType.System_Byte) + { + EmitOffsetAssign(sb, abs64, abs32); if (inlineSize > 0) { - sb.AppendLine($" {{ byte[] Inline = new byte[{inlineSize}]; Buffer.BlockCopy(Raw, {offVar}, Inline, 0, {inlineSize}); Value.{fieldName} = Inline; }}"); + sb.AppendLine(" {"); + sb.AppendLine($" byte[] Inline = new byte[{inlineSize}];"); + sb.AppendLine($" Buffer.BlockCopy(Raw, Off, Inline, 0, {inlineSize});"); + sb.AppendLine($" {accessExpr} = Inline;"); + sb.AppendLine(" }"); + } + else + { + sb.AppendLine($" {accessExpr} = null;"); } return; } - } - private static void WritePrimitiveSerialize(StringBuilder sb, string fieldName, string fieldType, string offVar) - { - switch (fieldType) + if (inlineSize <= 0) { - case "byte": sb.AppendLine($" Dst[{offVar}] = Value.{fieldName};"); break; - case "sbyte": sb.AppendLine($" Dst[{offVar}] = (byte)Value.{fieldName};"); break; - case "ushort": sb.AppendLine($" BinaryPrimitives.WriteUInt16LittleEndian(Dst.Slice({offVar}, 2), Value.{fieldName});"); break; - case "short": sb.AppendLine($" BinaryPrimitives.WriteInt16LittleEndian(Dst.Slice({offVar}, 2), Value.{fieldName});"); break; - case "uint": sb.AppendLine($" BinaryPrimitives.WriteUInt32LittleEndian(Dst.Slice({offVar}, 4), Value.{fieldName});"); break; - case "int": sb.AppendLine($" BinaryPrimitives.WriteInt32LittleEndian(Dst.Slice({offVar}, 4), Value.{fieldName});"); break; - case "ulong": sb.AppendLine($" BinaryPrimitives.WriteUInt64LittleEndian(Dst.Slice({offVar}, 8), Value.{fieldName};"); break; - case "long": sb.AppendLine($" BinaryPrimitives.WriteInt64LittleEndian(Dst.Slice({offVar}, 8), Value.{fieldName});"); break; - case "float": sb.AppendLine($" BinaryPrimitives.WriteSingleLittleEndian(Dst.Slice({offVar}, 4), Value.{fieldName});"); break; - case "double": sb.AppendLine($" BinaryPrimitives.WriteDoubleLittleEndian(Dst.Slice({offVar}, 8), Value.{fieldName});"); break; - default: sb.AppendLine($" // Unsupported: {fieldType} {fieldName}"); break; + ReportUnsupported(gc, field); + return; + } + + int elemSize64 = GetElementTypeSize(elem, true, gc); + int elemSize32 = GetElementTypeSize(elem, false, gc); + if (elemSize64 <= 0 || elemSize32 <= 0) + { + ReportUnsupported(gc, field); + return; + } + + string elemTypeDisplay = elem.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + sb.AppendLine($" {accessExpr} = new {elemTypeDisplay}[{inlineSize}];"); + + for (int i = 0; i < inlineSize; i++) + { + int e64 = abs64 + i * elemSize64; + int e32 = abs32 + i * elemSize32; + string elemAccess = $"{accessExpr}[{i}]"; + + if (elem.TypeKind == TypeKind.Enum) + { + EmitOffsetAssign(sb, e64, e32); + var underlying = ((INamedTypeSymbol)elem).EnumUnderlyingType!; + EmitEnumLeafRead(sb, elemAccess, elemTypeDisplay, underlying.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "Off"); + } + else if (elem.SpecialType == SpecialType.System_Boolean) + { + EmitOffsetAssign(sb, e64, e32); + sb.AppendLine($" {elemAccess} = Raw[Off] != 0;"); + } + else if (IsNumericPrimitive(elem)) + { + EmitOffsetAssign(sb, e64, e32); + EmitPrimitiveLeafRead(sb, elemAccess, elem.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "Off"); + } + else if (elem.IsValueType && !IsPrimitiveType(elem)) + { + EmitFieldsRead(sb, gc, (INamedTypeSymbol)elem, elemAccess, e64, e32, depth + 1); + } + else + { + ReportUnsupported(gc, field); + } } } - private static void WritePrimitiveDeserialize(StringBuilder sb, string fieldName, string fieldType, string offVar) + // --- Leaf emitters --- + + private static void EmitPrimitiveLeafWrite(StringBuilder sb, string accessExpr, string typeDisplay, string offVar) { - switch (fieldType) + switch (typeDisplay) { - case "byte": sb.AppendLine($" Value.{fieldName} = Raw[{offVar}];"); break; - case "sbyte": sb.AppendLine($" Value.{fieldName} = (sbyte)Raw[{offVar}];"); break; - case "ushort": sb.AppendLine($" Value.{fieldName} = BitConverter.ToUInt16(Raw, {offVar});"); break; - case "short": sb.AppendLine($" Value.{fieldName} = BitConverter.ToInt16(Raw, {offVar});"); break; - case "uint": sb.AppendLine($" Value.{fieldName} = BitConverter.ToUInt32(Raw, {offVar});"); break; - case "int": sb.AppendLine($" Value.{fieldName} = BitConverter.ToInt32(Raw, {offVar});"); break; - case "ulong": sb.AppendLine($" Value.{fieldName} = BitConverter.ToUInt64(Raw, {offVar});"); break; - case "long": sb.AppendLine($" Value.{fieldName} = BitConverter.ToInt64(Raw, {offVar});"); break; - case "float": sb.AppendLine($" Value.{fieldName} = BitConverter.ToSingle(Raw, {offVar});"); break; - case "double": sb.AppendLine($" Value.{fieldName} = BitConverter.ToDouble(Raw, {offVar});"); break; - default: sb.AppendLine($" // Unsupported: {fieldType} {fieldName}"); break; + case "byte": sb.AppendLine($" Dst[{offVar}] = {accessExpr};"); break; + case "sbyte": sb.AppendLine($" Dst[{offVar}] = (byte){accessExpr};"); break; + case "ushort": sb.AppendLine($" BinaryPrimitives.WriteUInt16LittleEndian(Dst.Slice({offVar}, 2), {accessExpr});"); break; + case "short": sb.AppendLine($" BinaryPrimitives.WriteInt16LittleEndian(Dst.Slice({offVar}, 2), {accessExpr});"); break; + case "uint": sb.AppendLine($" BinaryPrimitives.WriteUInt32LittleEndian(Dst.Slice({offVar}, 4), {accessExpr});"); break; + case "int": sb.AppendLine($" BinaryPrimitives.WriteInt32LittleEndian(Dst.Slice({offVar}, 4), {accessExpr});"); break; + case "ulong": sb.AppendLine($" BinaryPrimitives.WriteUInt64LittleEndian(Dst.Slice({offVar}, 8), {accessExpr});"); break; + case "long": sb.AppendLine($" BinaryPrimitives.WriteInt64LittleEndian(Dst.Slice({offVar}, 8), {accessExpr});"); break; + case "float": sb.AppendLine($" BinaryPrimitives.WriteSingleLittleEndian(Dst.Slice({offVar}, 4), {accessExpr});"); break; + case "double": sb.AppendLine($" BinaryPrimitives.WriteDoubleLittleEndian(Dst.Slice({offVar}, 8), {accessExpr});"); break; } } - private static void WriteEnumSerialize(StringBuilder sb, string fieldName, string underlying, string offVar) + private static void EmitPrimitiveLeafRead(StringBuilder sb, string accessExpr, string typeDisplay, string offVar) { - if (underlying == "byte") sb.AppendLine($" Dst[{offVar}] = (byte)Value.{fieldName};"); - else if (underlying == "sbyte") sb.AppendLine($" Dst[{offVar}] = (byte)Value.{fieldName};"); - else if (underlying == "ushort") sb.AppendLine($" BinaryPrimitives.WriteUInt16LittleEndian(Dst.Slice({offVar}, 2), (ushort)Value.{fieldName});"); - else if (underlying == "short") sb.AppendLine($" BinaryPrimitives.WriteInt16LittleEndian(Dst.Slice({offVar}, 2), (short)Value.{fieldName});"); - else if (underlying == "uint") sb.AppendLine($" BinaryPrimitives.WriteUInt32LittleEndian(Dst.Slice({offVar}, 4), (uint)Value.{fieldName});"); - else if (underlying == "int") sb.AppendLine($" BinaryPrimitives.WriteInt32LittleEndian(Dst.Slice({offVar}, 4), (int)Value.{fieldName};"); - else if (underlying == "ulong") sb.AppendLine($" BinaryPrimitives.WriteUInt64LittleEndian(Dst.Slice({offVar}, 8), (ulong)Value.{fieldName};"); - else if (underlying == "long") sb.AppendLine($" BinaryPrimitives.WriteInt64LittleEndian(Dst.Slice({offVar}, 8), (long)Value.{fieldName});"); + switch (typeDisplay) + { + case "byte": sb.AppendLine($" {accessExpr} = Raw[{offVar}];"); break; + case "sbyte": sb.AppendLine($" {accessExpr} = (sbyte)Raw[{offVar}];"); break; + case "ushort": sb.AppendLine($" {accessExpr} = BitConverter.ToUInt16(Raw, {offVar});"); break; + case "short": sb.AppendLine($" {accessExpr} = BitConverter.ToInt16(Raw, {offVar});"); break; + case "uint": sb.AppendLine($" {accessExpr} = BitConverter.ToUInt32(Raw, {offVar});"); break; + case "int": sb.AppendLine($" {accessExpr} = BitConverter.ToInt32(Raw, {offVar});"); break; + case "ulong": sb.AppendLine($" {accessExpr} = BitConverter.ToUInt64(Raw, {offVar});"); break; + case "long": sb.AppendLine($" {accessExpr} = BitConverter.ToInt64(Raw, {offVar});"); break; + case "float": sb.AppendLine($" {accessExpr} = BitConverter.ToSingle(Raw, {offVar});"); break; + case "double": sb.AppendLine($" {accessExpr} = BitConverter.ToDouble(Raw, {offVar});"); break; + } } - private static void WriteEnumDeserialize(StringBuilder sb, string fieldName, string fieldType, string underlying, string offVar) + private static void EmitEnumLeafWrite(StringBuilder sb, string accessExpr, string underlying, string offVar) { - if (underlying == "byte") sb.AppendLine($" Value.{fieldName} = ({fieldType})Raw[{offVar}];"); - else if (underlying == "sbyte") sb.AppendLine($" Value.{fieldName} = ({fieldType})(sbyte)Raw[{offVar}];"); - else if (underlying == "ushort") sb.AppendLine($" Value.{fieldName} = ({fieldType})BitConverter.ToUInt16(Raw, {offVar});"); - else if (underlying == "short") sb.AppendLine($" Value.{fieldName} = ({fieldType})BitConverter.ToInt16(Raw, {offVar});"); - else if (underlying == "uint") sb.AppendLine($" Value.{fieldName} = ({fieldType})BitConverter.ToUInt32(Raw, {offVar});"); - else if (underlying == "int") sb.AppendLine($" Value.{fieldName} = ({fieldType})BitConverter.ToInt32(Raw, {offVar});"); - else if (underlying == "ulong") sb.AppendLine($" Value.{fieldName} = ({fieldType})BitConverter.ToUInt64(Raw, {offVar});"); - else if (underlying == "long") sb.AppendLine($" Value.{fieldName} = ({fieldType})BitConverter.ToInt64(Raw, {offVar});"); + switch (underlying) + { + case "byte": sb.AppendLine($" Dst[{offVar}] = (byte){accessExpr};"); break; + case "sbyte": sb.AppendLine($" Dst[{offVar}] = (byte){accessExpr};"); break; + case "ushort": sb.AppendLine($" BinaryPrimitives.WriteUInt16LittleEndian(Dst.Slice({offVar}, 2), (ushort){accessExpr});"); break; + case "short": sb.AppendLine($" BinaryPrimitives.WriteInt16LittleEndian(Dst.Slice({offVar}, 2), (short){accessExpr});"); break; + case "uint": sb.AppendLine($" BinaryPrimitives.WriteUInt32LittleEndian(Dst.Slice({offVar}, 4), (uint){accessExpr});"); break; + case "int": sb.AppendLine($" BinaryPrimitives.WriteInt32LittleEndian(Dst.Slice({offVar}, 4), (int){accessExpr});"); break; + case "ulong": sb.AppendLine($" BinaryPrimitives.WriteUInt64LittleEndian(Dst.Slice({offVar}, 8), (ulong){accessExpr});"); break; + case "long": sb.AppendLine($" BinaryPrimitives.WriteInt64LittleEndian(Dst.Slice({offVar}, 8), (long){accessExpr});"); break; + } } + private static void EmitEnumLeafRead(StringBuilder sb, string accessExpr, string enumTypeDisplay, string underlying, string offVar) + { + switch (underlying) + { + case "byte": sb.AppendLine($" {accessExpr} = ({enumTypeDisplay})Raw[{offVar}];"); break; + case "sbyte": sb.AppendLine($" {accessExpr} = ({enumTypeDisplay})(sbyte)Raw[{offVar}];"); break; + case "ushort": sb.AppendLine($" {accessExpr} = ({enumTypeDisplay})BitConverter.ToUInt16(Raw, {offVar});"); break; + case "short": sb.AppendLine($" {accessExpr} = ({enumTypeDisplay})BitConverter.ToInt16(Raw, {offVar});"); break; + case "uint": sb.AppendLine($" {accessExpr} = ({enumTypeDisplay})BitConverter.ToUInt32(Raw, {offVar});"); break; + case "int": sb.AppendLine($" {accessExpr} = ({enumTypeDisplay})BitConverter.ToInt32(Raw, {offVar});"); break; + case "ulong": sb.AppendLine($" {accessExpr} = ({enumTypeDisplay})BitConverter.ToUInt64(Raw, {offVar});"); break; + case "long": sb.AppendLine($" {accessExpr} = ({enumTypeDisplay})BitConverter.ToInt64(Raw, {offVar});"); break; + } + } + + private static void EmitStringLeafWrite(StringBuilder sb, string accessExpr, int inlineSize, bool ascii, object? ptrContent, string offVar) + { + if (inlineSize > 0) + { + string enc = ascii ? "Encoding.ASCII" : "Encoding.Unicode"; + sb.AppendLine(" {"); + sb.AppendLine($" string S = {accessExpr} ?? \"\";"); + sb.AppendLine($" byte[] Enc = {enc}.GetBytes(S);"); + sb.AppendLine($" int Copy = Math.Min(Enc.Length, {inlineSize});"); + sb.AppendLine($" Enc.AsSpan(0, Copy).CopyTo(Dst.Slice({offVar}, Copy));"); + sb.AppendLine(" }"); + } + else if (ptrContent != null) + { + bool isAscii = ptrContent.ToString() == "AsciiString"; + string enc = isAscii ? "Encoding.ASCII" : "Encoding.Unicode"; + sb.AppendLine(" {"); + sb.AppendLine(" ulong StrPtr = 0;"); + sb.AppendLine($" string? S = {accessExpr};"); + sb.AppendLine(" if (S != null)"); + sb.AppendLine(" {"); + sb.AppendLine($" byte[] Enc = {enc}.GetBytes(S);"); + sb.AppendLine($" byte[] Full = new byte[Enc.Length + ({(isAscii ? "1" : "2")})];"); + sb.AppendLine(" Buffer.BlockCopy(Enc, 0, Full, 0, Enc.Length);"); + sb.AppendLine(" StrPtr = Emulator.MapUniqueAddress((ulong)Full.Length, MemoryProtection.ReadWrite);"); + sb.AppendLine(" if (StrPtr == 0) return WriteStructResult.Fail(WriteStructError.StringAllocationFailed, \"String allocation failed\");"); + sb.AppendLine(" if (!Emulator.WriteMemory(StrPtr, Full)) return WriteStructResult.Fail(WriteStructError.PointerDestinationNotMapped, \"WriteMemory failed for string field\");"); + sb.AppendLine(" }"); + sb.AppendLine($" if (Is64) BinaryPrimitives.WriteUInt64LittleEndian(Dst.Slice({offVar}, 8), StrPtr);"); + sb.AppendLine($" else BinaryPrimitives.WriteUInt32LittleEndian(Dst.Slice({offVar}, 4), (uint)StrPtr);"); + sb.AppendLine(" }"); + } + } + + private static void EmitStringLeafRead(StringBuilder sb, string accessExpr, int inlineSize, bool ascii, object? ptrContent, string offVar) + { + if (inlineSize > 0) + { + string enc = ascii ? "Encoding.ASCII" : "Encoding.Unicode"; + int step = ascii ? 1 : 2; + string trailingCheck = step == 2 ? $" && Raw[{offVar} + Len - 1] == 0" : ""; + sb.AppendLine(" {"); + sb.AppendLine($" int Len = {inlineSize};"); + sb.AppendLine($" while (Len >= {step} && Raw[{offVar} + Len - {step}] == 0{trailingCheck}) Len -= {step};"); + sb.AppendLine($" {accessExpr} = {enc}.GetString(Raw, {offVar}, Len);"); + sb.AppendLine(" }"); + } + else if (ptrContent != null) + { + bool isAscii = ptrContent.ToString() == "AsciiString"; + string enc = isAscii ? "Encoding.ASCII" : "Encoding.Unicode"; + sb.AppendLine(" {"); + sb.AppendLine($" ulong StrP = Is64 ? BitConverter.ToUInt64(Raw, {offVar}) : BitConverter.ToUInt32(Raw, {offVar});"); + sb.AppendLine($" {accessExpr} = StrP != 0 ? ReadNullTerminatedString(Emulator, StrP, {enc}) : null;"); + sb.AppendLine(" }"); + } + } + + // --- Diagnostics --- + + private static void ReportUnsupported(GenContext gc, IFieldSymbol field) + { + var loc = field.Locations.FirstOrDefault() ?? Location.None; + gc.Ctx.ReportDiagnostic(Diagnostic.Create(UnsupportedFieldDiagnostic, loc, field.Name, field.ContainingType?.Name ?? "?")); + } + + private static void ReportNestingTooDeep(GenContext gc, INamedTypeSymbol structType) + { + var loc = structType.Locations.FirstOrDefault() ?? Location.None; + gc.Ctx.ReportDiagnostic(Diagnostic.Create(NestingTooDeepDiagnostic, loc, structType.Name, MaxNestingDepth)); + } + + // --- Layout / size helpers --- + private static bool IsExplicitLayout(INamedTypeSymbol sym) { + // TypedConstant.Value for an enum-typed constructor argument is the boxed underlying + // integral value (e.g. 2), not the LayoutKind member itself -- compare against the int. return sym.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "System.Runtime.InteropServices.StructLayoutAttribute" && a.ConstructorArguments.Length > 0 && - a.ConstructorArguments[0].Value?.ToString() == "Explicit"); + a.ConstructorArguments[0].Value is int layoutKindValue && + layoutKindValue == (int)LayoutKind.Explicit); + } + + private static int GetDeclaredStructLayoutSize(INamedTypeSymbol sym) + { + var attr = sym.GetAttributes().FirstOrDefault(a => + a.AttributeClass?.ToDisplayString() == "System.Runtime.InteropServices.StructLayoutAttribute"); + if (attr == null) return 0; + + foreach (var na in attr.NamedArguments) + if (na.Key == "Size" && na.Value.Value is int sizeVal) + return sizeVal; + + return 0; } private static IEnumerable GetOrderedFields(INamedTypeSymbol sym, bool isExplicit) { var fields = sym.GetMembers().OfType().Where(f => !f.IsStatic).ToList(); if (isExplicit) - { - fields = fields.OrderBy(f => - { - var attr = f.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "FieldOffsetAttribute"); - return attr != null ? (int)attr.ConstructorArguments[0].Value! : 0; - }).ToList(); - } + fields = fields.OrderBy(GetExplicitFieldOffset).ToList(); return fields; } - private static (int inlineSize, bool ascii, object? ptrContent) GetFieldAttrs(IFieldSymbol field, - INamedTypeSymbol? inlineAttr, INamedTypeSymbol? ptrAttr) + private static int GetExplicitFieldOffset(IFieldSymbol field) + { + var attr = field.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "FieldOffsetAttribute"); + return attr != null ? (int)attr.ConstructorArguments[0].Value! : 0; + } + + private static (int inlineSize, bool ascii, object? ptrContent) GetFieldAttrs(IFieldSymbol field, GenContext gc) { int inlineSize = 0; bool ascii = false; @@ -469,16 +754,14 @@ namespace Brovan.Generators foreach (var attr in field.GetAttributes()) { - if (SymbolEquals(attr.AttributeClass, inlineAttr) && attr.ConstructorArguments.Length > 0) + if (SymbolEquals(attr.AttributeClass, gc.InlineAttr) && attr.ConstructorArguments.Length > 0) { inlineSize = (int)attr.ConstructorArguments[0].Value!; foreach (var na in attr.NamedArguments) - { if (na.Key == "Ascii" && na.Value.Value is bool b) ascii = b; - } } - if (SymbolEquals(attr.AttributeClass, ptrAttr) && attr.ConstructorArguments.Length > 0) + if (SymbolEquals(attr.AttributeClass, gc.PtrAttr) && attr.ConstructorArguments.Length > 0) { ptrContent = attr.ConstructorArguments[0].Value; } @@ -487,34 +770,70 @@ namespace Brovan.Generators return (inlineSize, ascii, ptrContent); } - private static int ComputeSize(INamedTypeSymbol sym, bool is64, - INamedTypeSymbol? inlineAttr, INamedTypeSymbol? ptrAttr, Compilation comp) + private static int ComputeSize(INamedTypeSymbol sym, bool is64, GenContext gc) { bool isExplicit = IsExplicitLayout(sym); int max = 0; + int runningTotal = 0; foreach (var field in GetOrderedFields(sym, isExplicit)) { - int fieldOffset = 0; + int sz = GetFieldSize(field, is64, gc); if (isExplicit) - { - var fieldOffsetAttr = field.GetAttributes().FirstOrDefault(a => a.AttributeClass?.Name == "FieldOffsetAttribute"); - fieldOffset = fieldOffsetAttr != null ? (int)fieldOffsetAttr.ConstructorArguments[0].Value! : 0; - } - - int sz = GetFieldSize(field, is64, inlineAttr, ptrAttr, comp); - max = Math.Max(max, fieldOffset + sz); + max = Math.Max(max, GetExplicitFieldOffset(field) + sz); + else + runningTotal += sz; } - return max; + int computed = isExplicit ? max : runningTotal; + int declared = GetDeclaredStructLayoutSize(sym); + return Math.Max(computed, declared); } - private static int GetFieldSize(IFieldSymbol field, bool is64, - INamedTypeSymbol? inlineAttr, INamedTypeSymbol? ptrAttr, Compilation comp) + private static int GetFieldSize(IFieldSymbol field, bool is64, GenContext gc) { - var (inlineSize, _, _) = GetFieldAttrs(field, inlineAttr, ptrAttr); - + var (inlineSize, _, _) = GetFieldAttrs(field, gc); var ft = field.Type; + + if (ft is IArrayTypeSymbol arrType) + { + if (arrType.ElementType.SpecialType == SpecialType.System_Byte) + return inlineSize > 0 ? inlineSize : (is64 ? 8 : 4); + + if (inlineSize <= 0) + return is64 ? 8 : 4; + + return GetElementTypeSize(arrType.ElementType, is64, gc) * inlineSize; + } + + if (ft.TypeKind == TypeKind.Enum) + ft = ((INamedTypeSymbol)ft).EnumUnderlyingType!; + + switch (ft.SpecialType) + { + case SpecialType.System_Boolean: return 1; + case SpecialType.System_Byte: return 1; + case SpecialType.System_SByte: return 1; + case SpecialType.System_Int16: return 2; + case SpecialType.System_UInt16: return 2; + case SpecialType.System_Int32: return 4; + case SpecialType.System_UInt32: return 4; + case SpecialType.System_Int64: return 8; + case SpecialType.System_UInt64: return 8; + case SpecialType.System_Single: return 4; + case SpecialType.System_Double: return 8; + case SpecialType.System_String: return inlineSize > 0 ? inlineSize : (is64 ? 8 : 4); + } + + if (ft.IsValueType && !IsPrimitiveType(ft)) + return ComputeSize((INamedTypeSymbol)ft, is64, gc); + + return 0; + } + + private static int GetElementTypeSize(ITypeSymbol elemType, bool is64, GenContext gc) + { + var ft = elemType; if (ft.TypeKind == TypeKind.Enum) ft = ((INamedTypeSymbol)ft).EnumUnderlyingType!; @@ -533,20 +852,32 @@ namespace Brovan.Generators case SpecialType.System_Double: return 8; } - if (ft.ToDisplayString() == "string" || ft.ToDisplayString() == "byte[]") - { - if (inlineSize > 0) return inlineSize; - return is64 ? 8 : 4; - } - if (ft.IsValueType && !IsPrimitiveType(ft)) - { - return ComputeSize((INamedTypeSymbol)ft, is64, inlineAttr, ptrAttr, comp); - } + return ComputeSize((INamedTypeSymbol)ft, is64, gc); return 0; } + private static bool IsNumericPrimitive(ITypeSymbol t) + { + switch (t.SpecialType) + { + case SpecialType.System_Byte: + case SpecialType.System_SByte: + case SpecialType.System_Int16: + case SpecialType.System_UInt16: + case SpecialType.System_Int32: + case SpecialType.System_UInt32: + case SpecialType.System_Int64: + case SpecialType.System_UInt64: + case SpecialType.System_Single: + case SpecialType.System_Double: + return true; + default: + return false; + } + } + private static bool IsPrimitiveType(ITypeSymbol t) { switch (t.SpecialType) diff --git a/Brovan/Core/Emulation/OS/Linux/LinuxStructs.cs b/Brovan/Core/Emulation/OS/Linux/LinuxStructs.cs index 070c3a5..313e988 100644 --- a/Brovan/Core/Emulation/OS/Linux/LinuxStructs.cs +++ b/Brovan/Core/Emulation/OS/Linux/LinuxStructs.cs @@ -4,6 +4,7 @@ using Brovan.Core.Emulation.OS.Linux.Files; namespace Brovan.Core.Emulation.OS.Linux { + [GenerateStructSerializer] public struct Utsname { [EmulatedInline(65, Ascii = true)] @@ -26,6 +27,7 @@ namespace Brovan.Core.Emulation.OS.Linux } + [GenerateStructSerializer] [StructLayout(LayoutKind.Explicit)] public struct LinuxStat32 { @@ -90,6 +92,7 @@ namespace Brovan.Core.Emulation.OS.Linux public uint __unused5; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Explicit)] public struct LinuxStatCompat64 { @@ -162,6 +165,7 @@ namespace Brovan.Core.Emulation.OS.Linux public long tv_nsec; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Explicit)] public struct LinuxStat64 { diff --git a/Brovan/Core/Emulation/OS/SharedHelpers/StructSerializer.cs b/Brovan/Core/Emulation/OS/SharedHelpers/StructSerializer.cs index 882877d..7d3e619 100644 --- a/Brovan/Core/Emulation/OS/SharedHelpers/StructSerializer.cs +++ b/Brovan/Core/Emulation/OS/SharedHelpers/StructSerializer.cs @@ -25,6 +25,16 @@ namespace Brovan.Core.Emulation.OS public EmulatedInlineAttribute(int Size) => this.Size = Size; } + /// + /// Opts a struct into , which emits + /// reflection-free WriteStructDirect_/ParseStructDirect_/GetStructSizeDirect_ methods on + /// for it. + /// + [AttributeUsage(AttributeTargets.Struct)] + public sealed class GenerateStructSerializerAttribute : Attribute + { + } + public enum WriteStructError { None, NullDestination, DestinationNotMapped, PointerAllocationFailed, @@ -186,6 +196,31 @@ namespace Brovan.Core.Emulation.OS return Cache[T] = new TypeDesc(T); } + // --- Generated-serializer dispatch --- + + internal delegate bool ParseStructAddrDelegate(BinaryEmulator Emulator, ulong Address, out T Value); + internal delegate bool ParseStructBytesDelegate(BinaryEmulator Emulator, byte[] Raw, out T Value); + + private static readonly Dictionary DirectWrite = new(); + private static readonly Dictionary DirectParseAddr = new(); + private static readonly Dictionary DirectParseBytes = new(); + private static readonly Dictionary DirectSizeBool = new(); + private static readonly Dictionary DirectSizeEmulator = new(); + + internal static void RegisterDirect( + Func Write, + ParseStructAddrDelegate ParseAddr, + ParseStructBytesDelegate ParseBytes, + Func SizeBool, + Func SizeEmulator) where T : struct + { + DirectWrite[typeof(T)] = Write; + DirectParseAddr[typeof(T)] = ParseAddr; + DirectParseBytes[typeof(T)] = ParseBytes; + DirectSizeBool[typeof(T)] = SizeBool; + DirectSizeEmulator[typeof(T)] = SizeEmulator; + } + // --- Public API --- /// @@ -196,6 +231,9 @@ namespace Brovan.Core.Emulation.OS /// return the size. public static uint GetStructSize<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] T>(BinaryEmulator Emulator) where T : struct { + if (DirectSizeEmulator.TryGetValue(typeof(T), out Delegate Fast)) + return (uint)((Func)Fast)(Emulator); + bool Is64 = Emulator._binary.Architecture == BinaryArchitecture.x64; return (uint)GetStructSize(typeof(T), Is64); } @@ -208,6 +246,9 @@ namespace Brovan.Core.Emulation.OS /// return the size. public static int GetStructSize<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] T>(bool Is64) where T : struct { + if (DirectSizeBool.TryGetValue(typeof(T), out Delegate Fast)) + return ((Func)Fast)(Is64); + return GetStructSize(typeof(T), Is64); } @@ -232,6 +273,9 @@ namespace Brovan.Core.Emulation.OS /// Returns on success, otherwise a failure result describing the error. public static WriteStructResult WriteStruct<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] T>(BinaryEmulator Emulator, ulong Address, T Value) where T : struct { + if (DirectWrite.TryGetValue(typeof(T), out Delegate Fast)) + return ((Func)Fast)(Emulator, Address, Value); + if (Address == 0) return WriteStructResult.Fail(WriteStructError.NullDestination, $"Destination address is NULL for {typeof(T).Name}"); @@ -293,6 +337,9 @@ namespace Brovan.Core.Emulation.OS /// Returns true if successful, otherwise false. public static bool ParseStruct<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] T>(BinaryEmulator Emulator, ulong Address, out T Value) where T : struct { + if (DirectParseAddr.TryGetValue(typeof(T), out Delegate Fast)) + return ((ParseStructAddrDelegate)Fast)(Emulator, Address, out Value); + Value = default; if (Address == 0) return false; @@ -342,6 +389,9 @@ namespace Brovan.Core.Emulation.OS /// public static bool ParseStruct<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] T>(BinaryEmulator Emulator, byte[] Data, out T Value) where T : struct { + if (DirectParseBytes.TryGetValue(typeof(T), out Delegate Fast)) + return ((ParseStructBytesDelegate)Fast)(Emulator, Data, out Value); + Value = default; bool Is64 = Emulator._binary.Architecture == BinaryArchitecture.x64; diff --git a/Brovan/Core/Emulation/OS/Windows/Devices/ConsoleServer.cs b/Brovan/Core/Emulation/OS/Windows/Devices/ConsoleServer.cs index 98eb6e4..e21dcb0 100644 --- a/Brovan/Core/Emulation/OS/Windows/Devices/ConsoleServer.cs +++ b/Brovan/Core/Emulation/OS/Windows/Devices/ConsoleServer.cs @@ -16,8 +16,9 @@ namespace Brovan.Core.Emulation.OS.Windows private const uint IoctlConDrvGetDisplayMode = 0x00500027; private const uint IoctlConDrvSetDisplayMode = 0x0050002B; + [GenerateStructSerializer] [StructLayout(LayoutKind.Explicit, Size = 0x30)] - private struct ConsoleApiMsgHeader + internal struct ConsoleApiMsgHeader { [FieldOffset(0x28)] public uint ApiNumber; [FieldOffset(0x2C)] public uint ApiDescriptorSize; diff --git a/Brovan/Core/Emulation/OS/Windows/RPC/NtConnectPort.cs b/Brovan/Core/Emulation/OS/Windows/RPC/NtConnectPort.cs index 1a19ca1..94ec46b 100644 --- a/Brovan/Core/Emulation/OS/Windows/RPC/NtConnectPort.cs +++ b/Brovan/Core/Emulation/OS/Windows/RPC/NtConnectPort.cs @@ -8,7 +8,8 @@ namespace Brovan.Core.Emulation.OS.Windows internal class NtConnectPort : IWinSyscall { [StructLayout(LayoutKind.Sequential)] - private struct PORT_VIEW64 + [GenerateStructSerializer] + internal struct PORT_VIEW64 { public uint Length; public uint Padding0; @@ -20,8 +21,9 @@ namespace Brovan.Core.Emulation.OS.Windows public ulong ViewRemoteBase; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential)] - private struct REMOTE_PORT_VIEW64 + internal struct REMOTE_PORT_VIEW64 { public uint Length; public uint Padding0; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRegisterClassExWOW.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRegisterClassExWOW.cs index 3d1208f..91f7609 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRegisterClassExWOW.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRegisterClassExWOW.cs @@ -177,7 +177,8 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k } [StructLayout(LayoutKind.Explicit, Size = 80)] - private struct WNDCLASSEXW64 + [GenerateStructSerializer] + internal struct WNDCLASSEXW64 { [FieldOffset(0)] public uint cbSize; [FieldOffset(4)] public uint style; @@ -193,8 +194,9 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k [FieldOffset(72)] public ulong hIconSm; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Explicit, Size = 24)] - private struct CLSMENUNAME64 + internal struct CLSMENUNAME64 { [FieldOffset(0)] public ulong pszClientAnsiMenuName; [FieldOffset(8)] public ulong pwszClientUnicodeMenuName; diff --git a/Brovan/Core/Emulation/OS/Windows/WinStructs.cs b/Brovan/Core/Emulation/OS/Windows/WinStructs.cs index 810f1bd..cd78e9d 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinStructs.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinStructs.cs @@ -9,6 +9,7 @@ using static System.Runtime.InteropServices.JavaScript.JSType; namespace Brovan.Core.Emulation.OS.Windows { + [GenerateStructSerializer] [StructLayout(LayoutKind.Explicit)] public struct UNICODE_STRING { @@ -22,6 +23,7 @@ namespace Brovan.Core.Emulation.OS.Windows public uint Buffer; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Explicit)] public struct UNICODE_STRING64 { @@ -35,6 +37,7 @@ namespace Brovan.Core.Emulation.OS.Windows public ulong Buffer; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Explicit, Size = 48)] public struct OBJECT_ATTRIBUTES64 { @@ -57,6 +60,7 @@ namespace Brovan.Core.Emulation.OS.Windows public ulong SecurityQualityOfService; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential)] public struct MEMORY_REGION_INFORMATION { @@ -69,6 +73,7 @@ namespace Brovan.Core.Emulation.OS.Windows public ulong NodePreference; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential)] public struct MEMORY_IMAGE_EXTENSION_INFORMATION { @@ -78,6 +83,7 @@ namespace Brovan.Core.Emulation.OS.Windows public ulong ExtensionSize; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential)] public struct MEMORY_WORKING_SET_EX_INFORMATION { @@ -154,6 +160,7 @@ namespace Brovan.Core.Emulation.OS.Windows public ulong Rip; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential)] public struct MEMORY_BASIC_INFORMATION { @@ -169,6 +176,7 @@ namespace Brovan.Core.Emulation.OS.Windows public uint Reserved2; // tail padding to 48 bytes } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential)] public struct MEMORY_IMAGE_INFORMATION { @@ -217,6 +225,7 @@ namespace Brovan.Core.Emulation.OS.Windows MutantOwnerInformation = 1 } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct MUTANT_BASIC_INFORMATION { @@ -231,6 +240,7 @@ namespace Brovan.Core.Emulation.OS.Windows SemaphoreBasicInformation = 0 } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct SEMAPHORE_BASIC_INFORMATION { @@ -238,6 +248,7 @@ namespace Brovan.Core.Emulation.OS.Windows public int MaximumCount; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct OBJECT_BASIC_INFORMATION_DATA { @@ -256,6 +267,7 @@ namespace Brovan.Core.Emulation.OS.Windows public long CreationTime; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct OBJECT_TYPE_INFORMATION64 { @@ -284,6 +296,7 @@ namespace Brovan.Core.Emulation.OS.Windows public uint DefaultNonPagedPoolCharge; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct OBJECT_TYPE_INFORMATION32 { @@ -312,6 +325,7 @@ namespace Brovan.Core.Emulation.OS.Windows public uint DefaultNonPagedPoolCharge; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Explicit, Size = 16)] public struct LARGE_STRING64 { @@ -325,6 +339,7 @@ namespace Brovan.Core.Emulation.OS.Windows public ulong Buffer; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential)] public struct RECT { @@ -334,6 +349,7 @@ namespace Brovan.Core.Emulation.OS.Windows public int Bottom; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_BATTERY_STATE { @@ -361,6 +377,7 @@ namespace Brovan.Core.Emulation.OS.Windows public uint Capacity; } + [GenerateStructSerializer] [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_POWER_CAPABILITIES {