diff --git a/src/SharpEmu.Core/Loader/SelfLoader.cs b/src/SharpEmu.Core/Loader/SelfLoader.cs index 814d1ef0..de6f436e 100644 --- a/src/SharpEmu.Core/Loader/SelfLoader.cs +++ b/src/SharpEmu.Core/Loader/SelfLoader.cs @@ -16,7 +16,9 @@ namespace SharpEmu.Core.Loader; public sealed class SelfLoader : ISelfLoader { - private const uint SelfMagic = 0x4F153D1D; + private const uint ElfMagic = 0x7F454C46; + private const uint Ps4SelfMagic = 0x4F153D1D; + private const uint Ps5SelfMagic = 0x5414F5EE; private const ulong SelfSegmentFlag = 0x800; private const int PageSize = 0x1000; private const ulong ImportStubBaseAddress = 0x0000_7000_0000_0000UL; @@ -355,10 +357,11 @@ public sealed class SelfLoader : ISelfLoader throw new InvalidDataException("Input image is too small to contain an ELF header."); } - if (imageData.Length >= sizeof(uint) && BinaryPrimitives.ReadUInt32BigEndian(imageData[..sizeof(uint)]) == SelfMagic) + var magic = BinaryPrimitives.ReadUInt32BigEndian(imageData[..sizeof(uint)]); + if (magic is Ps4SelfMagic or Ps5SelfMagic) { var selfHeader = ReadUnmanaged(imageData, 0); - if (!selfHeader.HasKnownLayout || selfHeader.Unknown != 0x22) + if (!selfHeader.HasKnownLayout) { throw new InvalidDataException("SELF header signature is not recognized."); } @@ -381,13 +384,11 @@ public sealed class SelfLoader : ISelfLoader // acceptable here; anything else — most commonly a still-encrypted // retail eboot — must be reported clearly rather than failing later // with an opaque "not a valid ELF header" message. - const uint ElfMagicBigEndian = 0x7F454C46; // "\x7fELF" - var leadingWord = BinaryPrimitives.ReadUInt32BigEndian(imageData[..sizeof(uint)]); - if (leadingWord != ElfMagicBigEndian) + if (magic != ElfMagic) { throw new InvalidDataException( $"Image is neither a decrypted ELF nor a recognized fake-signed SELF " + - $"(leading bytes 0x{leadingWord:X8}). This is almost certainly a still-encrypted " + + $"(leading bytes 0x{magic:X8}). This is almost certainly a still-encrypted " + $"retail eboot — SharpEmu has no decryption keys and requires a decrypted / " + $"fake-signed (fSELF) image."); } @@ -2789,28 +2790,27 @@ public sealed class SelfLoader : ISelfLoader private readonly ushort _size2; private readonly ulong _fileSize; private readonly ushort _segmentCount; - private readonly ushort _unknown; + private readonly ushort _flags; private readonly uint _padding; public ushort SegmentCount => _segmentCount; - public ushort Unknown => _unknown; - public ulong FileSize => _fileSize; + // Version, key type, and flags are signing metadata and vary across + // valid SELF images. They do not change the fixed header layout. public bool HasKnownLayout => - _ident0 == 0x4F && - _ident1 == 0x15 && - _ident2 == 0x3D && - _ident3 == 0x1D && - _ident4 == 0x00 && + ((_ident0 == 0x4F && + _ident1 == 0x15 && + _ident2 == 0x3D && + _ident3 == 0x1D) || + (_ident0 == 0x54 && + _ident1 == 0x14 && + _ident2 == 0xF5 && + _ident3 == 0xEE)) && _ident5 == 0x01 && _ident6 == 0x01 && - _ident7 == 0x12 && - _ident8 == 0x01 && - _ident9 == 0x01 && - _ident10 == 0x00 && - _ident11 == 0x00; + _ident7 == 0x12; } [StructLayout(LayoutKind.Sequential, Pack = 1)] diff --git a/tests/SharpEmu.Libs.Tests/Loader/SelfLoaderTests.cs b/tests/SharpEmu.Libs.Tests/Loader/SelfLoaderTests.cs new file mode 100644 index 00000000..e3d95806 --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/Loader/SelfLoaderTests.cs @@ -0,0 +1,89 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Buffers.Binary; +using SharpEmu.Core.Loader; +using SharpEmu.Core.Memory; +using Xunit; + +namespace SharpEmu.Libs.Tests.Loader; + +public sealed class SelfLoaderTests +{ + private const uint Ps4SelfMagic = 0x4F153D1D; + private const uint Ps5SelfMagic = 0x5414F5EE; + private const int SelfHeaderSize = 0x20; + private const int ElfHeaderSize = 0x40; + + [Theory] + [InlineData(Ps4SelfMagic, (byte)0x00, 0x0000_0101u, (ushort)0x22)] + [InlineData(Ps5SelfMagic, (byte)0x00, 0x0000_0101u, (ushort)0x22)] + [InlineData(Ps5SelfMagic, (byte)0x10, 0x1000_0101u, (ushort)0x32)] + public void Load_AcceptsSupportedSelfHeaderVariants( + uint magic, + byte version, + uint keyType, + ushort flags) + { + var imageData = CreateSelfImage(magic, version, keyType, flags); + + var image = new SelfLoader().Load(imageData, new VirtualMemory()); + + Assert.True(image.IsSelf); + Assert.Equal(2, image.ElfHeader.AbiVersion); + Assert.Empty(image.ProgramHeaders); + Assert.Empty(image.MappedRegions); + } + + [Theory] + [InlineData(0x05, (byte)0x00)] + [InlineData(0x06, (byte)0x02)] + [InlineData(0x07, (byte)0x00)] + public void Load_RejectsUnsupportedStructuralSelfHeaderValues(int offset, byte value) + { + var imageData = CreateSelfImage(Ps5SelfMagic, 0x10, 0x1000_0101, 0x32); + imageData[offset] = value; + + Assert.Throws(() => + new SelfLoader().Load(imageData, new VirtualMemory())); + } + + private static byte[] CreateSelfImage(uint magic, byte version, uint keyType, ushort flags) + { + var imageData = new byte[SelfHeaderSize + ElfHeaderSize]; + var selfHeader = imageData.AsSpan(0, SelfHeaderSize); + BinaryPrimitives.WriteUInt32BigEndian(selfHeader, magic); + selfHeader[0x04] = version; + selfHeader[0x05] = 0x01; + selfHeader[0x06] = 0x01; + selfHeader[0x07] = 0x12; + BinaryPrimitives.WriteUInt32LittleEndian(selfHeader[0x08..], keyType); + BinaryPrimitives.WriteUInt16LittleEndian(selfHeader[0x0C..], SelfHeaderSize); + BinaryPrimitives.WriteUInt64LittleEndian(selfHeader[0x10..], (ulong)imageData.Length); + BinaryPrimitives.WriteUInt16LittleEndian(selfHeader[0x18..], 0); + BinaryPrimitives.WriteUInt16LittleEndian(selfHeader[0x1A..], flags); + + WriteMinimalElfHeader(imageData.AsSpan(SelfHeaderSize, ElfHeaderSize)); + return imageData; + } + + private static void WriteMinimalElfHeader(Span header) + { + header.Clear(); + header[0x00] = 0x7F; + header[0x01] = (byte)'E'; + header[0x02] = (byte)'L'; + header[0x03] = (byte)'F'; + header[0x04] = 2; + header[0x05] = 1; + header[0x06] = 1; + header[0x07] = 9; + header[0x08] = 2; + BinaryPrimitives.WriteUInt16LittleEndian(header[0x10..], 3); + BinaryPrimitives.WriteUInt16LittleEndian(header[0x12..], 62); + BinaryPrimitives.WriteUInt32LittleEndian(header[0x14..], 1); + BinaryPrimitives.WriteUInt64LittleEndian(header[0x20..], ElfHeaderSize); + BinaryPrimitives.WriteUInt16LittleEndian(header[0x34..], ElfHeaderSize); + BinaryPrimitives.WriteUInt16LittleEndian(header[0x36..], 0x38); + } +}