16 CancellationToken cancellationToken =
default)
18 ArgumentNullException.ThrowIfNull(left);
19 ArgumentNullException.ThrowIfNull(right);
22 return Compare(leftManifest, rightManifest);
31 ArgumentNullException.ThrowIfNull(left);
32 ArgumentNullException.ThrowIfNull(right);
35 var differences =
new List<NdsSemanticDifference>();
37 CompareDsi(left.
Dsi, right.
Dsi, differences);
40 CompareFiles(left.
Files, right.
Files, differences);
50 private static void CompareDirectories(
51 IEnumerable<string> left,
52 IEnumerable<string> right,
53 List<NdsSemanticDifference> output)
55 var leftSet = left.ToHashSet(StringComparer.Ordinal);
56 var rightSet = right.ToHashSet(StringComparer.Ordinal);
57 output.AddRange(leftSet.Except(rightSet, StringComparer.Ordinal).Select(path =>
59 output.AddRange(rightSet.Except(leftSet, StringComparer.Ordinal).Select(path =>
64 private static void CompareHeader(NdsManifestHeader left, NdsManifestHeader right, List<NdsSemanticDifference> output)
66 AddValue(output,
"Header.Title", left.Title, right.Title);
67 AddValue(output,
"Header.GameCode", left.GameCode, right.GameCode);
68 AddValue(output,
"Header.MakerCode", left.MakerCode, right.MakerCode);
69 AddValue(output,
"Header.Kind", left.Kind, right.Kind);
70 AddValue(output,
"Header.Version", left.Version, right.Version);
71 AddValue(output,
"Header.RegionCode", left.RegionCode, right.RegionCode);
72 AddValue(output,
"Header.AutoStart", left.AutoStart, right.AutoStart);
73 AddValue(output,
"Header.UsedImageSize", left.UsedImageSize, right.UsedImageSize,
NdsDifferenceKind.Relocated);
74 AddValue(output,
"Header.DeviceCapacityBytes", left.DeviceCapacityBytes, right.DeviceCapacityBytes,
NdsDifferenceKind.Relocated);
75 AddValue(output,
"Header.NormalCardControl", left.NormalCardControl, right.NormalCardControl);
76 AddValue(output,
"Header.SecureCardControl", left.SecureCardControl, right.SecureCardControl);
77 AddValue(output,
"Header.Sha256", left.Sha256, right.Sha256);
81 private static void CompareDsi(NdsManifestDsi? left, NdsManifestDsi? right, List<NdsSemanticDifference> output)
83 if (left is
null || right is
null)
85 AddPresence(output,
"Dsi", left, right);
89 AddValue(output,
"Dsi.TitleId", left.TitleId, right.TitleId);
90 AddValue(output,
"Dsi.TotalImageSize", left.TotalImageSize, right.TotalImageSize,
NdsDifferenceKind.Relocated);
91 AddValue(output,
"Dsi.RegionFlags", left.RegionFlags, right.RegionFlags);
92 AddValue(output,
"Dsi.AccessControl", left.AccessControl, right.AccessControl);
93 AddValue(output,
"Dsi.HasModcryptAreas", left.HasModcryptAreas, right.HasModcryptAreas);
94 AddValue(output,
"Dsi.UsesInsecureModcryptKey", left.UsesInsecureModcryptKey, right.UsesInsecureModcryptKey);
95 CompareRegion(
"Dsi.ModcryptArea1", left.ModcryptArea1, right.ModcryptArea1, output);
96 CompareRegion(
"Dsi.ModcryptArea2", left.ModcryptArea2, right.ModcryptArea2, output);
100 private static void ComparePrograms(
101 IEnumerable<NdsManifestProgram> left,
102 IEnumerable<NdsManifestProgram> right,
103 List<NdsSemanticDifference> output)
105 Dictionary<NdsProcessor, NdsManifestProgram> leftMap = left.ToDictionary(
static value => value.Processor);
106 Dictionary<NdsProcessor, NdsManifestProgram> rightMap = right.ToDictionary(
static value => value.Processor);
107 foreach (
NdsProcessor processor
in leftMap.Keys.Union(rightMap.Keys).Order())
109 string path = $
"Programs[{processor}]";
110 bool hasBefore = leftMap.TryGetValue(processor, out NdsManifestProgram? before);
111 bool hasAfter = rightMap.TryGetValue(processor, out NdsManifestProgram? after);
112 if (!hasBefore || !hasAfter)
114 AddPresence(output, path, before, after);
118 NdsManifestProgram leftProgram = before!;
119 NdsManifestProgram rightProgram = after!;
120 AddValue(output, path +
".Sha256", leftProgram.Sha256, rightProgram.Sha256);
121 AddValue(output, path +
".Offset", leftProgram.Offset, rightProgram.Offset,
NdsDifferenceKind.Relocated);
122 AddValue(output, path +
".Length", leftProgram.Length, rightProgram.Length);
123 AddValue(output, path +
".LoadAddress", leftProgram.LoadAddress, rightProgram.LoadAddress);
124 AddValue(output, path +
".EntryAddress", leftProgram.EntryAddress, rightProgram.EntryAddress);
129 private static void CompareFiles(
130 IEnumerable<NdsManifestFile> left,
131 IEnumerable<NdsManifestFile> right,
132 List<NdsSemanticDifference> output)
134 Dictionary<string, NdsManifestFile> leftMap = left.ToDictionary(
static value => value.Path, StringComparer.Ordinal);
135 Dictionary<string, NdsManifestFile> rightMap = right.ToDictionary(
static value => value.Path, StringComparer.Ordinal);
136 foreach (
string path
in leftMap.Keys.Intersect(rightMap.Keys, StringComparer.Ordinal).Order(StringComparer.Ordinal))
138 CompareFile(
"Files[" + path +
"]", leftMap[path], rightMap[path], output);
141 List<NdsManifestFile> removed = leftMap.Values.Where(file => !rightMap.ContainsKey(file.Path)).ToList();
142 List<NdsManifestFile> added = rightMap.Values.Where(file => !leftMap.ContainsKey(file.Path)).ToList();
143 foreach (NdsManifestFile before
in removed.ToArray())
145 NdsManifestFile[] matches = added
146 .Where(after => after.Sha256 == before.Sha256 && after.Length == before.Length)
148 if (matches.Length != 1 || removed.Count(candidate => candidate.Sha256 == before.Sha256 && candidate.Length == before.Length) != 1)
153 NdsManifestFile after = matches[0];
155 CompareFile(
"Files[" + after.Path +
"]", before, after, output);
156 removed.Remove(before);
160 output.AddRange(removed.Select(file =>
new NdsSemanticDifference(
162 output.AddRange(added.Select(file =>
new NdsSemanticDifference(
167 private static void CompareFile(
169 NdsManifestFile left,
170 NdsManifestFile right,
171 List<NdsSemanticDifference> output)
173 AddValue(output, path +
".Sha256", left.Sha256, right.Sha256);
174 AddValue(output, path +
".FileId", left.FileId, right.FileId,
NdsDifferenceKind.Renumbered);
175 AddValue(output, path +
".Offset", left.Offset, right.Offset,
NdsDifferenceKind.Relocated);
176 AddValue(output, path +
".Length", left.Length, right.Length);
180 private static void CompareAllocations(
181 IEnumerable<NdsManifestAllocation> left,
182 IEnumerable<NdsManifestAllocation> right,
183 List<NdsSemanticDifference> output)
185 Dictionary<int, NdsManifestAllocation> leftMap = left.ToDictionary(
static value => value.FileId);
186 Dictionary<int, NdsManifestAllocation> rightMap = right.ToDictionary(
static value => value.FileId);
187 foreach (
int fileId
in leftMap.Keys.Union(rightMap.Keys).Order())
189 string path = $
"Allocations[{fileId}]";
190 bool hasBefore = leftMap.TryGetValue(fileId, out NdsManifestAllocation? before);
191 bool hasAfter = rightMap.TryGetValue(fileId, out NdsManifestAllocation? after);
192 if (!hasBefore || !hasAfter)
194 AddPresence(output, path, before, after);
198 NdsManifestAllocation leftAllocation = before!;
199 NdsManifestAllocation rightAllocation = after!;
200 AddValue(output, path +
".Sha256", leftAllocation.Sha256, rightAllocation.Sha256);
201 AddValue(output, path +
".Offset", leftAllocation.Offset, rightAllocation.Offset,
NdsDifferenceKind.Relocated);
202 AddValue(output, path +
".Length", leftAllocation.Length, rightAllocation.Length);
207 private static void CompareOverlays(
208 IEnumerable<NdsManifestOverlay> left,
209 IEnumerable<NdsManifestOverlay> right,
210 List<NdsSemanticDifference> output)
212 Dictionary<string, NdsManifestOverlay> leftMap = left.ToDictionary(OverlayKey, StringComparer.Ordinal);
213 Dictionary<string, NdsManifestOverlay> rightMap = right.ToDictionary(OverlayKey, StringComparer.Ordinal);
214 foreach (
string key
in leftMap.Keys.Union(rightMap.Keys, StringComparer.Ordinal).Order(StringComparer.Ordinal))
216 string path =
"Overlays[" + key +
"]";
217 bool hasBefore = leftMap.TryGetValue(key, out NdsManifestOverlay? before);
218 bool hasAfter = rightMap.TryGetValue(key, out NdsManifestOverlay? after);
219 if (!hasBefore || !hasAfter)
221 AddPresence(output, path, before, after);
225 NdsManifestOverlay leftOverlay = before!;
226 NdsManifestOverlay rightOverlay = after!;
227 AddValue(output, path +
".Sha256", leftOverlay.Sha256, rightOverlay.Sha256);
228 AddValue(output, path +
".FileId", leftOverlay.FileId, rightOverlay.FileId,
NdsDifferenceKind.Renumbered);
229 AddValue(output, path +
".FilePath", leftOverlay.FilePath, rightOverlay.FilePath,
NdsDifferenceKind.Moved);
230 AddValue(output, path +
".Offset", leftOverlay.Offset, rightOverlay.Offset,
NdsDifferenceKind.Relocated);
231 AddValue(output, path +
".Length", leftOverlay.Length, rightOverlay.Length);
232 AddValue(output, path +
".LoadAddress", leftOverlay.LoadAddress, rightOverlay.LoadAddress);
233 AddValue(output, path +
".RamSize", leftOverlay.RamSize, rightOverlay.RamSize);
234 AddValue(output, path +
".BssSize", leftOverlay.BssSize, rightOverlay.BssSize);
235 AddValue(output, path +
".StaticInitializerStart", leftOverlay.StaticInitializerStart, rightOverlay.StaticInitializerStart);
236 AddValue(output, path +
".StaticInitializerEnd", leftOverlay.StaticInitializerEnd, rightOverlay.StaticInitializerEnd);
237 AddValue(output, path +
".CompressedSize", leftOverlay.CompressedSize, rightOverlay.CompressedSize);
238 AddValue(output, path +
".Flags", leftOverlay.Flags, rightOverlay.Flags);
243 private static void CompareBanner(NdsManifestBanner? left, NdsManifestBanner? right, List<NdsSemanticDifference> output)
245 if (left is
null || right is
null)
247 AddPresence(output,
"Banner", left, right);
251 AddValue(output,
"Banner.Sha256", left.Sha256, right.Sha256);
252 AddValue(output,
"Banner.Offset", left.Offset, right.Offset,
NdsDifferenceKind.Relocated);
253 AddValue(output,
"Banner.Length", left.Length, right.Length);
254 AddValue(output,
"Banner.Version", left.Version, right.Version);
255 AddValue(output,
"Banner.IsAnimated", left.IsAnimated, right.IsAnimated);
256 foreach (
string language
in left.Titles.Keys.Union(right.Titles.Keys, StringComparer.Ordinal).Order(StringComparer.Ordinal))
258 left.Titles.TryGetValue(language, out
string? before);
259 right.Titles.TryGetValue(language, out
string? after);
260 AddValue(output, $
"Banner.Titles[{language}]", before, after);
265 private static void CompareRegion(
267 NdsManifestRegion left,
268 NdsManifestRegion right,
269 List<NdsSemanticDifference> output)
271 AddValue(output, path +
".Offset", left.Offset, right.Offset,
NdsDifferenceKind.Relocated);
272 AddValue(output, path +
".Length", left.Length, right.Length,
NdsDifferenceKind.Relocated);
276 private static void AddPresence(List<NdsSemanticDifference> output,
string path,
object? left,
object? right)
278 if (left is
null && right is not
null)
282 else if (left is not
null && right is
null)
289 private static void AddValue<T>(
290 List<NdsSemanticDifference> output,
296 if (EqualityComparer<T>.Default.Equals(left, right))
301 output.Add(
new(path, kind, Format(left), Format(right)));
305 private static string? Format<T>(T value) => value
switch
308 IFormattable formattable => formattable.ToString(
null, CultureInfo.InvariantCulture),
309 _ => value.ToString(),
313 private static string OverlayKey(NdsManifestOverlay overlay) => $
"{overlay.Processor}:{overlay.OverlayId}";