1using System.Buffers.Binary;
21internal sealed record NdsImageBuildContent(
22 NdsFileSystemBuildSnapshot FileSystem,
23 ReadOnlyMemory<byte> Arm9Data,
24 int Arm9DeclaredLength,
25 ReadOnlyMemory<byte> Arm9TrailingData,
26 ReadOnlyMemory<byte> Arm7Data,
27 int Arm7DeclaredLength,
28 ReadOnlyMemory<byte> Arm9iData,
29 ReadOnlyMemory<byte> Arm7iData,
30 ReadOnlyMemory<byte>[] Allocations,
31 byte[] Arm9OverlayTable,
32 byte[] Arm7OverlayTable);
35internal static class NdsImageBuildContentPreparer
44 public static NdsImageBuildContent Prepare(NdsImageBuilder builder, NdsImageBuildOptions options)
47 if (ndstoolProfile && builder.Kind !=
NdsImageKind.NintendoDs)
49 throw new InvalidDataException(
"The ndstool 1.50.3 compatibility profile predates DSi image creation.");
52 int arm9PrivateCount = builder.Arm9Overlays.Count(
static overlay => overlay.HasPrivateAllocation);
53 int arm7PrivateCount = builder.Arm7Overlays.Count(
static overlay => overlay.HasPrivateAllocation);
55 builder.Arm9Overlays.Concat(builder.Arm7Overlays).Any(
static overlay => !overlay.HasPrivateAllocation))
57 throw new InvalidDataException(
58 "The ndstool 1.50.3 build profile requires one private payload per Overlay record; its CLI cannot represent named-file links.");
61 if (ndstoolProfile && builder.Banner is not
null && builder.Banner.RawData.Length != 0x840)
63 throw new InvalidDataException(
64 "The ndstool 1.50.3 build profile supports only the 0x840-byte version-one Banner layout.");
67 int hiddenFileCount = ndstoolProfile ? checked(arm9PrivateCount + arm7PrivateCount) : 0;
68 NdsFileSystemBuildSnapshot fileSystem = builder.FileSystem.BuildSnapshot(hiddenFileCount);
69 ReadOnlyMemory<byte>[] allocations = CollectAllocations(builder, fileSystem, ndstoolProfile);
70 int arm9PrivateBase = ndstoolProfile
72 : checked(fileSystem.FirstFileId + fileSystem.FilesInIdOrder.Count);
73 int arm7PrivateBase = checked(arm9PrivateBase + arm9PrivateCount);
74 byte[] arm9OverlayTable = BuildOverlayTable(builder.Arm9Overlays, fileSystem, arm9PrivateBase);
75 byte[] arm7OverlayTable = BuildOverlayTable(builder.Arm7Overlays, fileSystem, arm7PrivateBase);
76 (ReadOnlyMemory<byte> arm9Data,
int arm9DeclaredLength) = PrepareProgram(
80 (ReadOnlyMemory<byte> arm7Data,
int arm7DeclaredLength) = PrepareProgram(
84 ReadOnlyMemory<byte> arm9TrailingData = PrepareArm9TrailingData(
86 synthesizeNdstoolFooter: ndstoolProfile && arm9OverlayTable.Length > 0);
87 ReadOnlyMemory<byte> arm9iData = builder.Arm9i?.Contents ?? ReadOnlyMemory<byte>.Empty;
88 ReadOnlyMemory<byte> arm7iData = builder.Arm7i?.Contents ?? ReadOnlyMemory<byte>.Empty;
111 private static (ReadOnlyMemory<byte> Data,
int DeclaredLength) PrepareProgram(
112 NdsProgramDefinition program,
118 return (program.Contents, program.Contents.Length);
121 bool alreadyHasSecureStubs = isArm9 &&
122 program.Contents.Length >= 4 &&
123 program.Contents.Span[0] == 0xFF &&
124 program.Contents.Span[1] == 0xDE &&
125 program.Contents.Span[2] == 0xFF &&
126 program.Contents.Span[3] == 0xE7;
127 int prefixLength = isArm9 && !alreadyHasSecureStubs ? 0x800 : 0;
128 byte[] data =
new byte[checked(program.Contents.Length + prefixLength)];
129 for (
int offset = 0; offset < prefixLength; offset += 4)
132 data[offset + 1] = 0xDE;
133 data[offset + 2] = 0xFF;
134 data[offset + 3] = 0xE7;
137 program.Contents.Span.CopyTo(data.AsSpan(prefixLength));
138 int declaredLength = checked((data.Length + 3) & ~3);
139 return (data, declaredLength);
149 private static ReadOnlyMemory<byte> PrepareArm9TrailingData(
150 NdsProgramDefinition program,
151 bool synthesizeNdstoolFooter)
153 if (!synthesizeNdstoolFooter)
155 return program.Footer;
158 byte[] trailing =
new byte[checked(program.Footer.Length + 12)];
159 program.Footer.Span.CopyTo(trailing);
160 Span<byte> generated = trailing.AsSpan(program.Footer.Length, 12);
161 BinaryPrimitives.WriteUInt32LittleEndian(generated, 0xDEC00621);
162 BinaryPrimitives.WriteUInt32LittleEndian(generated[4..], 0x00000AD8);
171 private static ReadOnlyMemory<byte>[] CollectAllocations(
172 NdsImageBuilder builder,
173 NdsFileSystemBuildSnapshot fileSystem,
176 IEnumerable<ReadOnlyMemory<byte>> arm9 = builder.Arm9Overlays
177 .Where(
static overlay => overlay.HasPrivateAllocation)
178 .Select(
static overlay => overlay.Contents);
179 IEnumerable<ReadOnlyMemory<byte>> arm7 = builder.Arm7Overlays
180 .Where(
static overlay => overlay.HasPrivateAllocation)
181 .Select(
static overlay => overlay.Contents);
182 IEnumerable<ReadOnlyMemory<byte>> named = fileSystem.FilesInIdOrder.Select(
static file => file.Contents);
183 return (ndstoolProfile ? arm9.Concat(arm7).Concat(named) : named.Concat(arm9).Concat(arm7)).ToArray();
191 private static byte[] BuildOverlayTable(
192 IReadOnlyList<NdsOverlayDefinition> overlays,
193 NdsFileSystemBuildSnapshot fileSystem,
194 int firstPrivateFileId)
196 Dictionary<string, int> namedFileIds = fileSystem.FilesInIdOrder
197 .Select((file, index) => (file.Path, FileId: checked(fileSystem.FirstFileId + index)))
198 .ToDictionary(
static item => item.Path,
static item => item.FileId, StringComparer.Ordinal);
199 byte[] data =
new byte[checked(overlays.Count * 32)];
200 int nextPrivateFileId = firstPrivateFileId;
201 for (
int index = 0; index < overlays.Count; index++)
203 NdsOverlayDefinition overlay = overlays[index];
204 string? linkedPath = overlay.EffectiveLinkedFilePath;
205 int fileId = linkedPath is
null
206 ? nextPrivateFileId++
207 : namedFileIds.TryGetValue(linkedPath, out
int linkedId)
209 :
throw new InvalidDataException($
"Overlay {overlay.Id} links missing NitroFS file '{linkedPath}'.");
210 Span<byte> entry = data.AsSpan(index * 32, 32);
211 NdsBinary.WriteUInt32(entry, 0x00, overlay.Id);
212 NdsBinary.WriteUInt32(entry, 0x04, overlay.LoadAddress);
213 NdsBinary.WriteUInt32(entry, 0x08, overlay.RamSize);
214 NdsBinary.WriteUInt32(entry, 0x0C, overlay.BssSize);
215 NdsBinary.WriteUInt32(entry, 0x10, overlay.StaticInitializerStart);
216 NdsBinary.WriteUInt32(entry, 0x14, overlay.StaticInitializerEnd);
217 NdsBinary.WriteUInt32(entry, 0x18, checked((uint)fileId));
218 NdsBinary.WriteUInt32(entry, 0x1C, overlay.CompressedSize | ((uint)overlay.Flags << 24));
NdsImageKind
Identifies the hardware family targeted by an image.
NdsImageBuildProfile
Selects a documented Layout personality without contaminating the logical Build Recipe.