The interactive demo could not load because your browser configuration
+ limits WebAssembly for large applications. This is a known limitation of
+ Microsoft Edge Enhanced Security Mode (Strict).
+
+ How to fix this in Edge
+
Allow WebAssembly for this site:
+
+
Navigate to edge://settings/privacy/security/secureModeSites
+
Add
+ to the “Never use enhanced security for these sites” list
+
Come back and refresh this page (or click Try again below)
+
+
This only affects Edge with Enhanced
+ Security set to Strict. The default Balanced mode is not
+ affected.
+
+
+
+
+
+ Preview without WebAssembly
+
You can preview the demo via screenshots and video:
This issue is specific to Edge Enhanced Security Mode.
+ Other browsers do not have this limitation.
+
+
+
+
+
+
diff --git a/DiagramGen/DiagramGen.csproj b/DiagramGen/DiagramGen.csproj
new file mode 100644
index 0000000..a66a019
--- /dev/null
+++ b/DiagramGen/DiagramGen.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+ DiagramGen
+
+ false
+
+
+
+
+
+
+
diff --git a/DiagramGen/Program.cs b/DiagramGen/Program.cs
new file mode 100644
index 0000000..6e22644
--- /dev/null
+++ b/DiagramGen/Program.cs
@@ -0,0 +1,32 @@
+// ─────────────────────────────────────────────────────────────────────────────
+// DiagramGen -- Developer tool for regenerating RFC-style ASCII diagrams
+//
+// Generates BitFieldDiagram output for pre-defined numeric types (and any
+// other [BitFields] / [BitFieldsView] structs) for embedding in documentation.
+//
+// Usage:
+// dotnet run --project DiagramGen
+// dotnet run --project DiagramGen -- --descriptions (include field descriptions)
+//
+// The output is written to stdout. Pipe or copy into README.md / BITFIELDS.md.
+// ─────────────────────────────────────────────────────────────────────────────
+
+using Stardust.Utilities;
+
+bool includeDescriptions = args.Contains("--descriptions", StringComparer.OrdinalIgnoreCase);
+
+(string title, Type type, int bitsPerRow)[] types =
+[
+ ("IEEE754Half (16-bit)", typeof(IEEE754Half), 16),
+ ("IEEE754Single (32-bit)", typeof(IEEE754Single), 32),
+ ("IEEE754Double (64-bit)", typeof(IEEE754Double), 64),
+ ("DecimalBitFields (128-bit)", typeof(DecimalBitFields), 32),
+];
+
+foreach (var (title, type, bitsPerRow) in types)
+{
+ Console.WriteLine($"=== {title} ===");
+ Console.WriteLine(BitFieldDiagram.RenderToString(
+ type, bitsPerRow: bitsPerRow, showByteOffset: false, includeDescriptions: includeDescriptions));
+ Console.WriteLine();
+}
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..12942fe
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,11 @@
+
+
+
+ 0.9.6
+
+
+ $(NoWarn);MultipleGlobalAnalyzerKeys
+
+
diff --git a/ENDIAN.md b/ENDIAN.md
index d2f168c..886bc69 100644
--- a/ENDIAN.md
+++ b/ENDIAN.md
@@ -1,10 +1,10 @@
-# Big-Endian Types
+# Endian Types
-Type-safe big-endian (network byte order) integer types for .NET.
+Type-safe big-endian and little-endian integer types for .NET.
## Overview
-Big-endian types store the most significant byte first in memory, which is the standard byte order for network protocols, many file formats, and non-x86 hardware. These types provide:
+Endian types store bytes in a guaranteed byte order regardless of the host platform. Big-endian types store the most significant byte first (network byte order), while little-endian types store the least significant byte first (x86 native order). These types provide:
- **Type safety** - Distinct types prevent accidentally mixing endianness
- **Natural syntax** - Full operator support for arithmetic and bitwise operations
@@ -14,6 +14,8 @@ Big-endian types store the most significant byte first in memory, which is the s
## Available Types
+### Big-Endian (Network Byte Order)
+
| Type | Size | Native Equivalent | Description |
|------|------|-------------------|-------------|
| `UInt16Be` | 2 bytes | `ushort` | Unsigned 16-bit |
@@ -23,408 +25,423 @@ Big-endian types store the most significant byte first in memory, which is the s
| `UInt64Be` | 8 bytes | `ulong` | Unsigned 64-bit |
| `Int64Be` | 8 bytes | `long` | Signed 64-bit |
-## Quick Start
-
-```csharp
-using Stardust.Utilities;
-
-// Create from native values - bytes are automatically reordered
-UInt32Be networkValue = 0x12345678;
-
-// Stored in memory as: 0x12, 0x34, 0x56, 0x78 (big-endian order)
-// On little-endian x86, native uint would be: 0x78, 0x56, 0x34, 0x12
-
-// Convert back to native
-uint nativeValue = networkValue; // Implicit conversion
-
-// Arithmetic works naturally
-UInt32Be sum = networkValue + 100;
-UInt32Be product = networkValue * 2;
-
-// Serialize to network/file
-Span buffer = stackalloc byte[4];
-networkValue.WriteTo(buffer);
-// buffer now contains: [0x12, 0x34, 0x56, 0x78]
-
-// Deserialize from network/file
-var restored = new UInt32Be(buffer);
-```
-
-## Construction
-
-### From Native Values
-
-```csharp
-// Implicit conversion from native types
-UInt16Be val16 = 0x1234;
-UInt32Be val32 = 0x12345678U;
-UInt64Be val64 = 0x123456789ABCDEF0UL;
-
-Int16Be signed16 = -1000;
-Int32Be signed32 = -1000000;
-Int64Be signed64 = -1234567890123456789L;
-
-// Explicit constructor
-var explicit32 = new UInt32Be(0xDEADBEEF);
-```
-
-### From Byte Arrays
-
-```csharp
-byte[] networkData = new byte[] { 0x12, 0x34, 0x56, 0x78 };
-
-// From array with offset
-var value = new UInt32Be(networkData, offset: 0);
-
-// From IList (works with List, arrays, etc.)
-IList bytes = networkData;
-var fromList = new UInt32Be(bytes, offset: 0);
-```
-
-### From Spans (Zero-Allocation)
-
-```csharp
-// From ReadOnlySpan - zero allocation
-ReadOnlySpan packet = stackalloc byte[] { 0xDE, 0xAD, 0xBE, 0xEF };
-var header = new UInt32Be(packet);
-
-// Static factory method
-var value = UInt32Be.ReadFrom(packet);
-
-// Slice for multiple values
-var first = new UInt16Be(packet[..2]);
-var second = new UInt16Be(packet[2..4]);
-```
-
-## Serialization
-
-### To Byte Arrays
-
-```csharp
-UInt64Be value = 0x0102030405060708UL;
-
-// Instance method
-byte[] buffer = new byte[8];
-value.ToBytes(buffer, offset: 0);
-// buffer: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
-
-// Static method
-UInt64Be.ToBytes(0x0102030405060708UL, buffer, offset: 0);
-```
-
-### To Spans (Zero-Allocation)
-
-```csharp
-UInt32Be value = 0x12345678;
-
-// WriteTo - throws if span too small
-Span buffer = stackalloc byte[4];
-value.WriteTo(buffer);
-
-// TryWriteTo - returns false if span too small
-Span smallBuffer = stackalloc byte[2];
-bool success = value.TryWriteTo(smallBuffer); // false
-
-// Static optimized write (uses BinaryPrimitives internally)
-UInt32Be.WriteTo(0x12345678U, buffer);
-```
-
-## Operators
-
-All types support the full range of operators:
-
-### Arithmetic
-
-```csharp
-UInt32Be a = 100;
-UInt32Be b = 50;
-
-UInt32Be sum = a + b; // 150
-UInt32Be diff = a - b; // 50
-UInt32Be product = a * b; // 5000
-UInt32Be quotient = a / b; // 2
-UInt32Be remainder = a % b; // 0
-
-UInt32Be negated = -a; // Two's complement negation
-UInt32Be incremented = ++a; // 101
-UInt32Be decremented = --b; // 49
-```
-
-### Bitwise
-
-```csharp
-UInt32Be x = 0xFF00FF00;
-UInt32Be y = 0x00FF00FF;
-
-UInt32Be andResult = x & y; // 0x00000000
-UInt32Be orResult = x | y; // 0xFFFFFFFF
-UInt32Be xorResult = x ^ y; // 0xFFFFFFFF
-UInt32Be notResult = ~x; // 0x00FF00FF
-
-// Shift operators
-UInt32Be shifted = x >> 8; // 0x00FF00FF
-UInt32Be leftShift = y << 8; // 0xFF00FF00
-```
-
-### Comparison
-
-```csharp
-UInt32Be a = 100;
-UInt32Be b = 200;
-
-bool less = a < b; // true
-bool greater = a > b; // false
-bool lessEq = a <= b; // true
-bool greaterEq = a >= b; // false
-bool equal = a == b; // false
-bool notEqual = a != b; // true
-```
-
-### Signed Comparisons
-
-```csharp
-Int32Be positive = 100;
-Int32Be negative = -100;
-
-// Signed comparison respects sign
-bool result = negative < positive; // true (correct signed comparison)
-```
-
-## Type Conversions
-
-### Implicit Conversions (Safe, No Data Loss)
-
-```csharp
-// Native to big-endian
-UInt16Be val16 = (ushort)0x1234;
-UInt32Be val32 = 0x12345678U;
-
-// Widening conversions
-UInt64Be wide = val32; // UInt32Be -> UInt64Be
-UInt64Be fromSmall = val16; // UInt16Be -> UInt64Be
-
-// Signed widening (sign-extends correctly)
-Int16Be small = -100;
-Int64Be large = small; // Still -100, sign-extended
-
-// Big-endian to native
-ushort native16 = val16;
-uint native32 = val32;
-ulong native64 = (UInt64Be)val32;
-```
-
-### Explicit Conversions (May Truncate)
-
-```csharp
-UInt64Be big = 0x123456789ABCDEF0UL;
-
-// Narrowing - takes low bytes
-UInt32Be truncated32 = (UInt32Be)big; // 0x9ABCDEF0
-UInt16Be truncated16 = (UInt16Be)big; // 0xDEF0
-byte truncatedByte = (byte)big; // 0xF0
-```
-
-## Hi/Lo Extension Methods
-
-Extract or replace the high/low halves of values:
-
-### Hi() - Get Upper Half
-
-```csharp
-// 16-bit types -> byte
-UInt16Be val16 = 0x1234;
-byte hi16 = val16.Hi(); // 0x12
-
-// 32-bit types -> UInt16Be / ushort
-UInt32Be val32 = 0x12345678;
-UInt16Be hi32 = val32.Hi(); // 0x1234
-
-// 64-bit types -> UInt32Be / uint
-UInt64Be val64 = 0x123456789ABCDEF0UL;
-UInt32Be hi64 = val64.Hi(); // 0x12345678
-
-// Native types work too
-ulong native = 0x123456789ABCDEF0UL;
-uint nativeHi = native.Hi(); // 0x12345678
-```
-
-### Lo() - Get Lower Half
-
-```csharp
-// 16-bit types -> byte
-UInt16Be val16 = 0x1234;
-byte lo16 = val16.Lo(); // 0x34
-
-// 32-bit types -> UInt16Be / ushort
-UInt32Be val32 = 0x12345678;
-UInt16Be lo32 = val32.Lo(); // 0x5678
-
-// 64-bit types -> UInt32Be / uint
-UInt64Be val64 = 0x123456789ABCDEF0UL;
-UInt32Be lo64 = val64.Lo(); // 0x9ABCDEF0
-
-// Native types work too
-ulong native = 0x123456789ABCDEF0UL;
-uint nativeLo = native.Lo(); // 0x9ABCDEF0
-```
-
-### SetHi() / SetLo() - Replace Half
-
-```csharp
-// Set upper half
-UInt64Be value = 0x123456789ABCDEF0UL;
-UInt64Be newHi = value.SetHi((UInt32Be)0xDEADBEEFU); // 0xDEADBEEF9ABCDEF0
-
-// Set lower half
-UInt64Be newLo = value.SetLo((UInt32Be)0xCAFEBABEU); // 0x12345678CAFEBABE
-
-// Works with native types too
-ulong nativeVal = 0x123456789ABCDEF0UL;
-ulong withNewHi = nativeVal.SetHi(0xDEADBEEFU); // 0xDEADBEEF9ABCDEF0
-ulong withNewLo = nativeVal.SetLo(0xCAFEBABEU); // 0x12345678CAFEBABE
-```
-
-## Parsing and Formatting
-
-### Parsing
-
-```csharp
-// Basic parsing
-UInt32Be decimal = UInt32Be.Parse("12345678");
-UInt32Be hex = UInt32Be.Parse("DEADBEEF", NumberStyles.HexNumber);
-UInt32Be hexWithPrefix = UInt32Be.Parse("0xDEADBEEF".Replace("0x", ""), NumberStyles.HexNumber);
-
-// With format provider (IParsable)
-UInt64Be value = UInt64Be.Parse("18446744073709551615", CultureInfo.InvariantCulture);
-
-// Span-based parsing (ISpanParsable)
-ReadOnlySpan text = "12345678";
-UInt32Be fromSpan = UInt32Be.Parse(text, CultureInfo.InvariantCulture);
-
-// TryParse
-if (UInt32Be.TryParse("invalid", null, out var result))
-{
- // Won't reach here
-}
-
-// TryParse with span
-if (UInt64Be.TryParse("9876543210".AsSpan(), null, out var spanResult))
-{
- Console.WriteLine(spanResult);
-}
-```
-
-### Formatting
-
-```csharp
-UInt32Be value = 0xDEADBEEF;
-
-// Default ToString (hex with 0x prefix)
-string defaultStr = value.ToString(); // "0xdeadbeef"
-
-// IFormattable - custom formats
-string hexUpper = value.ToString("X8", null); // "DEADBEEF"
-string decimalStr = value.ToString("D", null); // "3735928559"
-string grouped = value.ToString("N0", CultureInfo.InvariantCulture); // "3,735,928,559"
-
-// ISpanFormattable - allocation-free formatting
-Span buffer = stackalloc char[16];
-if (value.TryFormat(buffer, out int written, "X8", null))
-{
- string result = new string(buffer[..written]); // "DEADBEEF"
-}
-```
-
-## Interface Support
-
-All big-endian types implement:
-
-| Interface | Description |
-|-----------|-------------|
-| `IComparable` | Non-generic comparison |
-| `IComparable` | Generic comparison |
-| `IEquatable` | Equality comparison |
-| `IFormattable` | String formatting with format/provider |
-| `ISpanFormattable` | Allocation-free span formatting |
-| `IParsable` | Static parsing with provider |
-| `ISpanParsable` | Static span-based parsing |
-
-## Real-World Examples
-
-### Network Protocol Header
-
-```csharp
-public ref struct PacketHeader
-{
- public UInt16Be Version;
- public UInt16Be Length;
- public UInt32Be SequenceNumber;
- public UInt64Be Timestamp;
-
- public static PacketHeader Read(ReadOnlySpan data)
- {
- return new PacketHeader
- {
- Version = new UInt16Be(data[0..2]),
- Length = new UInt16Be(data[2..4]),
- SequenceNumber = new UInt32Be(data[4..8]),
- Timestamp = new UInt64Be(data[8..16])
- };
- }
-
- public void Write(Span destination)
- {
- Version.WriteTo(destination[0..2]);
- Length.WriteTo(destination[2..4]);
- SequenceNumber.WriteTo(destination[4..8]);
- Timestamp.WriteTo(destination[8..16]);
- }
-}
-
-// Usage
-Span packet = stackalloc byte[16];
-var header = new PacketHeader
-{
- Version = 1,
- Length = 100,
- SequenceNumber = 12345,
- Timestamp = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
-};
-header.Write(packet);
-```
-
-### Binary File Format
-
-```csharp
-public class BinaryFileReader
-{
- private readonly Stream _stream;
- private readonly byte[] _buffer = new byte[8];
-
- public UInt32Be ReadUInt32Be()
- {
- _stream.ReadExactly(_buffer, 0, 4);
- return new UInt32Be(_buffer, 0);
- }
-
- public UInt64Be ReadUInt64Be()
- {
- _stream.ReadExactly(_buffer, 0, 8);
- return new UInt64Be(_buffer, 0);
- }
-}
-
-// Reading a file header
-using var reader = new BinaryFileReader(fileStream);
-UInt32Be magic = reader.ReadUInt32Be();
-if (magic != 0x89504E47) // PNG magic number
-{
- throw new InvalidDataException("Not a PNG file");
-}
-```
+### Little-Endian
+| Type | Size | Native Equivalent | Description |
+|------|------|-------------------|-------------|
+| `UInt16Le` | 2 bytes | `ushort` | Unsigned 16-bit |
+| `Int16Le` | 2 bytes | `short` | Signed 16-bit |
+| `UInt32Le` | 4 bytes | `uint` | Unsigned 32-bit |
+| `Int32Le` | 4 bytes | `int` | Signed 32-bit |
+| `UInt64Le` | 8 bytes | `ulong` | Unsigned 64-bit |
+| `Int64Le` | 8 bytes | `long` | Signed 64-bit |
+
+## Quick Start
+
+```csharp
+using Stardust.Utilities;
+
+// Create from native values - bytes are automatically reordered
+UInt32Be networkValue = 0x12345678;
+
+// Stored in memory as: 0x12, 0x34, 0x56, 0x78 (big-endian order)
+// On little-endian x86, native uint would be: 0x78, 0x56, 0x34, 0x12
+
+// Convert back to native
+uint nativeValue = networkValue; // Implicit conversion
+
+// Arithmetic works naturally
+UInt32Be sum = networkValue + 100;
+UInt32Be product = networkValue * 2;
+
+// Serialize to network/file
+Span buffer = stackalloc byte[4];
+networkValue.WriteTo(buffer);
+// buffer now contains: [0x12, 0x34, 0x56, 0x78]
+
+// Deserialize from network/file
+var restored = new UInt32Be(buffer);
+```
+
+## Construction
+
+### From Native Values
+
+```csharp
+// Implicit conversion from native types
+UInt16Be val16 = 0x1234;
+UInt32Be val32 = 0x12345678U;
+UInt64Be val64 = 0x123456789ABCDEF0UL;
+
+Int16Be signed16 = -1000;
+Int32Be signed32 = -1000000;
+Int64Be signed64 = -1234567890123456789L;
+
+// Explicit constructor
+var explicit32 = new UInt32Be(0xDEADBEEF);
+```
+
+### From Byte Arrays
+
+```csharp
+byte[] networkData = new byte[] { 0x12, 0x34, 0x56, 0x78 };
+
+// From array with offset
+var value = new UInt32Be(networkData, offset: 0);
+
+// From IList (works with List, arrays, etc.)
+IList bytes = networkData;
+var fromList = new UInt32Be(bytes, offset: 0);
+```
+
+### From Spans (Zero-Allocation)
+
+```csharp
+// From ReadOnlySpan - zero allocation
+ReadOnlySpan packet = stackalloc byte[] { 0xDE, 0xAD, 0xBE, 0xEF };
+var header = new UInt32Be(packet);
+
+// Static factory method
+var value = UInt32Be.ReadFrom(packet);
+
+// Slice for multiple values
+var first = new UInt16Be(packet[..2]);
+var second = new UInt16Be(packet[2..4]);
+```
+
+## Serialization
+
+### To Byte Arrays
+
+```csharp
+UInt64Be value = 0x0102030405060708UL;
+
+// Instance method
+byte[] buffer = new byte[8];
+value.ToBytes(buffer, offset: 0);
+// buffer: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]
+
+// Static method
+UInt64Be.ToBytes(0x0102030405060708UL, buffer, offset: 0);
+```
+
+### To Spans (Zero-Allocation)
+
+```csharp
+UInt32Be value = 0x12345678;
+
+// WriteTo - throws if span too small
+Span buffer = stackalloc byte[4];
+value.WriteTo(buffer);
+
+// TryWriteTo - returns false if span too small
+Span smallBuffer = stackalloc byte[2];
+bool success = value.TryWriteTo(smallBuffer); // false
+
+// Static optimized write (uses BinaryPrimitives internally)
+UInt32Be.WriteTo(0x12345678U, buffer);
+```
+
+## Operators
+
+All types support the full range of operators:
+
+### Arithmetic
+
+```csharp
+UInt32Be a = 100;
+UInt32Be b = 50;
+
+UInt32Be sum = a + b; // 150
+UInt32Be diff = a - b; // 50
+UInt32Be product = a * b; // 5000
+UInt32Be quotient = a / b; // 2
+UInt32Be remainder = a % b; // 0
+
+UInt32Be negated = -a; // Two's complement negation
+UInt32Be incremented = ++a; // 101
+UInt32Be decremented = --b; // 49
+```
+
+### Bitwise
+
+```csharp
+UInt32Be x = 0xFF00FF00;
+UInt32Be y = 0x00FF00FF;
+
+UInt32Be andResult = x & y; // 0x00000000
+UInt32Be orResult = x | y; // 0xFFFFFFFF
+UInt32Be xorResult = x ^ y; // 0xFFFFFFFF
+UInt32Be notResult = ~x; // 0x00FF00FF
+
+// Shift operators
+UInt32Be shifted = x >> 8; // 0x00FF00FF
+UInt32Be leftShift = y << 8; // 0xFF00FF00
+```
+
+### Comparison
+
+```csharp
+UInt32Be a = 100;
+UInt32Be b = 200;
+
+bool less = a < b; // true
+bool greater = a > b; // false
+bool lessEq = a <= b; // true
+bool greaterEq = a >= b; // false
+bool equal = a == b; // false
+bool notEqual = a != b; // true
+```
+
+### Signed Comparisons
+
+```csharp
+Int32Be positive = 100;
+Int32Be negative = -100;
+
+// Signed comparison respects sign
+bool result = negative < positive; // true (correct signed comparison)
+```
+
+## Type Conversions
+
+### Implicit Conversions (Safe, No Data Loss)
+
+```csharp
+// Native to big-endian
+UInt16Be val16 = (ushort)0x1234;
+UInt32Be val32 = 0x12345678U;
+
+// Widening conversions
+UInt64Be wide = val32; // UInt32Be -> UInt64Be
+UInt64Be fromSmall = val16; // UInt16Be -> UInt64Be
+
+// Signed widening (sign-extends correctly)
+Int16Be small = -100;
+Int64Be large = small; // Still -100, sign-extended
+
+// Big-endian to native
+ushort native16 = val16;
+uint native32 = val32;
+ulong native64 = (UInt64Be)val32;
+```
+
+### Explicit Conversions (May Truncate)
+
+```csharp
+UInt64Be big = 0x123456789ABCDEF0UL;
+
+// Narrowing - takes low bytes
+UInt32Be truncated32 = (UInt32Be)big; // 0x9ABCDEF0
+UInt16Be truncated16 = (UInt16Be)big; // 0xDEF0
+byte truncatedByte = (byte)big; // 0xF0
+```
+
+## Hi/Lo Extension Methods
+
+Extract or replace the high/low halves of values:
+
+### Hi() - Get Upper Half
+
+```csharp
+// 16-bit types -> byte
+UInt16Be val16 = 0x1234;
+byte hi16 = val16.Hi(); // 0x12
+
+// 32-bit types -> UInt16Be / ushort
+UInt32Be val32 = 0x12345678;
+UInt16Be hi32 = val32.Hi(); // 0x1234
+
+// 64-bit types -> UInt32Be / uint
+UInt64Be val64 = 0x123456789ABCDEF0UL;
+UInt32Be hi64 = val64.Hi(); // 0x12345678
+
+// Native types work too
+ulong native = 0x123456789ABCDEF0UL;
+uint nativeHi = native.Hi(); // 0x12345678
+```
+
+### Lo() - Get Lower Half
+
+```csharp
+// 16-bit types -> byte
+UInt16Be val16 = 0x1234;
+byte lo16 = val16.Lo(); // 0x34
+
+// 32-bit types -> UInt16Be / ushort
+UInt32Be val32 = 0x12345678;
+UInt16Be lo32 = val32.Lo(); // 0x5678
+
+// 64-bit types -> UInt32Be / uint
+UInt64Be val64 = 0x123456789ABCDEF0UL;
+UInt32Be lo64 = val64.Lo(); // 0x9ABCDEF0
+
+// Native types work too
+ulong native = 0x123456789ABCDEF0UL;
+uint nativeLo = native.Lo(); // 0x9ABCDEF0
+```
+
+### SetHi() / SetLo() - Replace Half
+
+```csharp
+// Set upper half
+UInt64Be value = 0x123456789ABCDEF0UL;
+UInt64Be newHi = value.SetHi((UInt32Be)0xDEADBEEFU); // 0xDEADBEEF9ABCDEF0
+
+// Set lower half
+UInt64Be newLo = value.SetLo((UInt32Be)0xCAFEBABEU); // 0x12345678CAFEBABE
+
+// Works with native types too
+ulong nativeVal = 0x123456789ABCDEF0UL;
+ulong withNewHi = nativeVal.SetHi(0xDEADBEEFU); // 0xDEADBEEF9ABCDEF0
+ulong withNewLo = nativeVal.SetLo(0xCAFEBABEU); // 0x12345678CAFEBABE
+```
+
+## Parsing and Formatting
+
+### Parsing
+
+```csharp
+// Basic parsing
+UInt32Be decimal = UInt32Be.Parse("12345678");
+UInt32Be hex = UInt32Be.Parse("DEADBEEF", NumberStyles.HexNumber);
+UInt32Be hexWithPrefix = UInt32Be.Parse("0xDEADBEEF".Replace("0x", ""), NumberStyles.HexNumber);
+
+// With format provider (IParsable)
+UInt64Be value = UInt64Be.Parse("18446744073709551615", CultureInfo.InvariantCulture);
+
+// Span-based parsing (ISpanParsable)
+ReadOnlySpan text = "12345678";
+UInt32Be fromSpan = UInt32Be.Parse(text, CultureInfo.InvariantCulture);
+
+// TryParse
+if (UInt32Be.TryParse("invalid", null, out var result))
+{
+ // Won't reach here
+}
+
+// TryParse with span
+if (UInt64Be.TryParse("9876543210".AsSpan(), null, out var spanResult))
+{
+ Console.WriteLine(spanResult);
+}
+```
+
+### Formatting
+
+```csharp
+UInt32Be value = 0xDEADBEEF;
+
+// Default ToString (hex with 0x prefix)
+string defaultStr = value.ToString(); // "0xdeadbeef"
+
+// IFormattable - custom formats
+string hexUpper = value.ToString("X8", null); // "DEADBEEF"
+string decimalStr = value.ToString("D", null); // "3735928559"
+string grouped = value.ToString("N0", CultureInfo.InvariantCulture); // "3,735,928,559"
+
+// ISpanFormattable - allocation-free formatting
+Span buffer = stackalloc char[16];
+if (value.TryFormat(buffer, out int written, "X8", null))
+{
+ string result = new string(buffer[..written]); // "DEADBEEF"
+}
+```
+
+## Interface Support
+
+All big-endian types implement:
+
+| Interface | Description |
+|-----------|-------------|
+| `IComparable` | Non-generic comparison |
+| `IComparable` | Generic comparison |
+| `IEquatable` | Equality comparison |
+| `IFormattable` | String formatting with format/provider |
+| `ISpanFormattable` | Allocation-free span formatting |
+| `IParsable` | Static parsing with provider |
+| `ISpanParsable` | Static span-based parsing |
+
+## Real-World Examples
+
+### Network Protocol Header
+
+```csharp
+public ref struct PacketHeader
+{
+ public UInt16Be Version;
+ public UInt16Be Length;
+ public UInt32Be SequenceNumber;
+ public UInt64Be Timestamp;
+
+ public static PacketHeader Read(ReadOnlySpan data)
+ {
+ return new PacketHeader
+ {
+ Version = new UInt16Be(data[0..2]),
+ Length = new UInt16Be(data[2..4]),
+ SequenceNumber = new UInt32Be(data[4..8]),
+ Timestamp = new UInt64Be(data[8..16])
+ };
+ }
+
+ public void Write(Span destination)
+ {
+ Version.WriteTo(destination[0..2]);
+ Length.WriteTo(destination[2..4]);
+ SequenceNumber.WriteTo(destination[4..8]);
+ Timestamp.WriteTo(destination[8..16]);
+ }
+}
+
+// Usage
+Span packet = stackalloc byte[16];
+var header = new PacketHeader
+{
+ Version = 1,
+ Length = 100,
+ SequenceNumber = 12345,
+ Timestamp = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
+};
+header.Write(packet);
+```
+
+### Binary File Format
+
+```csharp
+public class BinaryFileReader
+{
+ private readonly Stream _stream;
+ private readonly byte[] _buffer = new byte[8];
+
+ public UInt32Be ReadUInt32Be()
+ {
+ _stream.ReadExactly(_buffer, 0, 4);
+ return new UInt32Be(_buffer, 0);
+ }
+
+ public UInt64Be ReadUInt64Be()
+ {
+ _stream.ReadExactly(_buffer, 0, 8);
+ return new UInt64Be(_buffer, 0);
+ }
+}
+
+// Reading a file header
+using var reader = new BinaryFileReader(fileStream);
+UInt32Be magic = reader.ReadUInt32Be();
+if (magic != 0x89504E47) // PNG magic number
+{
+ throw new InvalidDataException("Not a PNG file");
+}
+```
+
### Hardware Register (with BitFields)
+Big-endian types can be used as storage types in `[BitFields]` structs, and as per-field
+endian overrides in `[BitFieldsView]` structs. See [BITFIELDS.md](BITFIELDS.md) for full
+documentation on composition and mixed-endian nesting.
+
```csharp
// Combining Big-Endian with BitFields for hardware emulation
[BitFields(typeof(UInt32Be))]
@@ -447,41 +464,57 @@ if (status.Ready && !status.Error)
}
```
-### Zero-Allocation Network I/O
+Big-endian types can also override individual field endianness within a `[BitFieldsView]`
+struct. This is useful when a single struct mixes byte orders at the field level:
```csharp
-public class NetworkHandler
+// x86 file blob (little-endian default) with one big-endian IP address field
+[BitFieldsView]
+public partial record struct FileBlobView
{
- public void ProcessPacket(ReadOnlySpan packet)
- {
- // Parse header without any allocation
- var version = UInt16Be.ReadFrom(packet);
- var length = UInt16Be.ReadFrom(packet[2..]);
- var checksum = UInt32Be.ReadFrom(packet[4..]);
-
- // Validate
- if (version != 1 || length > packet.Length)
- {
- return;
- }
-
- // Process payload
- ReadOnlySpan payload = packet[8..(int)(ushort)length];
- ProcessPayload(payload);
- }
-
- public int BuildResponse(Span buffer, ushort status, uint sequenceId)
- {
- // Write response header without allocation
- ((UInt16Be)1).WriteTo(buffer); // Version
- ((UInt16Be)12).WriteTo(buffer[2..]); // Length
- ((UInt32Be)status).WriteTo(buffer[4..]); // Status
- ((UInt32Be)sequenceId).WriteTo(buffer[8..]); // Sequence
- return 12;
- }
+ [BitField(0, 31)] public partial uint Timestamp { get; set; } // LE (struct default)
+ [BitField(32, 63)] public partial UInt32Be NetworkIp { get; set; } // BE (per-field override)
}
```
+For uniform-endian structs, per-field overrides are unnecessary -- just set
+`ByteOrder.BigEndian` on the `[BitFieldsView]` attribute and use plain `uint`, `ushort`, etc.
+
+### Zero-Allocation Network I/O
+
+```csharp
+public class NetworkHandler
+{
+ public void ProcessPacket(ReadOnlySpan packet)
+ {
+ // Parse header without any allocation
+ var version = UInt16Be.ReadFrom(packet);
+ var length = UInt16Be.ReadFrom(packet[2..]);
+ var checksum = UInt32Be.ReadFrom(packet[4..]);
+
+ // Validate
+ if (version != 1 || length > packet.Length)
+ {
+ return;
+ }
+
+ // Process payload
+ ReadOnlySpan payload = packet[8..(int)(ushort)length];
+ ProcessPayload(payload);
+ }
+
+ public int BuildResponse(Span buffer, ushort status, uint sequenceId)
+ {
+ // Write response header without allocation
+ ((UInt16Be)1).WriteTo(buffer); // Version
+ ((UInt16Be)12).WriteTo(buffer[2..]); // Length
+ ((UInt32Be)status).WriteTo(buffer[4..]); // Status
+ ((UInt32Be)sequenceId).WriteTo(buffer[8..]); // Sequence
+ return 12;
+ }
+}
+```
+
## TypeConverters
Each type has a `TypeConverter` for WinForms PropertyGrid and similar UI scenarios:
@@ -494,119 +527,147 @@ Each type has a `TypeConverter` for WinForms PropertyGrid and similar UI scenari
| `Int32Be` | `Int32BeTypeConverter` |
| `UInt64Be` | `UInt64BeTypeConverter` |
| `Int64Be` | `Int64BeTypeConverter` |
-
-```csharp
-// TypeConverters support hex input with 0x prefix
-var converter = new UInt32BeTypeConverter();
-object? result = converter.ConvertFrom(null, null, "0xDEADBEEF");
-// result is UInt32Be with value 0xDEADBEEF
-```
-
-## Performance Notes
-
-- All types use `[StructLayout(LayoutKind.Explicit)]` for guaranteed memory layout
-- Hot-path methods are marked with `[MethodImpl(MethodImplOptions.AggressiveInlining)]`
-- Static `WriteTo()` methods use `BinaryPrimitives` for optimized byte swapping
-- Span-based APIs enable zero-allocation I/O patterns
-- Implicit conversions compile to simple register operations
-
-## Migration from Manual Byte Swapping
-
-Before (manual approach):
-```csharp
-// Writing
-uint value = 0x12345678;
-buffer[0] = (byte)(value >> 24);
-buffer[1] = (byte)(value >> 16);
-buffer[2] = (byte)(value >> 8);
-buffer[3] = (byte)value;
-
-// Reading
-uint result = (uint)buffer[0] << 24
- | (uint)buffer[1] << 16
- | (uint)buffer[2] << 8
- | buffer[3];
-```
-
-After (with UInt32Be):
-```csharp
-// Writing
-UInt32Be value = 0x12345678;
-value.WriteTo(buffer);
-
-// Reading
-UInt32Be result = new UInt32Be(buffer);
-```
-
-Both produce identical machine code, but the big-endian type version:
-- Is more readable
-- Is type-safe (can't accidentally use wrong endianness)
-- Catches size mismatches at compile time
-- Provides consistent API across 16/32/64-bit sizes
-
-## Design Decisions
-
-### Why No Little-Endian (*Le) Types?
-
-This library provides only big-endian types (`*Be`) and intentionally omits little-endian counterparts (`*Le`). Here's why:
-
-**1. Native types are already little-endian on modern platforms**
-
-The vast majority of .NET target platforms (x86, x64, ARM, ARM64) are little-endian. Native types like `uint` and `ulong` already store bytes in little-endian order, so `UInt32Le` would be redundant.
-
-**2. Big-endian is the exception that needs explicit handling**
-
-The `*Be` types exist because big-endian byte order is needed for:
+| `UInt16Le` | `UInt16LeTypeConverter` |
+| `Int16Le` | `Int16LeTypeConverter` |
+| `UInt32Le` | `UInt32LeTypeConverter` |
+| `Int32Le` | `Int32LeTypeConverter` |
+| `UInt64Le` | `UInt64LeTypeConverter` |
+| `Int64Le` | `Int64LeTypeConverter` |
+
+```csharp
+// TypeConverters support hex input with 0x prefix
+var converter = new UInt32BeTypeConverter();
+object? result = converter.ConvertFrom(null, null, "0xDEADBEEF");
+// result is UInt32Be with value 0xDEADBEEF
+```
+
+## Performance Notes
+
+- All types use `[StructLayout(LayoutKind.Explicit)]` for guaranteed memory layout
+- Hot-path methods are marked with `[MethodImpl(MethodImplOptions.AggressiveInlining)]`
+- Static `WriteTo()` methods use `BinaryPrimitives` for optimized byte swapping
+- Span-based APIs enable zero-allocation I/O patterns
+- Implicit conversions compile to simple register operations
+
+## Migration from Manual Byte Swapping
+
+Before (manual approach):
+```csharp
+// Writing
+uint value = 0x12345678;
+buffer[0] = (byte)(value >> 24);
+buffer[1] = (byte)(value >> 16);
+buffer[2] = (byte)(value >> 8);
+buffer[3] = (byte)value;
+
+// Reading
+uint result = (uint)buffer[0] << 24
+ | (uint)buffer[1] << 16
+ | (uint)buffer[2] << 8
+ | buffer[3];
+```
+
+After (with UInt32Be):
+```csharp
+// Writing
+UInt32Be value = 0x12345678;
+value.WriteTo(buffer);
+
+// Reading
+UInt32Be result = new UInt32Be(buffer);
+```
+
+Both produce identical machine code, but the big-endian type version:
+- Is more readable
+- Is type-safe (can't accidentally use wrong endianness)
+- Catches size mismatches at compile time
+- Provides consistent API across 16/32/64-bit sizes
+
+## Design Decisions
+
+### When to Use Endian Types
+
+**Big-endian types (`*Be`)** are for data that must be stored most-significant-byte first:
- Network protocols (TCP/IP uses "network byte order" = big-endian)
- Many file formats (PNG, JPEG, Java class files, etc.)
-- Big-endian hardware emulation (68000, PowerPC, SPARC)
-
-On a little-endian machine, you need explicit conversion to/from big-endian. The `*Be` types provide this.
-
-**3. BCL already provides little-endian support for big-endian machines**
-
-For the rare case of running on a big-endian machine and needing little-endian data, the BCL provides:
-```csharp
-// Reading little-endian on any platform
-ushort value = BinaryPrimitives.ReadUInt16LittleEndian(span);
-
-// Writing little-endian on any platform
-BinaryPrimitives.WriteUInt32LittleEndian(span, value);
-```
-
-**4. YAGNI (You Aren't Gonna Need It)**
-
-Adding `*Le` types would double the API surface for a use case with minimal demand. If a specific need arises, they can be added later following the same patterns as the `*Be` types.
-
-### Memory Layout
-
-The types use `[StructLayout(LayoutKind.Explicit)]` with explicit `[FieldOffset]` attributes to guarantee byte ordering:
-
-```csharp
-[StructLayout(LayoutKind.Explicit, Size = 4)]
-public struct UInt32Be
-{
- [FieldOffset(0)] internal UInt16Be hi; // Most significant bytes
- [FieldOffset(2)] internal UInt16Be lo; // Least significant bytes
-}
-```
-
-This ensures:
-- Predictable memory layout regardless of compiler/runtime optimizations
-- Direct byte access via `hi` and `lo` fields
-- Correct behavior when casting to/from `Span`
-
-### Endianness-Agnostic Implementation
-
-The byte extraction uses bit shifting rather than pointer casting, making the implementation correct on both little-endian and big-endian machines:
-
-```csharp
-// This works correctly regardless of machine endianness
-public UInt16Be(ushort num)
-{
- hi = (byte)(num >> 8); // Always extracts the high byte
- lo = (byte)(num & 0xff); // Always extracts the low byte
-}
-```
-
-The JIT compiler optimizes this to efficient native instructions on all platforms.
+- Big-endian hardware emulation (68000, PowerPC, SPARC, MIPS32/64 (BE), ARM (BE-8 mode))
+
+**Little-endian types (`*Le`)** are for data that must be stored least-significant-byte first:
+- Per-field endian overrides in a `[BitFields]` or `[BitFieldsView]` struct whose default is big-endian
+- Cross-platform binary formats that mandate little-endian storage
+- Big-endian host machines that need to read/write x86-native data
+
+On mainstream platforms (x86, x64, ARM), native types like `uint` are already little-endian
+in memory. In most code you can use plain native types and only reach for `*Le` when you need
+a type-safe marker or a per-field override inside a BE `[BitFields]` or `[BitFieldsView]`.
+
+### Historical Context
+1. **Mono Runtime (Cross-Platform .NET Implementation)**
+ Mono was designed to be portable and has been compiled for several big-endian architectures.
+
+ |Platform / Device |CPU Architecture |Endianness |Notes |
+ |:------------------|:------------------|:--------------|:------------------|
+ |PowerPC (PPC) (older Macs, Linux PPC) | PowerPC 32/64-bit | Big-endian | Early Mono supported Mac OS X PPC and Linux PPC. |
+ |PlayStation 3 | Cell Broadband Engine (PPE core = PowerPC) | Big-endian | Unity games on PS3 used Mono in big-endian mode. |
+ |Nintendo Wii | PowerPC 750CL | Big-endian | Mono was ported unofficially for homebrew. |
+ |Nintendo GameCube | PowerPC Gekko | Big-endian | Experimental Mono builds existed. |
+ |SPARC (Solaris, Linux) | SPARC V8/V9 | Big-endian | Mono had experimental SPARC support. |
+ |MIPS (BE mode) | MIPS32/64 | Big-endian | Used in some embedded devices and routers. |
+
+2. **.NET Compact Framework (CF)**
+ The .NET Compact Framework (for Windows CE / Windows Mobile) ran on multiple CPU types, including big-endian ones.
+
+ |Platform / Device |CPU Architecture |Endianness |Notes |
+ |:------------------|:------------------|:--------------|:------------------|
+ |MIPS (BE) | MIPS32 | Big-endian | Some Windows CE devices used big-endian MIPS.
+ | SH-4 | Hitachi SuperH-4 | Big-endian | Used in some industrial controllers and set-top boxes. |
+ | ARM (BE-8 mode) | ARMv5/v6 | Mixed-endian (big-endian data) | Rare; some specialized CE devices used this mode. |
+
+3. **Unity Engine (Mono-based)**
+ Unity’s scripting backend was Mono for many years, so any Unity build targeting a big-endian console inherited Mono’s endianness.
+
+ |Platform / Device |CPU Architecture |Endianness |Notes |
+ |:------------------|:------------------|:--------------|:------------------|
+ | PS3 | PowerPC | Big-endian | Widely used in AAA games. |
+ | Wii | PowerPC | Big-endian | Unity 4.x supported Wii. |
+4. **Experimental / Research Ports**
+ Mono on IBM System z (s390x) → Big-endian mainframe architecture.
+ Mono on HP-UX PA-RISC → Big-endian RISC CPU.
+ Custom embedded boards → Some ARM and MIPS boards in big-endian mode.
+5. **Modern Status (as of 2026)**
+ Microsoft .NET (Core / 5 / 6 / 7 / 8) → Only little-endian officially.
+ Mono → Still can be compiled for big-endian, but most active builds are little-endian.
+ Unity → Dropped support for big-endian consoles after PS3/Wii era.
+
+### Memory Layout
+
+The types use `[StructLayout(LayoutKind.Explicit)]` with explicit `[FieldOffset]` attributes to guarantee byte ordering:
+
+```csharp
+[StructLayout(LayoutKind.Explicit, Size = 4)]
+public struct UInt32Be
+{
+ [FieldOffset(0)] internal UInt16Be hi; // Most significant bytes
+ [FieldOffset(2)] internal UInt16Be lo; // Least significant bytes
+}
+```
+
+This ensures:
+- Predictable memory layout regardless of compiler/runtime optimizations
+- Direct byte access via `hi` and `lo` fields
+- Correct behavior when casting to/from `Span`
+
+### Endianness-Agnostic Implementation
+
+The byte extraction uses bit shifting rather than pointer casting, making the implementation correct on both little-endian and big-endian machines:
+
+```csharp
+// This works correctly regardless of machine endianness
+public UInt16Be(ushort num)
+{
+ hi = (byte)(num >> 8); // Always extracts the high byte
+ lo = (byte)(num & 0xff); // Always extracts the low byte
+}
+```
+
+The JIT compiler optimizes this to efficient native instructions on all platforms.
diff --git a/EXTENSIONS.md b/EXTENSIONS.md
index 4af7ac2..178b32e 100644
--- a/EXTENSIONS.md
+++ b/EXTENSIONS.md
@@ -155,6 +155,6 @@ All methods use `[MethodImpl(MethodImplOptions.AggressiveInlining)]` to hint to
## See Also
-- [BITFIELD.md](BITFIELD.md) - Source generator for bit field structs
+- [BITFIELDS.md](BITFIELDS.md) - Source generator for bit field structs and buffer views
- [ENDIAN.md](ENDIAN.md) - Big-endian primitive types
- [README.md](README.md) - Package overview
diff --git a/Extensions.cs b/Extensions.cs
index 6fd0d22..f4ded48 100644
--- a/Extensions.cs
+++ b/Extensions.cs
@@ -1,18 +1,790 @@
-using System;
+using System.Reflection;
using System.Runtime.CompilerServices;
namespace Stardust.Utilities
{
+ using static Result;
+
///
/// Extension methods for byte ordering and other utilities.
///
public static class Extensions
{
///
- /// Least-significant byte.
+ /// Return the first attribute of this type on the Type .
+ /// When the type was loaded from a different ,
+ /// standard returns nothing because
+ /// the attribute type identity differs across contexts. In that case we fall back to
+ /// metadata and reconstruct the attribute locally.
+ ///
+ ///
+ /// attribute or null if not found
+ public static T? GetAttribute(this Type type, bool inherit = true) where T : Attribute
+ {
+ Type attrType = typeof(T);
+ if (type == null || attrType == null) return null;
+
+ // Primary: works when attribute and type share the same ALC / type identity
+ var result = type.GetCustomAttributes(attrType, inherit: inherit).FirstOrDefault() as T;
+ if (result != null) return result;
+
+ // Fallback: reconstruct from CustomAttributeData (works across ALC boundaries)
+ return ReconstructAttributeFromMetadata(type.CustomAttributes);
+ }
+
+ ///
+ /// Return the first attribute of this type on the field (property) of Type T.
+ /// Falls back to when the type comes from a
+ /// different .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static T? GetAttribute(this Type type, string fieldName, bool inherit = true) where T: Attribute
+ {
+ Type attrType = typeof(T);
+ if (type == null || attrType == null || string.IsNullOrEmpty(fieldName)) return null;
+ var field = type.GetProperty(fieldName);
+ if (field == null) return null;
+
+ var result = field.GetCustomAttributes(attrType, inherit: inherit).FirstOrDefault() as T;
+ if (result != null) return result;
+
+ // Fallback: reconstruct from CustomAttributeData (works across ALC boundaries)
+ return ReconstructAttributeFromMetadata(field.CustomAttributes);
+ }
+
+ ///
+ /// Return true if this class, or a base class, has the [BitFields] attribute.
+ ///
+ ///
+ ///
+ public static bool IsBitFieldsType(this Type type, bool inherit = true)
+ {
+ /// See if this type has the [BitFields] attribute.
+ return type.GetAttribute(inherit) != null;
+ }
+
+ ///
+ /// Return true if this class , or a base class, has the [BitFieldsView] attribute.
+ ///
+ ///
+ ///
+ public static bool IsBitFieldsViewType(this Type type, bool inherit = true)
+ {
+ /// See if this type has the [BitFieldsView] attribute.
+ return type.GetAttribute(inherit) != null;
+ }
+
+ ///
+ /// True if type is a BitFields struct or a BitFieldsView struct (or derives from one).
+ ///
+ ///
+ ///
+ public static bool IsBitsType(this Type type, bool inherit = true)
+ {
+ if (type == null) return false;
+ return (type.IsBitFieldsType(inherit) || type.IsBitFieldsViewType(inherit));
+ }
+
+ ///
+ /// True if this field has a BitFieldAttribute and it is a member of a [BitFields] or
+ /// [BitFieldsView] struct.
+ ///
+ ///
+ ///
+ ///
+ public static bool IsBitField(this Type type, string fieldName, bool inherit = true)
+ {
+ if (type == null || string.IsNullOrEmpty(fieldName)) return false;
+ if (!type.IsBitsType(inherit)) return false;
+ return type.GetAttribute(fieldName, inherit) != null;
+ }
+
+ ///
+ /// True if this field has a BitFlagAttribute and it is a member of a [BitFields] or [BitFieldsView] struct.
///
- ///
+ ///
+ ///
///
+ public static bool IsBitFlag(this Type type, PropertyInfo field, bool inherit= true)
+ {
+ if (type == null || field == null) return false;
+ if (!type.IsBitsType(inherit)) return false;
+ return type.GetAttribute(field.Name, inherit) != null;
+ }
+
+ ///
+ /// True if this field has a BitFlagAttribute.
+ ///
+ ///
+ ///
+ ///
+ public static bool IsBitFlag(this Type type, string fieldName, bool inherit = true)
+ {
+ if (type == null || string.IsNullOrEmpty(fieldName)) return false;
+ if (!type.IsBitsType(inherit)) return false;
+ return type.GetAttribute(fieldName, inherit) != null;
+ }
+
+ private static int GetBitCountForTypeName(string typeName) => typeName switch
+ {
+ "Byte" or "SByte" => 8,
+ "UInt16" or "Int16" => 16,
+ "UInt32" or "Int32" => 32,
+ "UInt64" or "Int64" => 64,
+ "Single" => 32,
+ "Double" => 64,
+ "Half" => 16,
+ "Decimal" => 128,
+ "UInt128" or "Int128" => 128,
+ _ => 0
+ };
+
+ private static int GetBitCountForStorageTypeEnum(int enumValue) => enumValue switch
+ {
+ 0 or 1 => 8, // Byte, SByte
+ 2 or 3 => 16, // Int16, UInt16
+ 4 or 5 => 32, // Int32, UInt32
+ 6 or 7 => 64, // Int64, UInt64
+ 8 or 9 => 64, // NInt, NUInt (treated as 64-bit for metadata)
+ 10 => 16, // Half
+ 11 => 32, // Single
+ 12 => 64, // Double
+ 13 => 128, // Decimal
+ 14 or 15 => 128, // Int128, UInt128
+ _ => 0
+ };
+
+ ///
+ /// Get the length of this BitField or BitFlag in bits.
+ ///
+ ///
+ ///
+ /// Result with length if success or error message if failure.
+
+ public static Result GetBitLength(this Type type, string? fieldName = null, bool inherit = true)
+ {
+ if (fieldName != null)
+ {
+ return type.GetStartAndEndBits(fieldName, inherit).Match(
+ onSuccess: bits => Result.Ok(bits.endBit - bits.startBit + 1),
+ onFailure: err => Result.Err(err)
+ );
+ }
+ // Bits based on size of struct
+ if (!type.IsBitsType(inherit))
+ {
+ return Result.Err("Type is not a BitFields struct or BitFieldsView struct");
+ }
+ var structTotalBitsRes = type.GetBitTypeAttribute(inherit: inherit).Match(
+ onSuccess: attr =>
+ {
+ if (attr is BitFieldsAttribute fieldsAttr)
+ {
+ return Result.Ok(fieldsAttr.BitCount);
+ }
+ else
+ {
+ return Result.Err("Type does not have BitFields attribute");
+ }
+ },
+ onFailure: err => Result.Err(err)
+ );
+ return structTotalBitsRes;
+ }
+
+ ///
+ /// Get the start bit.
+ ///
+ ///
+ ///
+ /// Result with start bit number if success or error message if failure
+ public static Result GetStartBit(this Type type, string fieldName, bool inherit = true)
+ {
+ return type.GetStartAndEndBits(fieldName, inherit).Match(
+ onSuccess: bits => Result.Ok(bits.startBit),
+ onFailure: err => Result.Err(err)
+ );
+ }
+
+ ///
+ /// Get the end bit.
+ ///
+ ///
+ ///
+ /// Result with end bit number if success or error message if failure
+ public static Result GetEndBit(this Type type, string fieldName, bool inherit = true)
+ {
+ return type.GetStartAndEndBits(fieldName, inherit).Match(
+ onSuccess: bits => Result.Ok(bits.endBit),
+ onFailure: err => Result.Err(err)
+ );
+ }
+
+ ///
+ /// Get start and end bits for this BitField.
+ ///
+ ///
+ ///
+ /// Result with start and end bits if success or error message if failure.
+ public static Result<(int startBit,int endBit),string> GetStartAndEndBits(this Type type, string fieldName, bool inherit = true)
+ {
+ if (type == null)
+ {
+ return Result<(int startBit, int endBit), string>.Err("'type' is null");
+ }
+ if (!type.IsBitsType())
+ {
+ return Result<(int startBit, int endBit), string>.Err("Type is not a BitFields struct or BitFieldsView struct");
+ }
+ if (string.IsNullOrEmpty(fieldName))
+ {
+ return Result<(int startBit, int endBit), string>.Err("No field name");
+ }
+ var field = type.GetProperty(fieldName);
+ if (field == null)
+ {
+ return Result<(int startBit, int endBit), string>.Err("Field not found");
+ }
+ BitFieldAttribute? fieldAttr = type.GetAttribute(fieldName, inherit);
+ if (fieldAttr != null)
+ {
+ return Ok((fieldAttr.StartBit, fieldAttr.EndBit));
+ }
+ BitFlagAttribute? flagAttr = type.GetAttribute(fieldName, inherit);
+ if (flagAttr != null)
+ {
+ return Ok((flagAttr.Bit, flagAttr.Bit));
+ }
+
+ return Result<(int startBit, int endBit), string>.Err("Field is not a BitField or a BitFlag");
+ }
+
+ ///
+ /// Get the for this field if it has a BitField or BitFlag attribute.
+ /// If the field does not have a BitField or BitFlag attribute, or if the type is not a
+ /// BitFields struct or BitFieldsView struct, return an error.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Result GetFieldValueOverride(this Type type, string fieldName, bool inherit = true)
+ {
+ if (type == null)
+ {
+ return Result.Err("'type' cannot be null");
+ }
+ if (string.IsNullOrEmpty(fieldName))
+ {
+ return Result.Err("'fieldName' must not be empty");
+ }
+ var fieldAttr = type.GetAttribute(inherit);
+ if (fieldAttr != null)
+ {
+ var mustBe = fieldAttr.ValueOverride;
+ return Ok(mustBe);
+ }
+ var flagAttr = type.GetAttribute(inherit);
+ if (flagAttr != null)
+ {
+ var mustBe = flagAttr.ValueOverride;
+ return Ok(mustBe);
+ }
+ return Result.Err("Field does not have BitField or BitFlag attribute");
+ }
+
+ ///
+ /// Get the value for undefined bits in a struct.
+ ///
+ ///
+ ///
+ ///
+ public static Result GetUndefinedBitsMustBe(this Type type, bool inherit = true)
+ {
+ if (type == null)
+ {
+ return Result.Err("type is null");
+ }
+ if (!type.IsBitFieldsType() && !type.IsBitFieldsViewType())
+ {
+ return Result.Err("Type is not a BitFields struct or BitFieldsView struct");
+ }
+ var fieldsAttr = type.GetAttribute(inherit);
+ if (fieldsAttr != null)
+ {
+ return Ok(fieldsAttr.UndefinedBits);
+ }
+ else
+ {
+ return Result.Err("Type does not have BitFields attribute");
+ }
+ }
+ ///
+ /// Get the byte and bit order for this bit struct or bit struct view.
+ ///
+ ///
+ /// byte order, bit order or error message if failure
+ public static Result<(ByteOrder byteOrder, BitOrder bitOrder),string> GetBitAndByteOrder(this Type type, bool inherit = true)
+ {
+ if (type == null)
+ {
+ return Result<(ByteOrder byteOrder, BitOrder bitOrder), string>.Err("type is null");
+ }
+ var fieldsAttr = type.GetAttribute(inherit);
+ if (fieldsAttr != null)
+ {
+ return Ok((fieldsAttr.ByteOrder, fieldsAttr.BitOrder));
+ }
+ var viewAttr = type.GetAttribute(inherit);
+ if (viewAttr != null)
+ {
+ return Ok((viewAttr.ByteOrder, viewAttr.BitOrder));
+ }
+ else
+ {
+ return Result<(ByteOrder byteOrder, BitOrder bitOrder), string>.Err("Type does not have BitFields or BitFieldsView attribute");
+ }
+ }
+
+ ///
+ /// Get the description of the type or field.
+ ///
+ ///
+ ///
+ /// Description (may be null) of the type if field is null, else field description (may be null).
+ /// Error if BitFields, BitFieldsView, BitField, or BitFlag attribute is not found.
+ ///
+ public static Result<(string? description, Type? descriptionResourceType), string> GetBitsDescription(this Type type, string? field = null, bool inherit = true)
+ {
+ if (type == null)
+ {
+ return Result<(string? description, Type? descriptionResourceType), string>.Err("type is null");
+ }
+
+ var fieldInfo = field != null ? type.GetProperty(field) : null;
+ if (fieldInfo == null && field != null)
+ {
+ return Result<(string? description, Type? descriptionResourceType), string>.Err("Invalid field name");
+ }
+ // Return description of the struct if no field, otherwise return the description of the field.
+ if (fieldInfo == null)
+ {
+ // Return the description of the struct (type).
+ var fldsAttr = type.GetAttribute(inherit);
+ if (fldsAttr != null)
+ {
+ return Ok((fldsAttr.Description, fldsAttr.DescriptionResourceType));
+ }
+ var fldsViewAttr = type.GetAttribute(inherit);
+ if (fldsViewAttr != null)
+ {
+ return Ok((fldsViewAttr.Description, fldsViewAttr.DescriptionResourceType));
+ }
+ return Result<(string? description, Type? descriptionResourceType), string>.Err("Type does not have BitFields or BitFieldsView attribute");
+ }
+ // We have a field, so get the description of the field.
+ var fldAttr = type.GetAttribute(field!, inherit);
+ if (fldAttr != null)
+ {
+ return Ok((fldAttr.Description, fldAttr.DescriptionResourceType));
+ }
+ var flagAttr = type.GetAttribute(field!, inherit);
+ if (flagAttr != null)
+ {
+ return Ok((flagAttr.Description, flagAttr.DescriptionResourceType));
+ }
+ return Result<(string? description, Type? descriptionResourceType), string>.Err($"Field {type.FullName}.{field} does not have a BitField or BitFlag attribute");
+ }
+
+ public static Result GetBitTypeAttribute(this Type type, string? field = null, bool inherit = true)
+ {
+ if (type == null)
+ {
+ return Result.Err("type is null");
+ }
+ var fieldInfo = field != null ? type.GetProperty(field) : null;
+ if (fieldInfo == null && field != null)
+ {
+ return Result.Err("Invalid field name");
+ }
+ Attribute? attribute;
+ if (fieldInfo == null)
+ {
+ // Return the attribute of the struct (type).
+ attribute = type.GetAttribute(inherit);
+ attribute ??= type.GetAttribute(inherit);
+ if (attribute != null)
+ {
+ return Ok(attribute);
+ }
+ return Result.Err($"Type {type.FullName} does not have a BitFields or BitFieldsView attribute");
+ }
+
+ // We have a field, so get the attribute of the field.
+ attribute = type.GetAttribute(field!, inherit);
+ attribute ??= type.GetAttribute(field!, inherit);
+
+ return attribute != null ? Ok(attribute) : Result.Err($"Field {type.FullName}.{field} does not have a BitField or BitFlag attribute");
+ }
+
+ // ── Attribute-metadata discovery ────────────────────────────────
+
+ ///
+ /// Discovers metadata for a [BitFields] or [BitFieldsView]
+ /// type by reading directly, without invoking any generated code.
+ /// This works reliably across boundaries
+ /// where the generated Fields property cannot be called via delegates due to type-identity
+ /// mismatches.
+ ///
+ /// A struct type decorated with [BitFields] or [BitFieldsView].
+ ///
+ /// An array of describing each declared field/flag, sorted by start bit.
+ /// Returns an empty array if the type has no recognised bit-field attributes.
+ ///
+ public static BitFieldInfo[] GetBitFieldInfoFromAttributes(this Type type)
+ {
+ if (type == null) return [];
+
+ var structInfo = ReadStructAttributeMetadata(type);
+ if (!structInfo.Found) return [];
+
+ return ReadFieldInfoFromPropertyMetadata(
+ type,
+ structInfo.BitOrder,
+ structInfo.ByteOrder,
+ structInfo.TotalBits,
+ structInfo.UndefinedMustBe,
+ structInfo.Description);
+ }
+
+ ///
+ /// Retrieves the Fields metadata from a [BitFields] or [BitFieldsView]
+ /// type. First attempts to invoke the generated static Fields property via a typed
+ /// delegate (fastest path, works when the type is in the same
+ /// ). Falls back to
+ /// when the type was loaded from a different
+ /// context (cross-ALC scenario).
+ ///
+ /// A struct type decorated with [BitFields] or [BitFieldsView].
+ /// A successful result containing the field metadata array, or an error string on failure.
+ public static Result GetFieldInfo(this Type type)
+ {
+ if (type == null)
+ return Result.Err("'type' is null");
+
+ var prop = type.GetProperty("Fields", BindingFlags.Public | BindingFlags.Static);
+ if (prop != null)
+ {
+ // The generated property returns ReadOnlySpan backed by an array literal.
+ // ReadOnlySpan cannot be obtained via PropertyInfo.GetValue (reflection limitation).
+ // Instead, invoke the getter via a typed delegate.
+ var getter = prop.GetGetMethod();
+ if (getter != null)
+ {
+ var delegateType = typeof(SpanGetter<>).MakeGenericType(typeof(BitFieldInfo));
+ var del = Delegate.CreateDelegate(delegateType, getter, throwOnBindFailure: false);
+ if (del != null)
+ {
+ var span = ((SpanGetter)del)();
+ return Ok(span.ToArray());
+ }
+
+ // Fallback: try direct GetValue for non-span return types
+ try
+ {
+ var value = prop.GetValue(null);
+ if (value is BitFieldInfo[] array)
+ return Ok(array);
+ }
+ catch (NotSupportedException) { /* ReadOnlySpan