diff --git a/src/LibHac/Common/FixedArrays/Array28.cs b/src/LibHac/Common/FixedArrays/Array28.cs new file mode 100644 index 00000000..24b8beb7 --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array28.cs @@ -0,0 +1,32 @@ +#pragma warning disable CS0169, IDE0051 // Remove unused private members +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays; + +public struct Array28 +{ + public const int Length = 28; + + private Array16 _0; + private Array8 _16; + private Array4 _24; + + public ref T this[int i] => ref Items[i]; + + public Span Items + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); + } + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array28 value) => value.ItemsRo; +} \ No newline at end of file diff --git a/src/LibHac/Common/FixedArrays/Array38.cs b/src/LibHac/Common/FixedArrays/Array38.cs new file mode 100644 index 00000000..2c6b8e90 --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array38.cs @@ -0,0 +1,31 @@ +#pragma warning disable CS0169, IDE0051 // Remove unused private members +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace LibHac.Common.FixedArrays; + +public struct Array38 +{ + public const int Length = 38; + + private Array32 _0; + private Array6 _32; + + public ref T this[int i] => ref Items[i]; + + public Span Items + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.Items), Length); + } + + public readonly ReadOnlySpan ItemsRo + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => SpanHelpers.CreateSpan(ref MemoryMarshal.GetReference(_0.ItemsRo), Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(in Array38 value) => value.ItemsRo; +} \ No newline at end of file diff --git a/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs b/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs index 5c239659..4c7090a3 100644 --- a/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs +++ b/src/LibHac/Fs/Impl/SaveDataTransferTypes.cs @@ -1,53 +1,27 @@ -using System; -using System.Runtime.InteropServices; -using LibHac.Common; +using LibHac.Common.FixedArrays; namespace LibHac.Fs.Impl; -[StructLayout(LayoutKind.Sequential, Size = 0x20)] public struct InitialDataAad { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); + public Array32 Value; } -[StructLayout(LayoutKind.Sequential, Size = 0x10)] public struct KeySeed { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); + public Array16 Value; } -[StructLayout(LayoutKind.Sequential, Size = 0x10)] public struct InitialDataMac { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); + public Array16 Value; } -[StructLayout(LayoutKind.Sequential, Size = 0x20)] public struct ImportReportInfo { public byte DiffChunkCount; public byte DoubleDivisionDiffChunkCount; public byte HalfDivisionDiffChunkCount; public byte CompressionRate; -} + public Array28 Reserved; +} \ No newline at end of file diff --git a/src/LibHac/Fs/SaveDataTransferTypes.cs b/src/LibHac/Fs/SaveDataTransferTypes.cs index 18871a89..d3777e0a 100644 --- a/src/LibHac/Fs/SaveDataTransferTypes.cs +++ b/src/LibHac/Fs/SaveDataTransferTypes.cs @@ -1,32 +1,13 @@ -using System; -using System.Runtime.InteropServices; -using LibHac.Common; +using LibHac.Common.FixedArrays; namespace LibHac.Fs; -[StructLayout(LayoutKind.Sequential, Size = 0x100)] public struct RsaEncryptedKey { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); - + public Array256 Value; } -[StructLayout(LayoutKind.Sequential, Size = 0x10)] public struct AesKey { - public byte this[int i] - { - readonly get => BytesRo[i]; - set => Bytes[i] = value; - } - - public Span Bytes => SpanHelpers.AsByteSpan(ref this); - public readonly ReadOnlySpan BytesRo => SpanHelpers.AsReadOnlyByteSpan(in this); -} + public Array16 Value; +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/AccessControl.cs b/src/LibHac/FsSrv/Impl/AccessControl.cs index a40758f7..6d63c096 100644 --- a/src/LibHac/FsSrv/Impl/AccessControl.cs +++ b/src/LibHac/FsSrv/Impl/AccessControl.cs @@ -752,30 +752,30 @@ public readonly struct AccessControlBits public bool CanWriteSaveDataFileSystemExtraDataTimeStamp() => Has(Bits.SaveDataBackUp); } -[StructLayout(LayoutKind.Explicit, Size = 0x2C)] +[StructLayout(LayoutKind.Sequential, Pack = 4)] internal struct AccessControlDescriptor { - [FieldOffset(0x00)] public byte Version; - [FieldOffset(0x01)] public byte ContentOwnerIdCount; - [FieldOffset(0x02)] public byte SaveDataOwnerIdCount; - [FieldOffset(0x04)] public ulong AccessFlags; - [FieldOffset(0x0C)] public ulong ContentOwnerIdMin; - [FieldOffset(0x14)] public ulong ContentOwnerIdMax; - [FieldOffset(0x1C)] public ulong SaveDataOwnerIdMin; - [FieldOffset(0x24)] public ulong SaveDataOwnerIdMax; + public byte Version; + public byte ContentOwnerIdCount; + public byte SaveDataOwnerIdCount; + public ulong AccessFlags; + public ulong ContentOwnerIdMin; + public ulong ContentOwnerIdMax; + public ulong SaveDataOwnerIdMin; + public ulong SaveDataOwnerIdMax; // public ulong ContentOwnerIds[ContentOwnerIdCount]; // public ulong SaveDataOwnerIds[SaveDataOwnerIdCount]; } -[StructLayout(LayoutKind.Explicit, Size = 0x1C)] +[StructLayout(LayoutKind.Sequential, Pack = 4)] internal struct AccessControlDataHeader { - [FieldOffset(0x00)] public byte Version; - [FieldOffset(0x04)] public ulong AccessFlags; - [FieldOffset(0x0C)] public int ContentOwnerInfoOffset; - [FieldOffset(0x10)] public int ContentOwnerInfoSize; - [FieldOffset(0x14)] public int SaveDataOwnerInfoOffset; - [FieldOffset(0x18)] public int SaveDataOwnerInfoSize; + public byte Version; + public ulong AccessFlags; + public int ContentOwnerInfoOffset; + public int ContentOwnerInfoSize; + public int SaveDataOwnerInfoOffset; + public int SaveDataOwnerInfoSize; // [FieldOffset(ContentOwnerInfoOffset)] // public int ContentOwnerInfoCount; @@ -911,4 +911,4 @@ public enum AccessibilityType MountTemporaryDirectory, MountAllBaseFileSystem, NotMount -} +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs index 7c847132..a147c4d0 100644 --- a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs +++ b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs @@ -498,13 +498,12 @@ internal class MultiCommitManager : IMultiCommitManager return Recover(multiCommitInterface, fileSystem.Get, saveService); } - [StructLayout(LayoutKind.Explicit, Size = 0x18)] private struct Context { - [FieldOffset(0x00)] public int Version; - [FieldOffset(0x04)] public CommitState State; - [FieldOffset(0x08)] public int FileSystemCount; - [FieldOffset(0x10)] public long Counter; + public int Version; + public CommitState State; + public int FileSystemCount; + public long Counter; } private enum CommitState diff --git a/src/LibHac/FsSrv/SaveDataIndexerValue.cs b/src/LibHac/FsSrv/SaveDataIndexerValue.cs index 34b09a5c..b1a2e9a5 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerValue.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerValue.cs @@ -1,14 +1,14 @@ -using System.Runtime.InteropServices; +using LibHac.Common.FixedArrays; using LibHac.Fs; namespace LibHac.FsSrv; -[StructLayout(LayoutKind.Explicit, Size = 0x40)] public struct SaveDataIndexerValue { - [FieldOffset(0x00)] public ulong SaveDataId; - [FieldOffset(0x08)] public long Size; - [FieldOffset(0x10)] public ulong Field10; - [FieldOffset(0x18)] public SaveDataSpaceId SpaceId; - [FieldOffset(0x19)] public SaveDataState State; -} + public ulong SaveDataId; + public long Size; + public ulong Field10; + public SaveDataSpaceId SpaceId; + public SaveDataState State; + public Array38 Reserved; +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Sf/FspPath.cs b/src/LibHac/FsSrv/Sf/FspPath.cs index 9fdc4fd4..37e1c8ff 100644 --- a/src/LibHac/FsSrv/Sf/FspPath.cs +++ b/src/LibHac/FsSrv/Sf/FspPath.cs @@ -1,26 +1,21 @@ using System; -using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Common.FixedArrays; using LibHac.Fs; using LibHac.Util; namespace LibHac.FsSrv.Sf; -[StructLayout(LayoutKind.Sequential, Size = MaxLength + 1)] +[StructLayout(LayoutKind.Sequential)] public readonly struct FspPath { internal const int MaxLength = 0x300; -#if DEBUG - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; -#endif + private readonly Array769 _value; - public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); + public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in _value); public static Result FromSpan(out FspPath fspPath, ReadOnlySpan path) { @@ -28,7 +23,7 @@ public readonly struct FspPath Span str = SpanHelpers.AsByteSpan(ref fspPath); - // Ensure null terminator even if the creation fails for safety + // Ensure null terminator even if the creation fails str[MaxLength] = 0; var sb = new U8StringBuilder(str); diff --git a/src/LibHac/FsSrv/Sf/Path.cs b/src/LibHac/FsSrv/Sf/Path.cs index 79996102..111ed9e4 100644 --- a/src/LibHac/FsSrv/Sf/Path.cs +++ b/src/LibHac/FsSrv/Sf/Path.cs @@ -1,23 +1,14 @@ using System; using System.Runtime.InteropServices; using LibHac.Common; -using LibHac.Fs; - -#if DEBUG -using System.Diagnostics; -#endif +using LibHac.Common.FixedArrays; namespace LibHac.FsSrv.Sf; -[StructLayout(LayoutKind.Sequential, Size = PathTool.EntryNameLengthMax + 1)] +[StructLayout(LayoutKind.Sequential)] public readonly struct Path { -#if DEBUG - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; - [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; -#endif + private readonly Array769 _value; - public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); -} + public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in _value); +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Storage/StorageDeviceHandle.cs b/src/LibHac/FsSrv/Storage/StorageDeviceHandle.cs index 1210d9ff..bde985c7 100644 --- a/src/LibHac/FsSrv/Storage/StorageDeviceHandle.cs +++ b/src/LibHac/FsSrv/Storage/StorageDeviceHandle.cs @@ -1,18 +1,19 @@ using System; -using System.Runtime.InteropServices; +using LibHac.Common.FixedArrays; namespace LibHac.FsSrv.Storage; -[StructLayout(LayoutKind.Sequential, Size = 0x10)] public readonly struct StorageDeviceHandle : IEquatable { public readonly uint Value; public readonly StorageDevicePortId PortId; + public readonly Array11 Reserved; public StorageDeviceHandle(uint value, StorageDevicePortId portId) { Value = value; PortId = portId; + Reserved = default; } public override bool Equals(object obj) => obj is StorageDeviceHandle other && Equals(other); @@ -22,4 +23,4 @@ public readonly struct StorageDeviceHandle : IEquatable public static bool operator !=(StorageDeviceHandle left, StorageDeviceHandle right) => !(left == right); public override int GetHashCode() => HashCode.Combine(Value, (int)PortId); -} +} \ No newline at end of file diff --git a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs index 057d9818..bbef6c80 100644 --- a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; using LibHac.Fs; +using LibHac.Fs.Impl; using Xunit; using static LibHac.Tests.Common.Layout; @@ -289,4 +290,68 @@ public class TypeLayoutTests Assert.Equal(0x00, GetOffset(in s, in s.Value)); } + + [Fact] + public static void RsaEncryptedKey_Layout() + { + var s = new RsaEncryptedKey(); + + Assert.Equal(0x100, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Value)); + } + + [Fact] + public static void AesKey_Layout() + { + var s = new AesKey(); + + Assert.Equal(0x10, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Value)); + } + + [Fact] + public static void InitialDataAad_Layout() + { + var s = new InitialDataAad(); + + Assert.Equal(0x20, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Value)); + } + + [Fact] + public static void KeySeed_Layout() + { + var s = new KeySeed(); + + Assert.Equal(0x10, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Value)); + } + + [Fact] + public static void InitialDataMac_Layout() + { + var s = new InitialDataMac(); + + Assert.Equal(0x10, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Value)); + } + + [Fact] + public static void ImportReportInfo_Layout() + { + var s = new ImportReportInfo(); + + Assert.Equal(0x20, Unsafe.SizeOf()); + + Assert.Equal(0, GetOffset(in s, in s.DiffChunkCount)); + Assert.Equal(1, GetOffset(in s, in s.DoubleDivisionDiffChunkCount)); + Assert.Equal(2, GetOffset(in s, in s.HalfDivisionDiffChunkCount)); + Assert.Equal(3, GetOffset(in s, in s.CompressionRate)); + Assert.Equal(4, GetOffset(in s, in s.Reserved)); + } } \ No newline at end of file diff --git a/tests/LibHac.Tests/FsSrv/TypeLayoutTests.cs b/tests/LibHac.Tests/FsSrv/TypeLayoutTests.cs new file mode 100644 index 00000000..ed20395c --- /dev/null +++ b/tests/LibHac.Tests/FsSrv/TypeLayoutTests.cs @@ -0,0 +1,91 @@ +using System.Runtime.CompilerServices; +using LibHac.FsSrv; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.FsSrv.Storage; +using Xunit; +using static LibHac.Tests.Common.Layout; + +namespace LibHac.Tests.FsSrv; + +public class TypeLayoutTests +{ + [Fact] + public static void AccessControlDescriptor_Layout() + { + var s = new AccessControlDescriptor(); + + Assert.Equal(0x2C, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Version)); + Assert.Equal(0x01, GetOffset(in s, in s.ContentOwnerIdCount)); + Assert.Equal(0x02, GetOffset(in s, in s.SaveDataOwnerIdCount)); + Assert.Equal(0x04, GetOffset(in s, in s.AccessFlags)); + Assert.Equal(0x0C, GetOffset(in s, in s.ContentOwnerIdMin)); + Assert.Equal(0x14, GetOffset(in s, in s.ContentOwnerIdMax)); + Assert.Equal(0x1C, GetOffset(in s, in s.SaveDataOwnerIdMin)); + Assert.Equal(0x24, GetOffset(in s, in s.SaveDataOwnerIdMax)); + } + + [Fact] + public static void AccessControlDataHeader_Layout() + { + var s = new AccessControlDataHeader(); + + Assert.Equal(0x1C, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Version)); + Assert.Equal(0x04, GetOffset(in s, in s.AccessFlags)); + Assert.Equal(0x0C, GetOffset(in s, in s.ContentOwnerInfoOffset)); + Assert.Equal(0x10, GetOffset(in s, in s.ContentOwnerInfoSize)); + Assert.Equal(0x14, GetOffset(in s, in s.SaveDataOwnerInfoOffset)); + Assert.Equal(0x18, GetOffset(in s, in s.SaveDataOwnerInfoSize)); + } + + [Fact] + public static void FspPath_Layout() + { + var s = new FspPath(); + + Assert.Equal(0x301, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Str[0])); + } + + [Fact] + public static void Path_Layout() + { + var s = new Path(); + + Assert.Equal(0x301, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Str[0])); + } + + [Fact] + public static void StorageDeviceHandle_Layout() + { + var s = new StorageDeviceHandle(); + + Assert.Equal(0x10, Unsafe.SizeOf()); + + Assert.Equal(0x0, GetOffset(in s, in s.Value)); + Assert.Equal(0x4, GetOffset(in s, in s.PortId)); + Assert.Equal(0x5, GetOffset(in s, in s.Reserved)); + } + + [Fact] + public static void SaveDataIndexerValue_Layout() + { + var s = new SaveDataIndexerValue(); + + Assert.Equal(0x40, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.SaveDataId)); + Assert.Equal(0x08, GetOffset(in s, in s.Size)); + Assert.Equal(0x10, GetOffset(in s, in s.Field10)); + Assert.Equal(0x18, GetOffset(in s, in s.SpaceId)); + Assert.Equal(0x19, GetOffset(in s, in s.State)); + Assert.Equal(0x1A, GetOffset(in s, in s.Reserved)); + } +} \ No newline at end of file