diff --git a/src/LibHac/Common/FixedArrays/Array48.cs b/src/LibHac/Common/FixedArrays/Array48.cs new file mode 100644 index 00000000..ce3268c4 --- /dev/null +++ b/src/LibHac/Common/FixedArrays/Array48.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 Array48 +{ + public const int Length = 48; + + private Array32 _0; + private Array16 _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 Array48 value) => value.ItemsRo; +} \ No newline at end of file diff --git a/src/LibHac/Kvdb/KeyValueArchive.cs b/src/LibHac/Kvdb/KeyValueArchive.cs index 314a5757..b27ff680 100644 --- a/src/LibHac/Kvdb/KeyValueArchive.cs +++ b/src/LibHac/Kvdb/KeyValueArchive.cs @@ -1,15 +1,13 @@ using System; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Diag; namespace LibHac.Kvdb; -[StructLayout(LayoutKind.Sequential, Size = 0xC)] public struct KeyValueArchiveHeader { - public const uint ExpectedMagic = 0x564B4D49; // IMKV + public static readonly uint ExpectedMagic = 0x564B4D49; // IMKV public uint Magic; public int Reserved; @@ -25,10 +23,9 @@ public struct KeyValueArchiveHeader } } -[StructLayout(LayoutKind.Sequential, Size = 0xC)] internal struct KeyValueArchiveEntryHeader { - public const uint ExpectedMagic = 0x4E454D49; // IMEN + public static readonly uint ExpectedMagic = 0x4E454D49; // IMEN public uint Magic; public int KeySize; @@ -202,4 +199,4 @@ internal ref struct KeyValueArchiveBufferWriter Write(key); Write(value); } -} +} \ No newline at end of file diff --git a/src/LibHac/Loader/NsoHeader.cs b/src/LibHac/Loader/NsoHeader.cs index 259de222..4529b86b 100644 --- a/src/LibHac/Loader/NsoHeader.cs +++ b/src/LibHac/Loader/NsoHeader.cs @@ -2,65 +2,63 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; +using LibHac.Common.FixedArrays; namespace LibHac.Loader; -[StructLayout(LayoutKind.Explicit, Size = 0x100)] public struct NsoHeader { - public const int SegmentCount = 3; + public static readonly int SegmentCount = 3; - [FieldOffset(0x00)] public uint Magic; - [FieldOffset(0x04)] public uint Version; - [FieldOffset(0x08)] public uint Reserved08; - [FieldOffset(0x0C)] public Flag Flags; + public uint Magic; + public uint Version; + public uint Reserved08; + public Flag Flags; - [FieldOffset(0x10)] public uint TextFileOffset; - [FieldOffset(0x14)] public uint TextMemoryOffset; - [FieldOffset(0x18)] public uint TextSize; + public uint TextFileOffset; + public uint TextMemoryOffset; + public uint TextSize; - [FieldOffset(0x1C)] public uint ModuleNameOffset; + public uint ModuleNameOffset; - [FieldOffset(0x20)] public uint RoFileOffset; - [FieldOffset(0x24)] public uint RoMemoryOffset; - [FieldOffset(0x28)] public uint RoSize; + public uint RoFileOffset; + public uint RoMemoryOffset; + public uint RoSize; - [FieldOffset(0x2C)] public uint ModuleNameSize; + public uint ModuleNameSize; - [FieldOffset(0x30)] public uint DataFileOffset; - [FieldOffset(0x34)] public uint DataMemoryOffset; - [FieldOffset(0x38)] public uint DataSize; + public uint DataFileOffset; + public uint DataMemoryOffset; + public uint DataSize; - [FieldOffset(0x3C)] public uint BssSize; + public uint BssSize; - [FieldOffset(0x40)] public Buffer32 ModuleId; + public Array32 ModuleId; // Size of the sections in the NSO file - [FieldOffset(0x60)] public uint TextFileSize; - [FieldOffset(0x64)] public uint RoFileSize; - [FieldOffset(0x68)] public uint DataFileSize; + public uint TextFileSize; + public uint RoFileSize; + public uint DataFileSize; - [FieldOffset(0x6C)] private byte _reserved6C; + public Array28 Reserved6C; - [FieldOffset(0x88)] public uint ApiInfoOffset; - [FieldOffset(0x8C)] public uint ApiInfoSize; - [FieldOffset(0x90)] public uint DynStrOffset; - [FieldOffset(0x94)] public uint DynStrSize; - [FieldOffset(0x98)] public uint DynSymOffset; - [FieldOffset(0x9C)] public uint DynSymSize; + public uint ApiInfoOffset; + public uint ApiInfoSize; + public uint DynStrOffset; + public uint DynStrSize; + public uint DynSymOffset; + public uint DynSymSize; - [FieldOffset(0xA0)] public Buffer32 TextHash; - [FieldOffset(0xC0)] public Buffer32 RoHash; - [FieldOffset(0xE0)] public Buffer32 DataHash; + public Array32 TextHash; + public Array32 RoHash; + public Array32 DataHash; public Span Segments => SpanHelpers.CreateSpan(ref Unsafe.As(ref TextFileOffset), SegmentCount); public Span CompressedSizes => SpanHelpers.CreateSpan(ref TextFileSize, SegmentCount); - public Span SegmentHashes => SpanHelpers.CreateSpan(ref TextHash, SegmentCount); - - public Span Reserved6C => SpanHelpers.CreateSpan(ref _reserved6C, 0x1C); + public Span> SegmentHashes => SpanHelpers.CreateSpan(ref TextHash, SegmentCount); [Flags] public enum Flag @@ -73,11 +71,12 @@ public struct NsoHeader DataHash = 1 << 5 } - [StructLayout(LayoutKind.Sequential, Size = 0x10)] + [StructLayout(LayoutKind.Sequential)] public struct SegmentHeader { public uint FileOffset; public uint MemoryOffset; public uint Size; + private int _unused; } -} +} \ No newline at end of file diff --git a/src/LibHac/Loader/NsoReader.cs b/src/LibHac/Loader/NsoReader.cs index 35554ba4..4538de88 100644 --- a/src/LibHac/Loader/NsoReader.cs +++ b/src/LibHac/Loader/NsoReader.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.Common.FixedArrays; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Util; @@ -56,7 +57,7 @@ public class NsoReader Header.SegmentHashes[(int)segment], isCompressed, checkHash, buffer); } - private Result ReadSegmentImpl(ref NsoHeader.SegmentHeader segment, uint fileSize, Buffer32 fileHash, + private Result ReadSegmentImpl(ref NsoHeader.SegmentHeader segment, uint fileSize, Array32 fileHash, bool isCompressed, bool checkHash, Span buffer) { // Select read size based on compression. @@ -90,10 +91,10 @@ public class NsoReader // Check hash if necessary. if (checkHash) { - var hash = new Buffer32(); - Crypto.Sha256.GenerateSha256Hash(buffer.Slice(0, (int)segment.Size), hash.Bytes); + var hash = new Array32(); + Crypto.Sha256.GenerateSha256Hash(buffer.Slice(0, (int)segment.Size), hash.Items); - if (hash.Bytes.SequenceCompareTo(fileHash.Bytes) != 0) + if (hash.ItemsRo.SequenceCompareTo(fileHash) != 0) return ResultLoader.InvalidNso.Log(); } diff --git a/src/LibHac/Loader/Types.cs b/src/LibHac/Loader/Types.cs index 123cd5ef..b8c7fd03 100644 --- a/src/LibHac/Loader/Types.cs +++ b/src/LibHac/Loader/Types.cs @@ -27,26 +27,22 @@ public struct Meta public uint Magic; public int SignatureKeyGeneration; - private Array4 _reserved08; + public Array4 Reserved08; public byte Flags; - private byte _reserved0D; + public byte Reserved0D; public byte MainThreadPriority; public byte DefaultCpuId; - private Array4 _reserved10; + public Array4 Reserved10; public uint SystemResourceSize; public uint Version; public uint MainThreadStackSize; - private Array16 _programName; - private Array16 _productCode; - private Array32 _reserved40; - private Array16 _reserved60; + public Array16 ProgramName; + public Array16 ProductCode; + public Array48 Reserved40; public int AciOffset; public int AciSize; public int AcidOffset; public int AcidSize; - - public readonly ReadOnlySpan ProgramName => _programName.ItemsRo; - public readonly ReadOnlySpan ProductCode => _productCode.ItemsRo; } public struct AciHeader @@ -54,24 +50,24 @@ public struct AciHeader public static readonly uint MagicValue = 0x30494341; // ACI0 public uint Magic; - private Array12 _reserved04; + public Array12 Reserved04; public ProgramId ProgramId; - private Array8 _reserved18; + public Array8 Reserved18; public int FsAccessControlOffset; public int FsAccessControlSize; public int ServiceAccessControlOffset; public int ServiceAccessControlSize; public int KernelCapabilityOffset; public int KernelCapabilitySize; - private Array4 _reserved38; + public Array8 Reserved38; } public struct AcidHeaderData { public static readonly uint MagicValue = 0x44494341; // ACID - private Array256 _signature; - private Array256 _modulus; + public Array256 Signature; + public Array256 Modulus; public uint Magic; public int Size; public byte Version; @@ -84,8 +80,5 @@ public struct AcidHeaderData public int ServiceAccessControlSize; public int KernelCapabilityOffset; public int KernelCapabilitySize; - private Array4 _reserved238; - - public readonly ReadOnlySpan Signature => _signature.ItemsRo; - public readonly ReadOnlySpan Modulus => _modulus.ItemsRo; -} + public Array4 Reserved238; +} \ No newline at end of file diff --git a/tests/LibHac.Tests/Kvdb/TypeLayoutTests.cs b/tests/LibHac.Tests/Kvdb/TypeLayoutTests.cs new file mode 100644 index 00000000..9f0f57c9 --- /dev/null +++ b/tests/LibHac.Tests/Kvdb/TypeLayoutTests.cs @@ -0,0 +1,33 @@ +using System.Runtime.CompilerServices; +using LibHac.Kvdb; +using Xunit; +using static LibHac.Tests.Common.Layout; + +namespace LibHac.Tests.Kvdb; + +public class TypeLayoutTests +{ + [Fact] + public static void KeyValueArchiveHeader_Layout() + { + var s = new KeyValueArchiveHeader(); + + Assert.Equal(0xC, Unsafe.SizeOf()); + + Assert.Equal(0x0, GetOffset(in s, in s.Magic)); + Assert.Equal(0x4, GetOffset(in s, in s.Reserved)); + Assert.Equal(0x8, GetOffset(in s, in s.EntryCount)); + } + + [Fact] + public static void KeyValueArchiveEntryHeader_Layout() + { + var s = new KeyValueArchiveEntryHeader(); + + Assert.Equal(0xC, Unsafe.SizeOf()); + + Assert.Equal(0x0, GetOffset(in s, in s.Magic)); + Assert.Equal(0x4, GetOffset(in s, in s.KeySize)); + Assert.Equal(0x8, GetOffset(in s, in s.ValueSize)); + } +} \ No newline at end of file diff --git a/tests/LibHac.Tests/Loader/TypeLayoutTests.cs b/tests/LibHac.Tests/Loader/TypeLayoutTests.cs new file mode 100644 index 00000000..6d4763f0 --- /dev/null +++ b/tests/LibHac.Tests/Loader/TypeLayoutTests.cs @@ -0,0 +1,130 @@ +using System.Runtime.CompilerServices; +using LibHac.Loader; +using Xunit; +using static LibHac.Tests.Common.Layout; + +namespace LibHac.Tests.Loader; + +public class TypeLayoutTests +{ + [Fact] + public static void NsoHeader_Layout() + { + var s = new NsoHeader(); + + Assert.Equal(0x100, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Magic)); + Assert.Equal(0x04, GetOffset(in s, in s.Version)); + Assert.Equal(0x08, GetOffset(in s, in s.Reserved08)); + Assert.Equal(0x0C, GetOffset(in s, in s.Flags)); + + Assert.Equal(0x10, GetOffset(in s, in s.TextFileOffset)); + Assert.Equal(0x14, GetOffset(in s, in s.TextMemoryOffset)); + Assert.Equal(0x18, GetOffset(in s, in s.TextSize)); + + Assert.Equal(0x1C, GetOffset(in s, in s.ModuleNameOffset)); + + Assert.Equal(0x20, GetOffset(in s, in s.RoFileOffset)); + Assert.Equal(0x24, GetOffset(in s, in s.RoMemoryOffset)); + Assert.Equal(0x28, GetOffset(in s, in s.RoSize)); + + Assert.Equal(0x2C, GetOffset(in s, in s.ModuleNameSize)); + + Assert.Equal(0x30, GetOffset(in s, in s.DataFileOffset)); + Assert.Equal(0x34, GetOffset(in s, in s.DataMemoryOffset)); + Assert.Equal(0x38, GetOffset(in s, in s.DataSize)); + + Assert.Equal(0x3C, GetOffset(in s, in s.BssSize)); + + Assert.Equal(0x40, GetOffset(in s, in s.ModuleId)); + + Assert.Equal(0x60, GetOffset(in s, in s.TextFileSize)); + Assert.Equal(0x64, GetOffset(in s, in s.RoFileSize)); + Assert.Equal(0x68, GetOffset(in s, in s.DataFileSize)); + + Assert.Equal(0x6C, GetOffset(in s, in s.Reserved6C)); + + Assert.Equal(0x88, GetOffset(in s, in s.ApiInfoOffset)); + Assert.Equal(0x8C, GetOffset(in s, in s.ApiInfoSize)); + Assert.Equal(0x90, GetOffset(in s, in s.DynStrOffset)); + Assert.Equal(0x94, GetOffset(in s, in s.DynStrSize)); + Assert.Equal(0x98, GetOffset(in s, in s.DynSymOffset)); + Assert.Equal(0x9C, GetOffset(in s, in s.DynSymSize)); + + Assert.Equal(0xA0, GetOffset(in s, in s.TextHash)); + Assert.Equal(0xC0, GetOffset(in s, in s.RoHash)); + Assert.Equal(0xE0, GetOffset(in s, in s.DataHash)); + } + + [Fact] + public static void Meta_layout() + { + var s = new Meta(); + + Assert.Equal(0x80, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Magic)); + Assert.Equal(0x04, GetOffset(in s, in s.SignatureKeyGeneration)); + Assert.Equal(0x08, GetOffset(in s, in s.Reserved08)); + Assert.Equal(0x0C, GetOffset(in s, in s.Flags)); + Assert.Equal(0x0D, GetOffset(in s, in s.Reserved0D)); + Assert.Equal(0x0E, GetOffset(in s, in s.MainThreadPriority)); + Assert.Equal(0x0F, GetOffset(in s, in s.DefaultCpuId)); + Assert.Equal(0x10, GetOffset(in s, in s.Reserved10)); + Assert.Equal(0x14, GetOffset(in s, in s.SystemResourceSize)); + Assert.Equal(0x18, GetOffset(in s, in s.Version)); + Assert.Equal(0x1C, GetOffset(in s, in s.MainThreadStackSize)); + Assert.Equal(0x20, GetOffset(in s, in s.ProgramName)); + Assert.Equal(0x30, GetOffset(in s, in s.ProductCode)); + Assert.Equal(0x40, GetOffset(in s, in s.Reserved40)); + Assert.Equal(0x70, GetOffset(in s, in s.AciOffset)); + Assert.Equal(0x74, GetOffset(in s, in s.AciSize)); + Assert.Equal(0x78, GetOffset(in s, in s.AcidOffset)); + Assert.Equal(0x7C, GetOffset(in s, in s.AcidSize)); + } + + [Fact] + public static void AciHeader_Layout() + { + var s = new AciHeader(); + + Assert.Equal(0x40, Unsafe.SizeOf()); + + Assert.Equal(0x00, GetOffset(in s, in s.Magic)); + Assert.Equal(0x04, GetOffset(in s, in s.Reserved04)); + Assert.Equal(0x10, GetOffset(in s, in s.ProgramId)); + Assert.Equal(0x18, GetOffset(in s, in s.Reserved18)); + Assert.Equal(0x20, GetOffset(in s, in s.FsAccessControlOffset)); + Assert.Equal(0x24, GetOffset(in s, in s.FsAccessControlSize)); + Assert.Equal(0x28, GetOffset(in s, in s.ServiceAccessControlOffset)); + Assert.Equal(0x2C, GetOffset(in s, in s.ServiceAccessControlSize)); + Assert.Equal(0x30, GetOffset(in s, in s.KernelCapabilityOffset)); + Assert.Equal(0x34, GetOffset(in s, in s.KernelCapabilitySize)); + Assert.Equal(0x38, GetOffset(in s, in s.Reserved38)); + } + + [Fact] + public static void AcidHeaderData_Layout() + { + var s = new AcidHeaderData(); + + Assert.Equal(0x240, Unsafe.SizeOf()); + + Assert.Equal(0x000, GetOffset(in s, in s.Signature)); + Assert.Equal(0x100, GetOffset(in s, in s.Modulus)); + Assert.Equal(0x200, GetOffset(in s, in s.Magic)); + Assert.Equal(0x204, GetOffset(in s, in s.Size)); + Assert.Equal(0x208, GetOffset(in s, in s.Version)); + Assert.Equal(0x20C, GetOffset(in s, in s.Flags)); + Assert.Equal(0x210, GetOffset(in s, in s.ProgramIdMin)); + Assert.Equal(0x218, GetOffset(in s, in s.ProgramIdMax)); + Assert.Equal(0x220, GetOffset(in s, in s.FsAccessControlOffset)); + Assert.Equal(0x224, GetOffset(in s, in s.FsAccessControlSize)); + Assert.Equal(0x228, GetOffset(in s, in s.ServiceAccessControlOffset)); + Assert.Equal(0x22C, GetOffset(in s, in s.ServiceAccessControlSize)); + Assert.Equal(0x230, GetOffset(in s, in s.KernelCapabilityOffset)); + Assert.Equal(0x234, GetOffset(in s, in s.KernelCapabilitySize)); + Assert.Equal(0x238, GetOffset(in s, in s.Reserved238)); + } +} \ No newline at end of file diff --git a/tests/LibHac.Tests/Loader/TypeSizeTests.cs b/tests/LibHac.Tests/Loader/TypeSizeTests.cs deleted file mode 100644 index 27f4f2e3..00000000 --- a/tests/LibHac.Tests/Loader/TypeSizeTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Runtime.CompilerServices; -using LibHac.Loader; -using Xunit; - -namespace LibHac.Tests.Loader; - -public class TypeSizeTests -{ - [Fact] - public static void MetaSizeIsCorrect() - { - Assert.Equal(0x80, Unsafe.SizeOf()); - } - - [Fact] - public static void AciSizeIsCorrect() - { - Assert.Equal(0x40, Unsafe.SizeOf()); - } - - [Fact] - public static void AcidSizeIsCorrect() - { - Assert.Equal(0x240, Unsafe.SizeOf()); - } -}