NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsFileSystem.cs
1using System.Diagnostics.CodeAnalysis;
2
3namespace NdsForge;
4
6public sealed class NdsFileSystem
7{
9 private readonly Dictionary<string, NdsFile> _filesByPath;
11 private readonly Dictionary<int, NdsFile> _filesById;
13 private readonly Dictionary<string, NdsDirectory> _directoriesByPath;
14
20 internal NdsFileSystem(
21 NdsDirectory root,
22 IReadOnlyList<NdsDirectory> directories,
23 IReadOnlyList<NdsFile> files,
24 IReadOnlyList<NdsFileAllocation> allocations)
25 {
26 Root = root;
27 Directories = directories;
28 Files = files;
29 Allocations = allocations;
30 _filesByPath = files.ToDictionary(static file => file.FullPath, StringComparer.Ordinal);
31 _filesById = files.ToDictionary(static file => file.Id);
32 _directoriesByPath = directories.ToDictionary(static directory => directory.FullPath, StringComparer.Ordinal);
33 }
34
36 public NdsDirectory Root { get; }
37
39 public IReadOnlyList<NdsDirectory> Directories { get; }
40
42 public IReadOnlyList<NdsFile> Files { get; }
43
45 public IReadOnlyList<NdsFileAllocation> Allocations { get; }
46
50 public NdsFile GetFile(string path)
51 {
52 string normalized = NormalizePath(path);
53 return _filesByPath.TryGetValue(normalized, out NdsFile? file)
54 ? file
55 : throw new FileNotFoundException($"NitroFS file '{normalized}' was not found.", normalized);
56 }
57
62 public bool TryGetFile(string path, [NotNullWhen(true)] out NdsFile? file) =>
63 _filesByPath.TryGetValue(NormalizePath(path), out file);
64
68 public NdsDirectory GetDirectory(string path)
69 {
70 string normalized = NormalizeDirectoryPath(path);
71 return _directoriesByPath.TryGetValue(normalized, out NdsDirectory? directory)
72 ? directory
73 : throw new DirectoryNotFoundException($"NitroFS directory '{normalized}' was not found.");
74 }
75
80 public bool TryGetDirectory(string path, [NotNullWhen(true)] out NdsDirectory? directory) =>
81 _directoriesByPath.TryGetValue(NormalizeDirectoryPath(path), out directory);
82
86 public NdsFile GetFile(int fileId) => _filesById.TryGetValue(fileId, out NdsFile? file)
87 ? file
88 : throw new KeyNotFoundException($"No named NitroFS file has ID {fileId}.");
89
94 internal bool TryGetFile(int fileId, [NotNullWhen(true)] out NdsFile? file) =>
95 _filesById.TryGetValue(fileId, out file);
96
100 private static string NormalizePath(string path)
101 {
102 ArgumentException.ThrowIfNullOrWhiteSpace(path);
103 string normalized = path.Replace('\\', '/');
104 if (!normalized.StartsWith('/'))
105 {
106 normalized = "/" + normalized;
107 }
108
109 if (normalized.EndsWith('/') ||
110 normalized.Contains("//", StringComparison.Ordinal))
111 {
112 throw new ArgumentException("NitroFS paths must identify a file and contain no empty segments.", nameof(path));
113 }
114
115 foreach (string segment in normalized.Split('/', StringSplitOptions.RemoveEmptyEntries))
116 {
117 if (segment is "." or "..")
118 {
119 throw new ArgumentException("NitroFS paths cannot contain traversal segments.", nameof(path));
120 }
121 }
122
123 return normalized;
124 }
125
129 private static string NormalizeDirectoryPath(string path)
130 {
131 ArgumentException.ThrowIfNullOrWhiteSpace(path);
132 string normalized = path.Replace('\\', '/');
133 if (!normalized.StartsWith('/'))
134 {
135 normalized = "/" + normalized;
136 }
137
138 if (normalized.Length > 1 && normalized.EndsWith('/'))
139 {
140 normalized = normalized.TrimEnd('/');
141 }
142
143 if (normalized.Contains("//", StringComparison.Ordinal) ||
144 normalized.Split('/', StringSplitOptions.RemoveEmptyEntries).Any(static segment => segment is "." or ".."))
145 {
146 throw new ArgumentException("NitroFS directory paths cannot contain empty or traversal segments.", nameof(path));
147 }
148
149 return normalized;
150 }
151}
Materializes one FNT directory record as a navigable node while preserving its encoded child order.
bool TryGetFile(string path, [NotNullWhen(true)] out NdsFile? file)
Attempts to find a named file by canonical or root-relative path.
NdsFile GetFile(int fileId)
Resolves a FAT identifier only when the FNT assigns that allocation a visible name.
IReadOnlyList< NdsDirectory > Directories
Gets all reachable directories in directory-ID order.
bool TryGetDirectory(string path, [NotNullWhen(true)] out NdsDirectory? directory)
Attempts direct directory lookup without conflating an absent path with an empty directory.
IReadOnlyList< NdsFile > Files
Gets all named files in file-ID order.
NdsFile GetFile(string path)
Resolves a case-sensitive logical path without applying host-filesystem normalization.
NdsDirectory Root
Anchors hierarchical traversal at directory ID 0xF000 and canonical path /.
NdsDirectory GetDirectory(string path)
Resolves the root or a nested directory by its case-sensitive logical path.
IReadOnlyList< NdsFileAllocation > Allocations
Gets every FAT allocation, including unnamed overlay payloads.
Connects a byte-preserving FNT path and stable FAT identifier to lazily read cartridge bytes.
Definition NdsFile.cs:5