NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsImageManifestCapture.cs
1using System.Security.Cryptography;
2
3namespace NdsForge;
4
6internal static class NdsImageManifestCapture
7{
12 public static async ValueTask<NdsImageManifest> CaptureAsync(
13 NdsImage image,
14 CancellationToken cancellationToken)
15 {
16 ArgumentNullException.ThrowIfNull(image);
17 NdsManifestProgram[] programs = await CaptureProgramsAsync(image, cancellationToken).ConfigureAwait(false);
18 var allocations = new List<NdsManifestAllocation>(image.FileSystem.Allocations.Count);
19 var allocationHashes = new Dictionary<int, string>(image.FileSystem.Allocations.Count);
20 foreach (NdsFileAllocation allocation in image.FileSystem.Allocations.OrderBy(static value => value.FileId))
21 {
22 string hash = await HashRegionAsync(image, allocation.Data, cancellationToken).ConfigureAwait(false);
23 allocationHashes.Add(allocation.FileId, hash);
24 allocations.Add(new()
25 {
26 FileId = allocation.FileId,
27 Offset = allocation.Data.Offset,
28 Length = allocation.Data.Length,
29 Sha256 = hash,
30 });
31 }
32
33 var files = new List<NdsManifestFile>(image.FileSystem.Files.Count);
34 foreach (NdsFile file in image.FileSystem.Files.OrderBy(static value => value.FullPath, StringComparer.Ordinal))
35 {
36 cancellationToken.ThrowIfCancellationRequested();
37 files.Add(new()
38 {
39 Path = file.FullPath,
40 FileId = file.Id,
41 Offset = file.Data.Offset,
42 Length = file.Data.Length,
43 Sha256 = allocationHashes[file.Id],
44 });
45 }
46
47 var overlays = new List<NdsManifestOverlay>();
48 foreach (NdsOverlay overlay in image.Arm9Overlays.Concat(image.Arm7Overlays)
49 .OrderBy(static value => value.Processor).ThenBy(static value => value.Id))
50 {
51 cancellationToken.ThrowIfCancellationRequested();
52 overlays.Add(new()
53 {
54 Processor = overlay.Processor,
55 OverlayId = overlay.Id,
56 FileId = overlay.FileId,
57 FilePath = overlay.File?.FullPath,
58 Offset = overlay.Data?.Offset,
59 Length = overlay.Data?.Length,
60 LoadAddress = overlay.LoadAddress,
61 RamSize = overlay.RamSize,
62 BssSize = overlay.BssSize,
63 StaticInitializerStart = overlay.StaticInitializerStart,
64 StaticInitializerEnd = overlay.StaticInitializerEnd,
65 CompressedSize = overlay.CompressedSize,
66 Flags = overlay.Flags,
67 Sha256 = overlay.Data is not null
68 ? allocationHashes[checked((int)overlay.FileId)]
69 : null,
70 });
71 }
72
73 NdsHeader header = image.Header;
74 var manifest = new NdsImageManifest
75 {
76 PhysicalLength = image.Length,
77 ImageSha256 = await HashRegionAsync(image, new(0, image.Length), cancellationToken).ConfigureAwait(false),
78 Header = new()
79 {
80 Title = header.Title,
81 GameCode = header.GameCode,
82 MakerCode = header.MakerCode,
83 Kind = header.Kind,
84 Version = header.Version,
85 RegionCode = header.RegionCode,
86 AutoStart = header.AutoStart,
87 UsedImageSize = header.UsedImageSize,
88 DeviceCapacityBytes = header.DeviceCapacityBytes,
89 NormalCardControl = header.NormalCardControl,
90 SecureCardControl = header.SecureCardControl,
91 Sha256 = HashMemory(header.RawData.Span),
92 },
93 Dsi = CaptureDsi(header.Dsi),
94 Programs = programs,
95 Directories = image.FileSystem.Directories
96 .Select(static value => value.FullPath)
97 .Order(StringComparer.Ordinal)
98 .ToArray(),
99 Files = files.AsReadOnly(),
100 Allocations = allocations.AsReadOnly(),
101 Overlays = overlays.AsReadOnly(),
102 Banner = CaptureBanner(image),
103 };
104 manifest.Validate();
105 return manifest;
106 }
107
112 private static async ValueTask<NdsManifestProgram[]> CaptureProgramsAsync(
113 NdsImage image,
114 CancellationToken cancellationToken)
115 {
116 NdsProgram[] programs = [image.Header.Arm9, image.Header.Arm7];
117 IEnumerable<NdsProgram> all = programs.Concat(
118 new[] { image.Header.Arm9i, image.Header.Arm7i }.OfType<NdsProgram>());
119 var result = new List<NdsManifestProgram>();
120 foreach (NdsProgram program in all.OrderBy(static value => value.Processor))
121 {
122 result.Add(new()
123 {
124 Processor = program.Processor,
125 Offset = program.Data.Offset,
126 Length = program.Data.Length,
127 LoadAddress = program.LoadAddress,
128 EntryAddress = program.EntryAddress,
129 Sha256 = await HashRegionAsync(image, program.Data, cancellationToken).ConfigureAwait(false),
130 });
131 }
132
133 return result.ToArray();
134 }
135
139 private static NdsManifestDsi? CaptureDsi(NdsDsiHeader? dsi) => dsi is null ? null : new()
140 {
141 TitleId = dsi.TitleId,
142 TotalImageSize = dsi.TotalImageSize,
143 RegionFlags = dsi.RegionFlags,
144 AccessControl = dsi.AccessControl,
145 HasModcryptAreas = dsi.HasModcryptAreas,
146 UsesInsecureModcryptKey = dsi.UsesInsecureModcryptKey,
147 ModcryptArea1 = CaptureRegion(dsi.ModcryptArea1),
148 ModcryptArea2 = CaptureRegion(dsi.ModcryptArea2),
149 };
150
154 private static NdsManifestRegion CaptureRegion(NdsRegion region) => new()
155 {
156 Offset = region.Offset,
157 Length = region.Length,
158 };
159
163 private static NdsManifestBanner? CaptureBanner(NdsImage image)
164 {
165 NdsBanner? banner = image.Banner;
166 return banner is null ? null : new()
167 {
168 Offset = image.Header.BannerOffset,
169 Length = banner.RawData.Length,
170 Version = banner.Version,
171 IsAnimated = banner.IsAnimated,
172 Titles = banner.Titles.ToDictionary(
173 static pair => pair.Key.ToString(),
174 static pair => pair.Value,
175 StringComparer.Ordinal),
176 Sha256 = HashMemory(banner.RawData.Span),
177 };
178 }
179
185 private static async ValueTask<string> HashRegionAsync(
186 NdsImage image,
187 NdsRegion region,
188 CancellationToken cancellationToken)
189 {
190 using Stream stream = image.OpenRead(region);
191 byte[] digest = await SHA256.HashDataAsync(stream, cancellationToken).ConfigureAwait(false);
192 return Convert.ToHexStringLower(digest);
193 }
194
198 private static string HashMemory(ReadOnlySpan<byte> data) => Convert.ToHexStringLower(SHA256.HashData(data));
199}
@ Overlays
Overlay tables and resolved payloads.
@ Banner
Exports the complete version-sized banner structure, including reserved and animated DSi bytes.
@ Programs
ARM7, ARM9, ARM7i, and ARM9i programs.
@ Header
The parsed header bytes.