NdsForge.NET 1.0.1
Read, validate, edit, compare, and build Nintendo DS and DSi images from .NET
Loading...
Searching...
No Matches
NdsFileNameTableWriter.cs
1using System.Buffers.Binary;
2using System.Text;
3
4namespace NdsForge;
5
9internal static class NdsFileNameTableWriter
10{
14 private const ushort RootDirectoryId = 0xF000;
15
23 public static NdsFileSystemBuildSnapshot Write(
24 IReadOnlyList<string> directories,
25 IReadOnlyCollection<NdsBuildFile> files,
26 int firstFileId)
27 {
28 var directoryIds = directories
29 .Select((path, index) => (path, id: checked((ushort)(RootDirectoryId + index))))
30 .ToDictionary(static value => value.path, static value => value.id, StringComparer.Ordinal);
31 var filesByDirectory = files
32 .GroupBy(static file => GetParent(file.Path), StringComparer.Ordinal)
33 .ToDictionary(
34 static group => group.Key,
35 static group => group.OrderBy(static file => file.Path, StringComparer.Ordinal).ToArray(),
36 StringComparer.Ordinal);
37 var childrenByDirectory = directories
38 .Where(static path => path != "/")
39 .GroupBy(static path => GetParent(path), StringComparer.Ordinal)
40 .ToDictionary(
41 static group => group.Key,
42 static group => group.Order(StringComparer.Ordinal).ToArray(),
43 StringComparer.Ordinal);
44 var orderedFiles = new List<NdsBuildFile>();
45 var subtables = new byte[directories.Count][];
46 var firstFileIds = new ushort[directories.Count];
47 for (int index = 0; index < directories.Count; index++)
48 {
49 string directory = directories[index];
50 firstFileIds[index] = checked((ushort)(firstFileId + orderedFiles.Count));
51 NdsBuildFile[] directoryFiles = filesByDirectory.GetValueOrDefault(directory) ?? [];
52 string[] childDirectories = childrenByDirectory.GetValueOrDefault(directory) ?? [];
53 orderedFiles.AddRange(directoryFiles);
54 subtables[index] = WriteSubtable(directoryFiles, childDirectories, directoryIds);
55 }
56
57 int mainTableLength = checked(directories.Count * 8);
58 int totalLength = checked(mainTableLength + subtables.Sum(static data => data.Length));
59 byte[] fnt = new byte[totalLength];
60 int subtableOffset = mainTableLength;
61 for (int index = 0; index < directories.Count; index++)
62 {
63 string directory = directories[index];
64 int recordOffset = index * 8;
65 BinaryPrimitives.WriteUInt32LittleEndian(fnt.AsSpan(recordOffset), checked((uint)subtableOffset));
66 BinaryPrimitives.WriteUInt16LittleEndian(fnt.AsSpan(recordOffset + 4), firstFileIds[index]);
67 ushort parent = directory == "/"
68 ? checked((ushort)directories.Count)
69 : directoryIds[GetParent(directory)];
70 BinaryPrimitives.WriteUInt16LittleEndian(fnt.AsSpan(recordOffset + 6), parent);
71 subtables[index].CopyTo(fnt, subtableOffset);
72 subtableOffset += subtables[index].Length;
73 }
74
75 return new(fnt, orderedFiles, directoryIds, firstFileId);
76 }
77
85 private static byte[] WriteSubtable(
86 IReadOnlyList<NdsBuildFile> files,
87 IReadOnlyList<string> childDirectories,
88 Dictionary<string, ushort> directoryIds)
89 {
90 using var stream = new MemoryStream();
91 foreach (NdsBuildFile file in files)
92 {
93 WriteName(stream, GetName(file.Path), isDirectory: false);
94 }
95
96 foreach (string directory in childDirectories)
97 {
98 WriteName(stream, GetName(directory), isDirectory: true);
99 ushort id = directoryIds[directory];
100 stream.WriteByte((byte)id);
101 stream.WriteByte((byte)(id >> 8));
102 }
103
104 stream.WriteByte(0);
105 return stream.ToArray();
106 }
107
114 private static void WriteName(Stream stream, string name, bool isDirectory)
115 {
116 byte[] bytes = Encoding.Latin1.GetBytes(name);
117 stream.WriteByte((byte)(bytes.Length | (isDirectory ? 0x80 : 0)));
118 stream.Write(bytes);
119 }
120
124 private static string GetParent(string path)
125 {
126 int separator = path.LastIndexOf('/');
127 return separator == 0 ? "/" : path[..separator];
128 }
129
133 private static string GetName(string path) => path[(path.LastIndexOf('/') + 1)..];
134}