1using System.Security.Cryptography;
6internal static class NdsImageManifestCapture
12 public static async ValueTask<NdsImageManifest> CaptureAsync(
14 CancellationToken cancellationToken)
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))
22 string hash = await HashRegionAsync(image, allocation.Data, cancellationToken).ConfigureAwait(
false);
23 allocationHashes.Add(allocation.FileId, hash);
26 FileId = allocation.FileId,
27 Offset = allocation.Data.Offset,
28 Length = allocation.Data.Length,
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))
36 cancellationToken.ThrowIfCancellationRequested();
41 Offset = file.Data.Offset,
42 Length = file.Data.Length,
43 Sha256 = allocationHashes[file.Id],
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))
51 cancellationToken.ThrowIfCancellationRequested();
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)]
73 NdsHeader header = image.Header;
74 var manifest =
new NdsImageManifest
76 PhysicalLength = image.Length,
77 ImageSha256 = await HashRegionAsync(image,
new(0, image.Length), cancellationToken).ConfigureAwait(
false),
81 GameCode = header.GameCode,
82 MakerCode = header.MakerCode,
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),
93 Dsi = CaptureDsi(header.Dsi),
95 Directories = image.FileSystem.Directories
96 .Select(
static value => value.FullPath)
97 .Order(StringComparer.Ordinal)
99 Files = files.AsReadOnly(),
100 Allocations = allocations.AsReadOnly(),
102 Banner = CaptureBanner(image),
112 private static async ValueTask<NdsManifestProgram[]> CaptureProgramsAsync(
114 CancellationToken cancellationToken)
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))
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),
133 return result.ToArray();
139 private static NdsManifestDsi? CaptureDsi(NdsDsiHeader? dsi) => dsi is
null ? null :
new()
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),
154 private static NdsManifestRegion CaptureRegion(NdsRegion region) =>
new()
156 Offset = region.Offset,
157 Length = region.Length,
163 private static NdsManifestBanner? CaptureBanner(NdsImage image)
165 NdsBanner? banner = image.Banner;
166 return banner is
null ? null :
new()
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),
185 private static async ValueTask<string> HashRegionAsync(
188 CancellationToken cancellationToken)
190 using Stream stream = image.OpenRead(region);
191 byte[] digest = await SHA256.HashDataAsync(stream, cancellationToken).ConfigureAwait(
false);
192 return Convert.ToHexStringLower(digest);
198 private static string HashMemory(ReadOnlySpan<byte> data) => Convert.ToHexStringLower(SHA256.HashData(data));
@ 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.