@@ -595,6 +595,21 @@ public static ref D2SaveLayout From(Span<byte> data)
595595 return ref layout ;
596596 }
597597
598+ /// <summary>
599+ /// Gets a read-only reference to the layout from a read-only byte span.
600+ /// Throws InvalidDataException if magic values are invalid or version is >= 104.
601+ /// For v104+ saves, use <see cref="D2SaveLayoutV104.FromReadOnly(ReadOnlySpan{byte})"/> instead.
602+ /// </summary>
603+ public static ref readonly D2SaveLayout FromReadOnly ( ReadOnlySpan < byte > data )
604+ {
605+ ref readonly var layout = ref MemoryMarshal . AsRef < D2SaveLayout > ( data ) ;
606+ if ( ! layout . IsValid )
607+ throw new InvalidDataException ( "Invalid D2 save file: magic values do not match." ) ;
608+ if ( layout . Header . Version >= 104 )
609+ throw new InvalidDataException ( "Save version >= 104 uses a different header layout. Use D2SaveLayoutV104.FromReadOnly() instead." ) ;
610+ return ref layout ;
611+ }
612+
598613 /// <summary>
599614 /// Updates the checksum in the provided data buffer.
600615 /// </summary>
@@ -652,44 +667,20 @@ public static ref D2SaveLayoutV104 From(Span<byte> data)
652667 }
653668
654669 /// <summary>
655- /// Updates the checksum in the provided data buffer.
656- /// </summary>
657- public static void UpdateChecksum ( Span < byte > data )
658- => ChecksumCalculator . Update ( data ) ;
659- }
660-
661- /// <summary>
662- /// Blittable layout for the fixed 64-byte header of a shared stash tab.
663- /// Provides zero-copy access to tab header fields without parsing items.
664- /// </summary>
665- [ StructLayout ( LayoutKind . Explicit , Size = Size ) ]
666- public unsafe struct D2StashTabLayout
667- {
668- /// <summary>Size of the stash tab header in bytes.</summary>
669- public const int Size = 64 ;
670-
671- [ FieldOffset ( 0 ) ] public uint Magic ;
672- [ FieldOffset ( 4 ) ] public uint StashFormat ;
673- [ FieldOffset ( 8 ) ] public uint ItemFormat ;
674- [ FieldOffset ( 12 ) ] public uint Gold ;
675- [ FieldOffset ( 16 ) ] public ushort TabSize ;
676- [ FieldOffset ( 18 ) ] public ushort Season ;
677- [ FieldOffset ( 20 ) ] public StashTabType TabType ;
678- [ FieldOffset ( 21 ) ] private fixed byte _reserved [ 43 ] ;
679-
680- private readonly bool IsValid => Magic == D2StashTab . Magic ;
681-
682- /// <summary>
683- /// Gets a reference to the layout from a byte span at the given offset.
670+ /// Gets a read-only reference to the layout from a read-only byte span.
671+ /// Throws InvalidDataException if magic values are invalid or version is < 104.
684672 /// </summary>
685- public static ref D2StashTabLayout From ( Span < byte > data , int offset = 0 )
673+ public static ref readonly D2SaveLayoutV104 FromReadOnly ( ReadOnlySpan < byte > data )
686674 {
687- if ( data . Length - offset < Size )
688- throw new InvalidDataException ( $ "Data too small for stash tab header: { data . Length - offset } bytes, need { Size } .") ;
689- ref var layout = ref MemoryMarshal . AsRef < D2StashTabLayout > ( data [ offset ..] ) ;
675+ ref readonly var layout = ref MemoryMarshal . AsRef < D2SaveLayoutV104 > ( data ) ;
690676 if ( ! layout . IsValid )
691- throw new InvalidDataException ( $ "Invalid stash tab magic: 0x { layout . Magic : X8 } , expected 0x { D2StashTab . Magic : X8 } ") ;
677+ throw new InvalidDataException ( "Invalid D2 save file: magic values do not match or version < 104. " ) ;
692678 return ref layout ;
693679 }
694680
695- }
681+ /// <summary>
682+ /// Updates the checksum in the provided data buffer.
683+ /// </summary>
684+ public static void UpdateChecksum ( Span < byte > data )
685+ => ChecksumCalculator . Update ( data ) ;
686+ }
0 commit comments