NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsHostDirectoryImporter.cs
1namespace NdsForge;
2
4internal static class NdsHostDirectoryImporter
5{
12 public static async ValueTask<NdsHostDirectorySnapshot> StageAsync(
13 string sourceDirectory,
14 string destinationDirectory,
15 NdsDirectoryImportOptions options,
16 CancellationToken cancellationToken)
17 {
18 string sourceRoot = Path.GetFullPath(sourceDirectory);
19 if (!Directory.Exists(sourceRoot))
20 {
21 throw new DirectoryNotFoundException($"Host import directory was not found: {sourceRoot}");
22 }
23
24 if ((File.GetAttributes(sourceRoot) & FileAttributes.ReparsePoint) != 0)
25 {
26 throw new IOException("The host import root cannot itself be a link or reparse point.");
27 }
28
29 var directories = new List<string> { destinationDirectory };
30 var files = new List<NdsHostFileSnapshot>();
31 var pending = new Stack<(string Host, string Relative)>();
32 pending.Push((sourceRoot, string.Empty));
33 long totalBytes = 0;
34 int skipped = 0;
35 while (pending.Count != 0)
36 {
37 cancellationToken.ThrowIfCancellationRequested();
38 (string hostDirectory, string relativeDirectory) = pending.Pop();
39 string[] entries = Directory.EnumerateFileSystemEntries(hostDirectory)
40 .Order(StringComparer.Ordinal)
41 .ToArray();
42 for (int index = entries.Length - 1; index >= 0; index--)
43 {
44 string entry = entries[index];
45 FileAttributes attributes = File.GetAttributes(entry);
46 if ((attributes & FileAttributes.ReparsePoint) != 0)
47 {
48 if (options.LinkPolicy == NdsHostLinkPolicy.Reject)
49 {
50 throw new IOException($"Host import encountered a link or reparse point: {entry}");
51 }
52
53 skipped++;
54 continue;
55 }
56
57 string relative = string.IsNullOrEmpty(relativeDirectory)
58 ? Path.GetFileName(entry)
59 : relativeDirectory + "/" + Path.GetFileName(entry);
60 string target = Combine(destinationDirectory, relative);
61 if ((attributes & FileAttributes.Directory) != 0)
62 {
63 directories.Add(target);
64 pending.Push((entry, relative));
65 continue;
66 }
67
68 if (files.Count >= options.MaximumFiles)
69 {
70 throw new InvalidDataException("Host directory import exceeds the configured file-count limit.");
71 }
72 long length = new FileInfo(entry).Length;
73 if (length > options.MaximumTotalBytes - totalBytes || length > Array.MaxLength)
74 {
75 throw new InvalidDataException("Host directory import exceeds the configured byte allocation limit.");
76 }
77
78 byte[] contents = await File.ReadAllBytesAsync(entry, cancellationToken).ConfigureAwait(false);
79 if (contents.LongLength > options.MaximumTotalBytes - totalBytes)
80 {
81 throw new InvalidDataException("A host file changed size and exceeded the import allocation limit while being read.");
82 }
83
84 totalBytes = checked(totalBytes + contents.LongLength);
85 files.Add(new(target, contents));
86 }
87 }
88
89 return new(
90 directories.Distinct(StringComparer.Ordinal).Order(StringComparer.Ordinal).ToArray(),
91 files.OrderBy(static value => value.Path, StringComparer.Ordinal).ToArray(),
92 totalBytes,
93 skipped);
94 }
95
97 private static string Combine(string destinationDirectory, string relativePath) =>
98 NdsFileSystemBuilder.NormalizePath(
99 destinationDirectory == "/" ? "/" + relativePath : destinationDirectory + "/" + relativePath,
100 allowRoot: true);
101}
102
106internal sealed record NdsHostFileSnapshot(string Path, byte[] Contents);
107
113internal sealed record NdsHostDirectorySnapshot(
114 string[] Directories,
115 NdsHostFileSnapshot[] Files,
116 long TotalBytes,
117 int SkippedLinks);
NdsHostLinkPolicy
Controls treatment of host reparse points and symbolic links during bounded directory ingestion.