4internal static class NdsImageBuildVerifier
11 public static async ValueTask VerifyAsync(
13 NdsImageBuilder builder,
14 NdsFileSystemBuildSnapshot fileSystem,
15 CancellationToken cancellationToken)
17 destination.Position = 0;
18 using NdsImage image = await NdsImage.OpenAsync(
21 cancellationToken: cancellationToken).ConfigureAwait(
false);
22 var validationOptions =
new NdsValidationOptions();
23 if (builder.DsiMetadata?.Integrity.HmacKey.IsEmpty ==
false)
25 validationOptions.SetDsiHmacKey(builder.DsiMetadata.Integrity.HmacKey.Span);
28 NdsValidationResult validation = image.Validate(validationOptions);
29 if (!validation.IsValid)
31 throw new InvalidDataException(
32 $
"Generated image verification failed: {string.Join(";
", validation.Diagnostics.Select(static item => item.Message))}");
35 foreach (NdsBuildFile expectedFile
in fileSystem.FilesInIdOrder)
37 NdsFile actualFile = image.FileSystem.GetFile(expectedFile.Path);
38 byte[] actual = await actualFile.ReadAllBytesAsync(cancellationToken).ConfigureAwait(
false);
39 if (!actual.AsSpan().SequenceEqual(expectedFile.Contents.Span))
41 throw new InvalidDataException(
42 $
"Generated image payload verification failed for '{expectedFile.Path}' at File ID {actualFile.Id}.");
46 if (builder.Arm9i is not
null)
48 await VerifyProgramAsync(image, image.Header.Arm9i, builder.Arm9i, cancellationToken).ConfigureAwait(
false);
51 if (builder.Arm7i is not
null)
53 await VerifyProgramAsync(image, image.Header.Arm7i, builder.Arm7i, cancellationToken).ConfigureAwait(
false);
56 await VerifyOverlaysAsync(image, image.Arm9Overlays, builder.Arm9Overlays, cancellationToken).ConfigureAwait(
false);
57 await VerifyOverlaysAsync(image, image.Arm7Overlays, builder.Arm7Overlays, cancellationToken).ConfigureAwait(
false);
65 private static async ValueTask VerifyProgramAsync(
68 NdsProgramDefinition expected,
69 CancellationToken cancellationToken)
71 if (actual is
null || actual.Processor != expected.Processor || actual.LoadAddress != expected.LoadAddress)
73 throw new InvalidDataException($
"Generated {expected.Processor} Program metadata did not round-trip.");
76 using Stream stream = image.OpenRead(actual.Data);
77 byte[] contents =
new byte[expected.Contents.Length];
78 await stream.ReadExactlyAsync(contents, cancellationToken).ConfigureAwait(
false);
79 if (!contents.AsSpan().SequenceEqual(expected.Contents.Span))
81 throw new InvalidDataException($
"Generated {expected.Processor} Program payload did not round-trip.");
90 private static async ValueTask VerifyOverlaysAsync(
92 IReadOnlyList<NdsOverlay> actual,
93 IReadOnlyList<NdsOverlayDefinition> expected,
94 CancellationToken cancellationToken)
96 if (actual.Count != expected.Count)
98 throw new InvalidDataException(
"Generated Overlay table entry count did not round-trip.");
101 for (
int index = 0; index < actual.Count; index++)
103 if (actual[index].Id != expected[index].Id || actual[index].Data is
null)
105 throw new InvalidDataException($
"Generated Overlay record {index} did not round-trip.");
108 string? linkedFilePath = expected[index].EffectiveLinkedFilePath;
109 if (linkedFilePath is not
null)
111 if (actual[index].File?.FullPath != linkedFilePath)
113 throw new InvalidDataException($
"Generated Overlay file link {index} did not round-trip.");
119 using Stream stream = image.OpenRead(actual[index].Data!.Value);
120 byte[] contents =
new byte[expected[index].Contents.Length];
121 await stream.ReadExactlyAsync(contents, cancellationToken).ConfigureAwait(
false);
122 if (!contents.AsSpan().SequenceEqual(expected[index].Contents.Span))
124 throw new InvalidDataException($
"Generated Overlay payload {index} did not round-trip.");