NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsImageBuildContent.cs
1using System.Buffers.Binary;
2
3namespace NdsForge;
4
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);
33
35internal static class NdsImageBuildContentPreparer
36{
44 public static NdsImageBuildContent Prepare(NdsImageBuilder builder, NdsImageBuildOptions options)
45 {
46 bool ndstoolProfile = options.Profile == NdsImageBuildProfile.Ndstool1503;
47 if (ndstoolProfile && builder.Kind != NdsImageKind.NintendoDs)
48 {
49 throw new InvalidDataException("The ndstool 1.50.3 compatibility profile predates DSi image creation.");
50 }
51
52 int arm9PrivateCount = builder.Arm9Overlays.Count(static overlay => overlay.HasPrivateAllocation);
53 int arm7PrivateCount = builder.Arm7Overlays.Count(static overlay => overlay.HasPrivateAllocation);
54 if (ndstoolProfile &&
55 builder.Arm9Overlays.Concat(builder.Arm7Overlays).Any(static overlay => !overlay.HasPrivateAllocation))
56 {
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.");
59 }
60
61 if (ndstoolProfile && builder.Banner is not null && builder.Banner.RawData.Length != 0x840)
62 {
63 throw new InvalidDataException(
64 "The ndstool 1.50.3 build profile supports only the 0x840-byte version-one Banner layout.");
65 }
66
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
71 ? 0
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(
77 builder.Arm9!,
78 isArm9: true,
79 ndstoolProfile);
80 (ReadOnlyMemory<byte> arm7Data, int arm7DeclaredLength) = PrepareProgram(
81 builder.Arm7!,
82 isArm9: false,
83 ndstoolProfile);
84 ReadOnlyMemory<byte> arm9TrailingData = PrepareArm9TrailingData(
85 builder.Arm9!,
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;
89 return new(
90 fileSystem,
91 arm9Data,
92 arm9DeclaredLength,
93 arm9TrailingData,
94 arm7Data,
95 arm7DeclaredLength,
96 arm9iData,
97 arm7iData,
98 allocations,
99 arm9OverlayTable,
100 arm7OverlayTable);
101 }
102
111 private static (ReadOnlyMemory<byte> Data, int DeclaredLength) PrepareProgram(
112 NdsProgramDefinition program,
113 bool isArm9,
114 bool ndstoolProfile)
115 {
116 if (!ndstoolProfile)
117 {
118 return (program.Contents, program.Contents.Length);
119 }
120
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)
130 {
131 data[offset] = 0xFF;
132 data[offset + 1] = 0xDE;
133 data[offset + 2] = 0xFF;
134 data[offset + 3] = 0xE7;
135 }
136
137 program.Contents.Span.CopyTo(data.AsSpan(prefixLength));
138 int declaredLength = checked((data.Length + 3) & ~3);
139 return (data, declaredLength);
140 }
141
149 private static ReadOnlyMemory<byte> PrepareArm9TrailingData(
150 NdsProgramDefinition program,
151 bool synthesizeNdstoolFooter)
152 {
153 if (!synthesizeNdstoolFooter)
154 {
155 return program.Footer;
156 }
157
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);
163 return trailing;
164 }
165
171 private static ReadOnlyMemory<byte>[] CollectAllocations(
172 NdsImageBuilder builder,
173 NdsFileSystemBuildSnapshot fileSystem,
174 bool ndstoolProfile)
175 {
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();
184 }
185
191 private static byte[] BuildOverlayTable(
192 IReadOnlyList<NdsOverlayDefinition> overlays,
193 NdsFileSystemBuildSnapshot fileSystem,
194 int firstPrivateFileId)
195 {
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++)
202 {
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)
208 ? 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));
219 }
220
221 return data;
222 }
223}
NdsImageKind
Identifies the hardware family targeted by an image.
NdsImageBuildProfile
Selects a documented Layout personality without contaminating the logical Build Recipe.