1using System.Security.Cryptography;
9internal static class NdsDsiHeaderWriter
20 public static void Write(
22 NdsImageBuilder builder,
23 NdsImageBuildLayout layout,
24 NdsImageBuildContent content,
25 NdsDsiDigestBuildResult? digestResult)
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);
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)
52 digestResult.MasterHmac.CopyTo(header[0x328..0x33C]);
55 WriteHmacs(header, builder, content, metadata.Integrity);
63 private static void WriteDigestMetadata(
65 NdsImageBuildLayout layout,
66 NdsDsiDigestOptions? options,
67 NdsDsiDigestBuildResult? result)
69 header[0x1E0..0x208].Clear();
70 if (options is
null || result is
null)
75 if (layout.SectorHashTable.Length != result.SectorHashes.Length ||
76 layout.BlockHashTable.Length != result.BlockHashes.Length)
78 throw new InvalidDataException(
"Generated DSi digest bytes disagree with their planned Regions.");
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));
95 public static void FinalizeSignature(Span<byte> header, NdsDsiIntegrityOptions integrity)
99 INdsDsiSignatureProvider provider = integrity.SignatureProvider ??
100 throw new InvalidDataException(
"RSA signature mode has no signing provider.");
101 provider.SignHeader(header[..0xE00], header[0xF80..0x1000]);
105 WriteDevelopmentSignature(header, integrity.SignatureMode);
113 private static void WriteProgram(Span<byte> header,
int offset, NdsRegion region, NdsProgramDefinition program)
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));
124 private static void WriteRegion(Span<byte> header,
int offset, NdsRegion region)
126 NdsBinary.WriteUInt32(header, offset, checked((uint)region.Offset));
127 NdsBinary.WriteUInt32(header, offset + 4, checked((uint)region.Length));
135 private static void ClearAuthenticationFields(Span<byte> header)
137 header[0x300..0x378].Clear();
138 header[0x3A0..0x3B4].Clear();
139 header[0xF80..0x1000].Clear();
147 private static void WriteHmacs(
149 NdsImageBuilder builder,
150 NdsImageBuildContent content,
151 NdsDsiIntegrityOptions integrity)
153 if (!integrity.ComputesHmacSha1)
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);
170 private static void WriteHmac(Span<byte> destination, ReadOnlySpan<byte> key, ReadOnlySpan<byte> data)
172#pragma warning disable CA5350
173 HMACSHA1.HashData(key, data, destination);
174#pragma warning restore CA5350
187 Span<byte> signature = header[0xF80..0x1000];
188 signature.Fill(0xFF);
192#pragma warning disable CA5350
193 SHA1.HashData(header[..0xE00], signature[0x6C..0x80]);
194#pragma warning restore CA5350
NdsDsiSignatureMode
Selects how a DSi build populates the 128-byte signature field under an explicit authenticity policy.