NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsElfParser.cs
1namespace NdsForge;
2
4internal static class NdsElfParser
5{
7 private const int HeaderSize = 52;
9 private const int ProgramHeaderSize = 32;
10
15 public static NdsElfFile Parse(ReadOnlyMemory<byte> data, NdsElfImportOptions options)
16 {
17 ReadOnlySpan<byte> bytes = data.Span;
18 if (bytes.Length < HeaderSize)
19 {
20 throw new InvalidDataException("The ELF file is shorter than its 52-byte ELF32 header.");
21 }
22
23 if (bytes[0] != 0x7F || !bytes.Slice(1, 3).SequenceEqual("ELF"u8))
24 {
25 throw new InvalidDataException("The input does not begin with the ELF magic number.");
26 }
27
28 if (bytes[4] != 1 || bytes[5] != 1 || bytes[6] != 1)
29 {
30 throw new InvalidDataException("NDS ingestion requires ELF32, little-endian encoding, and identification version one.");
31 }
32
33 if (NdsBinary.ReadUInt16(bytes, 16) != 2 || NdsBinary.ReadUInt16(bytes, 18) != 40 ||
34 NdsBinary.ReadUInt32(bytes, 20) != 1 || NdsBinary.ReadUInt16(bytes, 40) != HeaderSize)
35 {
36 throw new InvalidDataException("NDS ingestion requires an executable ARM ELF with a canonical ELF32 header.");
37 }
38
39 uint tableOffset = NdsBinary.ReadUInt32(bytes, 28);
40 ushort entrySize = NdsBinary.ReadUInt16(bytes, 42);
41 ushort count = NdsBinary.ReadUInt16(bytes, 44);
42 if (count == 0 || count > options.MaxProgramHeaders || entrySize != ProgramHeaderSize)
43 {
44 throw new InvalidDataException("The ELF program-header count or entry width is unsupported.");
45 }
46
47 long tableLength = checked((long)count * ProgramHeaderSize);
48 if (tableOffset > bytes.Length || tableLength > bytes.Length - tableOffset)
49 {
50 throw new InvalidDataException("The ELF program-header table lies outside the input.");
51 }
52
53 var segments = new NdsElfSegment[count];
54 for (int index = 0; index < count; index++)
55 {
56 int offset = checked((int)tableOffset + (index * ProgramHeaderSize));
57 ReadOnlySpan<byte> item = bytes.Slice(offset, ProgramHeaderSize);
58 var segment = new NdsElfSegment(
59 NdsBinary.ReadUInt32(item, 0),
60 NdsBinary.ReadUInt32(item, 4),
61 NdsBinary.ReadUInt32(item, 8),
62 NdsBinary.ReadUInt32(item, 12),
63 NdsBinary.ReadUInt32(item, 16),
64 NdsBinary.ReadUInt32(item, 20),
65 NdsBinary.ReadUInt32(item, 24),
66 NdsBinary.ReadUInt32(item, 28));
67 ValidateSegment(segment, bytes.Length, index);
68 segments[index] = segment;
69 }
70
71 return new(NdsBinary.ReadUInt32(bytes, 24), segments);
72 }
73
78 private static void ValidateSegment(NdsElfSegment segment, int fileLength, int index)
79 {
80 if (segment.IsLoadable && segment.MemorySize < segment.FileSize)
81 {
82 throw new InvalidDataException($"ELF program header {index} has a memory size smaller than its file image.");
83 }
84
85 if (segment.FileOffset > fileLength || segment.FileSize > fileLength - segment.FileOffset)
86 {
87 throw new InvalidDataException($"ELF program header {index} references bytes outside the input.");
88 }
89
90 if (segment.Alignment != 0 && (segment.Alignment & (segment.Alignment - 1)) != 0)
91 {
92 throw new InvalidDataException($"ELF program header {index} has a non-power-of-two alignment.");
93 }
94
95 if (segment.IsLoadable && segment.Alignment > 1 &&
96 segment.VirtualAddress % segment.Alignment != segment.FileOffset % segment.Alignment)
97 {
98 throw new InvalidDataException($"ELF loadable program header {index} has incongruent file and virtual alignment.");
99 }
100
101 if (segment.IsLoadable &&
102 ((ulong)segment.PhysicalAddress + segment.MemorySize > 0x1_0000_0000UL ||
103 (ulong)segment.VirtualAddress + segment.MemorySize > 0x1_0000_0000UL))
104 {
105 throw new InvalidDataException($"ELF loadable program header {index} wraps the 32-bit address space.");
106 }
107 }
108}