NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsImageLoader.cs
1namespace NdsForge;
2
6internal static class NdsImageLoader
7{
9 private const int BaseHeaderLength = 0x200;
10
12 private const int DsiHeaderLength = 0x1000;
13
19 public static async ValueTask<NdsImage> OpenPathAsync(
20 string path,
21 NdsReadOptions? options,
22 CancellationToken cancellationToken)
23 {
24 ArgumentException.ThrowIfNullOrWhiteSpace(path);
25 var source = new FileImageDataSource(path);
26 try
27 {
28 return await LoadAsync(source, options, cancellationToken).ConfigureAwait(false);
29 }
30 catch
31 {
32 await source.DisposeAsync().ConfigureAwait(false);
33 throw;
34 }
35 }
36
42 public static NdsImage OpenStream(Stream stream, bool leaveOpen, NdsReadOptions? options)
43 {
44 var source = new StreamImageDataSource(stream, leaveOpen);
45 try
46 {
47 return Load(source, options);
48 }
49 catch
50 {
51 source.Dispose();
52 throw;
53 }
54 }
55
62 public static async ValueTask<NdsImage> OpenStreamAsync(
63 Stream stream,
64 bool leaveOpen,
65 NdsReadOptions? options,
66 CancellationToken cancellationToken)
67 {
68 var source = new StreamImageDataSource(stream, leaveOpen);
69 try
70 {
71 return await LoadAsync(source, options, cancellationToken).ConfigureAwait(false);
72 }
73 catch
74 {
75 await source.DisposeAsync().ConfigureAwait(false);
76 throw;
77 }
78 }
79
84 public static NdsImage LoadMemory(ReadOnlyMemory<byte> data, NdsReadOptions? options)
85 {
86 var source = new MemoryImageDataSource(data);
87 try
88 {
89 return Load(source, options);
90 }
91 catch
92 {
93 source.Dispose();
94 throw;
95 }
96 }
97
102 private static NdsImage Load(IImageDataSource source, NdsReadOptions? options)
103 {
104 options ??= NdsReadOptions.Default;
105 options.Validate();
106 NdsHeader header = ReadHeader(source);
107 DetectArm9Footer(source, header);
108 NdsFileSystem fileSystem = NitroFileSystemParser.Parse(source, header, options);
109 IReadOnlyList<NdsOverlay> arm9Overlays = NdsOverlayParser.Parse(
110 source, header.Arm9OverlayTable, NdsProcessor.Arm9, fileSystem, options);
111 IReadOnlyList<NdsOverlay> arm7Overlays = NdsOverlayParser.Parse(
112 source, header.Arm7OverlayTable, NdsProcessor.Arm7, fileSystem, options);
113 NdsBanner? banner = NdsBannerParser.Parse(source, header.BannerOffset, options);
114 return new NdsImage(source, header, fileSystem, arm9Overlays, arm7Overlays, banner);
115 }
116
122 private static async ValueTask<NdsImage> LoadAsync(
123 IImageDataSource source,
124 NdsReadOptions? options,
125 CancellationToken cancellationToken)
126 {
127 options ??= NdsReadOptions.Default;
128 options.Validate();
129 NdsHeader header = await ReadHeaderAsync(source, cancellationToken).ConfigureAwait(false);
130 await DetectArm9FooterAsync(source, header, cancellationToken).ConfigureAwait(false);
131 NdsFileSystem fileSystem = await NitroFileSystemParser.ParseAsync(
132 source, header, options, cancellationToken).ConfigureAwait(false);
133 IReadOnlyList<NdsOverlay> arm9Overlays = await NdsOverlayParser.ParseAsync(
134 source, header.Arm9OverlayTable, NdsProcessor.Arm9, fileSystem, options, cancellationToken)
135 .ConfigureAwait(false);
136 IReadOnlyList<NdsOverlay> arm7Overlays = await NdsOverlayParser.ParseAsync(
137 source, header.Arm7OverlayTable, NdsProcessor.Arm7, fileSystem, options, cancellationToken)
138 .ConfigureAwait(false);
139 NdsBanner? banner = await NdsBannerParser.ParseAsync(
140 source, header.BannerOffset, options, cancellationToken).ConfigureAwait(false);
141 return new NdsImage(source, header, fileSystem, arm9Overlays, arm7Overlays, banner);
142 }
143
147 private static NdsHeader ReadHeader(IImageDataSource source)
148 {
149 byte[] baseHeader = new byte[BaseHeaderLength];
150 source.ReadExactly(0, baseHeader);
151 int length = (baseHeader[0x12] & 2) == 0 ? BaseHeaderLength : DsiHeaderLength;
152 if (length == BaseHeaderLength)
153 {
154 return new NdsHeader(baseHeader);
155 }
156
157 byte[] fullHeader = new byte[length];
158 baseHeader.CopyTo(fullHeader, 0);
159 source.ReadExactly(BaseHeaderLength, fullHeader.AsSpan(BaseHeaderLength));
160 return new NdsHeader(fullHeader);
161 }
162
167 private static async ValueTask<NdsHeader> ReadHeaderAsync(
168 IImageDataSource source,
169 CancellationToken cancellationToken)
170 {
171 byte[] baseHeader = new byte[BaseHeaderLength];
172 await source.ReadExactlyAsync(0, baseHeader, cancellationToken).ConfigureAwait(false);
173 int length = (baseHeader[0x12] & 2) == 0 ? BaseHeaderLength : DsiHeaderLength;
174 if (length == BaseHeaderLength)
175 {
176 return new NdsHeader(baseHeader);
177 }
178
179 byte[] fullHeader = new byte[length];
180 baseHeader.CopyTo(fullHeader, 0);
181 await source.ReadExactlyAsync(
182 BaseHeaderLength,
183 fullHeader.AsMemory(BaseHeaderLength),
184 cancellationToken).ConfigureAwait(false);
185 return new NdsHeader(fullHeader);
186 }
187
192 private static void DetectArm9Footer<TSource>(TSource source, NdsHeader header)
193 where TSource : IImageDataSource
194 {
195 if (header.Arm9.Data.End > source.Length - 12)
196 {
197 return;
198 }
199
200 Span<byte> marker = stackalloc byte[4];
201 source.ReadExactly(header.Arm9.Data.End, marker);
202 if (NdsBinary.ReadUInt32(marker, 0) == 0xDEC00621)
203 {
204 header.Arm9.Footer = new(header.Arm9.Data.End, 12);
205 }
206 }
207
213 private static async ValueTask DetectArm9FooterAsync<TSource>(
214 TSource source,
215 NdsHeader header,
216 CancellationToken cancellationToken)
217 where TSource : IImageDataSource
218 {
219 if (header.Arm9.Data.End > source.Length - 12)
220 {
221 return;
222 }
223
224 byte[] marker = new byte[4];
225 await source.ReadExactlyAsync(header.Arm9.Data.End, marker, cancellationToken).ConfigureAwait(false);
226 if (NdsBinary.ReadUInt32(marker, 0) == 0xDEC00621)
227 {
228 header.Arm9.Footer = new(header.Arm9.Data.End, 12);
229 }
230 }
231}
NdsProcessor
Identifies a processor and execution mode.