NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsDsiHeaderWriter.cs
1using System.Security.Cryptography;
2
3namespace NdsForge;
4
9internal static class NdsDsiHeaderWriter
10{
20 public static void Write(
21 Span<byte> header,
22 NdsImageBuilder builder,
23 NdsImageBuildLayout layout,
24 NdsImageBuildContent content,
25 NdsDsiDigestBuildResult? digestResult)
26 {
27 NdsDsiBuildMetadata metadata = builder.DsiMetadata!;
28 metadata.ExtensionTemplate.Span.CopyTo(header[0x180..0x1000]);
29 metadata.MemoryBankSettings.Span.CopyTo(header[0x180..0x1B0]);
30 NdsBinary.WriteUInt32(header, 0x1B0, metadata.RegionFlags);
31 NdsBinary.WriteUInt32(header, 0x1B4, metadata.AccessControl);
32 NdsBinary.WriteUInt32(header, 0x1B8, metadata.ScfgExtMask);
33 header[0x1BF] = metadata.ApplicationFlags;
34 WriteProgram(header, 0x1C0, layout.Arm9i!.Value, builder.Arm9i!);
35 WriteProgram(header, 0x1D0, layout.Arm7i!.Value, builder.Arm7i!);
36 NdsBinary.WriteUInt32(header, 0x1D4, metadata.Arm7DeviceListAddress);
37
38 WriteDigestMetadata(header, layout, metadata.Digests, digestResult);
39 NdsBinary.WriteUInt32(header, 0x208, checked((uint)(builder.Banner?.RawData.Length ?? 0)));
40 NdsBinary.WriteUInt32(header, 0x20C, 0x00010000);
41 NdsBinary.WriteUInt32(header, 0x210, checked((uint)layout.PhysicalSize));
42 WriteRegion(header, 0x220, metadata.ResolveModcryptArea(metadata.ModcryptArea1, first: true, layout));
43 WriteRegion(header, 0x228, metadata.ResolveModcryptArea(metadata.ModcryptArea2, first: false, layout));
44 NdsBinary.WriteUInt32(header, 0x230, (uint)metadata.TitleId);
45 NdsBinary.WriteUInt32(header, 0x234, (uint)(metadata.TitleId >> 32));
46 NdsBinary.WriteUInt32(header, 0x238, metadata.PublicSaveSize);
47 NdsBinary.WriteUInt32(header, 0x23C, metadata.PrivateSaveSize);
48 metadata.AgeRatings.Span.CopyTo(header[0x2F0..0x300]);
49 ClearAuthenticationFields(header);
50 if (digestResult is not null)
51 {
52 digestResult.MasterHmac.CopyTo(header[0x328..0x33C]);
53 }
54
55 WriteHmacs(header, builder, content, metadata.Integrity);
56 }
57
63 private static void WriteDigestMetadata(
64 Span<byte> header,
65 NdsImageBuildLayout layout,
66 NdsDsiDigestOptions? options,
67 NdsDsiDigestBuildResult? result)
68 {
69 header[0x1E0..0x208].Clear();
70 if (options is null || result is null)
71 {
72 return;
73 }
74
75 if (layout.SectorHashTable.Length != result.SectorHashes.Length ||
76 layout.BlockHashTable.Length != result.BlockHashes.Length)
77 {
78 throw new InvalidDataException("Generated DSi digest bytes disagree with their planned Regions.");
79 }
80
81 WriteRegion(header, 0x1E0, layout.NtrDigest);
82 WriteRegion(header, 0x1E8, layout.TwlDigest);
83 WriteRegion(header, 0x1F0, layout.SectorHashTable);
84 WriteRegion(header, 0x1F8, layout.BlockHashTable);
85 NdsBinary.WriteUInt32(header, 0x200, checked((uint)options.SectorSize));
86 NdsBinary.WriteUInt32(header, 0x204, checked((uint)options.BlockSectorCount));
87 }
88
95 public static void FinalizeSignature(Span<byte> header, NdsDsiIntegrityOptions integrity)
96 {
97 if (integrity.SignatureMode == NdsDsiSignatureMode.RsaSha1)
98 {
99 INdsDsiSignatureProvider provider = integrity.SignatureProvider ??
100 throw new InvalidDataException("RSA signature mode has no signing provider.");
101 provider.SignHeader(header[..0xE00], header[0xF80..0x1000]);
102 return;
103 }
104
105 WriteDevelopmentSignature(header, integrity.SignatureMode);
106 }
107
113 private static void WriteProgram(Span<byte> header, int offset, NdsRegion region, NdsProgramDefinition program)
114 {
115 NdsBinary.WriteUInt32(header, offset, checked((uint)region.Offset));
116 NdsBinary.WriteUInt32(header, offset + 8, program.LoadAddress);
117 NdsBinary.WriteUInt32(header, offset + 12, checked((uint)region.Length));
118 }
119
124 private static void WriteRegion(Span<byte> header, int offset, NdsRegion region)
125 {
126 NdsBinary.WriteUInt32(header, offset, checked((uint)region.Offset));
127 NdsBinary.WriteUInt32(header, offset + 4, checked((uint)region.Length));
128 }
129
135 private static void ClearAuthenticationFields(Span<byte> header)
136 {
137 header[0x300..0x378].Clear();
138 header[0x3A0..0x3B4].Clear();
139 header[0xF80..0x1000].Clear();
140 }
141
147 private static void WriteHmacs(
148 Span<byte> header,
149 NdsImageBuilder builder,
150 NdsImageBuildContent content,
151 NdsDsiIntegrityOptions integrity)
152 {
153 if (!integrity.ComputesHmacSha1)
154 {
155 return;
156 }
157
158 WriteHmac(header[0x300..0x314], integrity.HmacKey.Span, content.Arm9Data.Span);
159 WriteHmac(header[0x314..0x328], integrity.HmacKey.Span, content.Arm7Data.Span);
160 ReadOnlySpan<byte> bannerData = builder.Banner is null ? [] : builder.Banner.RawData.Span;
161 WriteHmac(header[0x33C..0x350], integrity.HmacKey.Span, bannerData);
162 WriteHmac(header[0x350..0x364], integrity.HmacKey.Span, content.Arm9iData.Span);
163 WriteHmac(header[0x364..0x378], integrity.HmacKey.Span, content.Arm7iData.Span);
164 }
165
170 private static void WriteHmac(Span<byte> destination, ReadOnlySpan<byte> key, ReadOnlySpan<byte> data)
171 {
172#pragma warning disable CA5350 // The DSi header format mandates HMAC-SHA1; this API does not recommend SHA-1 for new protocols.
173 HMACSHA1.HashData(key, data, destination);
174#pragma warning restore CA5350
175 }
176
180 private static void WriteDevelopmentSignature(Span<byte> header, NdsDsiSignatureMode mode)
181 {
182 if (mode != NdsDsiSignatureMode.NoGbaDevelopmentMarker)
183 {
184 return;
185 }
186
187 Span<byte> signature = header[0xF80..0x1000];
188 signature.Fill(0xFF);
189 signature[0] = 0;
190 signature[1] = 1;
191 signature[0x6B] = 0;
192#pragma warning disable CA5350 // The no$gba development marker is defined as SHA-1 and is explicitly documented as non-authenticating.
193 SHA1.HashData(header[..0xE00], signature[0x6C..0x80]);
194#pragma warning restore CA5350
195 }
196}
NdsDsiSignatureMode
Selects how a DSi build populates the 128-byte signature field under an explicit authenticity policy.