1using System.Buffers.Binary;
9internal static class NdsFileNameTableWriter
14 private const ushort RootDirectoryId = 0xF000;
23 public static NdsFileSystemBuildSnapshot Write(
24 IReadOnlyList<string> directories,
25 IReadOnlyCollection<NdsBuildFile> files,
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)
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)
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++)
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);
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++)
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;
75 return new(fnt, orderedFiles, directoryIds, firstFileId);
85 private static byte[] WriteSubtable(
86 IReadOnlyList<NdsBuildFile> files,
87 IReadOnlyList<string> childDirectories,
88 Dictionary<string, ushort> directoryIds)
90 using var stream =
new MemoryStream();
91 foreach (NdsBuildFile file
in files)
93 WriteName(stream, GetName(file.Path), isDirectory:
false);
96 foreach (
string directory
in childDirectories)
98 WriteName(stream, GetName(directory), isDirectory:
true);
99 ushort
id = directoryIds[directory];
100 stream.WriteByte((
byte)
id);
101 stream.WriteByte((
byte)(
id >> 8));
105 return stream.ToArray();
114 private static void WriteName(Stream stream,
string name,
bool isDirectory)
116 byte[] bytes = Encoding.Latin1.GetBytes(name);
117 stream.WriteByte((
byte)(bytes.Length | (isDirectory ? 0x80 : 0)));
124 private static string GetParent(
string path)
126 int separator = path.LastIndexOf(
'/');
127 return separator == 0 ?
"/" : path[..separator];
133 private static string GetName(
string path) => path[(path.LastIndexOf(
'/') + 1)..];