10 private readonly Dictionary<NdsBannerLanguage, string> _titles = [];
12 private byte[] _paletteIndices =
new byte[32 * 32];
14 private ushort[] _palette =
new ushort[16];
16 private readonly
byte[][] _animatedPaletteIndices = Enumerable.Range(0, 8)
17 .Select(
static _ =>
new byte[32 * 32])
20 private readonly ushort[][] _animatedPalettes = Enumerable.Range(0, 8)
21 .Select(
static _ =>
new ushort[16])
30 if (version is not (1 or 2 or 3 or 0x0103))
32 throw new ArgumentOutOfRangeException(nameof(version),
"Banner version must be 1, 2, 3, or 0x0103.");
47 ArgumentNullException.ThrowIfNull(title);
48 if ((
int)language >= GetLanguageCount() ||
50 title.Contains(
'\0', StringComparison.Ordinal))
52 throw new ArgumentException(
"The title language or length is not supported by this banner version.", nameof(title));
55 _titles[language] = title;
64 ReadOnlySpan<byte> paletteIndices,
65 ReadOnlySpan<ushort> bgr555Palette)
67 ValidateIcon(paletteIndices, bgr555Palette);
68 _paletteIndices = paletteIndices.ToArray();
69 _palette = bgr555Palette.ToArray();
84 ReadOnlySpan<byte> paletteIndices,
85 ReadOnlySpan<ushort> bgr555Palette)
88 ArgumentOutOfRangeException.ThrowIfNegative(frame);
89 ArgumentOutOfRangeException.ThrowIfGreaterThan(frame, 7);
90 ValidateIcon(paletteIndices, bgr555Palette);
91 _animatedPaletteIndices[frame] = paletteIndices.ToArray();
92 _animatedPalettes[frame] = bgr555Palette.ToArray();
106 ArgumentNullException.ThrowIfNull(steps);
108 if (materialized.Length > 63)
110 throw new ArgumentException(
"A DSi animation accepts at most 63 poses plus its required terminator.", nameof(steps));
118 _animationSteps = materialized;
127 NdsBinary.WriteUInt16(data, 0,
Version);
131 int offset = 0x240 + ((int)language * 0x100);
132 Encoding.Unicode.GetBytes(title, data.AsSpan(offset, 0xFE));
137 WriteAnimation(data);
141 for (
int slot = 0; slot < crcCount; slot++)
143 (
int offset,
int length) =
NdsBanner.GetCrcRegion(slot);
144 BinaryPrimitives.WriteUInt16LittleEndian(
145 data.AsSpan(2 + (slot * 2)),
154 private void WriteIcon(Span<byte> data)
156 WriteTiles(data.Slice(0x20, 0x200), _paletteIndices);
157 WritePalette(data.Slice(0x220, 0x20), _palette);
162 private void WriteAnimation(Span<byte> data)
164 for (
int frame = 0; frame < 8; frame++)
166 WriteTiles(data.Slice(0x1240 + (frame * 0x200), 0x200), _animatedPaletteIndices[frame]);
167 WritePalette(data.Slice(0x2240 + (frame * 0x20), 0x20), _animatedPalettes[frame]);
170 for (
int index = 0; index < _animationSteps.Length; index++)
172 BinaryPrimitives.WriteUInt16LittleEndian(data[(0x2340 + (index * 2))..], _animationSteps[index].Pack());
179 private static void WriteTiles(Span<byte> tiles, ReadOnlySpan<byte> paletteIndices)
181 for (
int y = 0; y < 32; y++)
183 for (
int x = 0; x < 32; x += 2)
185 int tileOffset = ((((y / 8) * 4) + (x / 8)) * 32) + ((y % 8) * 4) + ((x % 8) / 2);
186 byte low = paletteIndices[(y * 32) + x];
187 byte high = paletteIndices[(y * 32) + x + 1];
188 tiles[tileOffset] = (byte)(low | (high << 4));
196 private static void WritePalette(Span<byte> destination, ReadOnlySpan<ushort> palette)
198 for (
int index = 0; index < palette.Length; index++)
200 BinaryPrimitives.WriteUInt16LittleEndian(destination[(index * 2)..], palette[index]);
207 private static void ValidateIcon(ReadOnlySpan<byte> paletteIndices, ReadOnlySpan<ushort> bgr555Palette)
209 if (paletteIndices.Length != 32 * 32 || bgr555Palette.Length != 16)
211 throw new ArgumentException(
"A banner icon requires 1024 indices and 16 palette colors.");
214 if (paletteIndices.ContainsAnyExceptInRange((
byte)0, (
byte)15))
216 throw new ArgumentException(
"Banner palette indices must be between zero and 15.", nameof(paletteIndices));
221 private void EnsureAnimated()
225 throw new InvalidOperationException(
"Animated frames require DSi banner version 0x0103.");
231 private int GetLanguageCount() =>
Version switch
NdsBannerBuilder SetAnimatedFrame(int frame, ReadOnlySpan< byte > paletteIndices, ReadOnlySpan< ushort > bgr555Palette)
Assigns one of the eight DSi tile-and-palette slots from conventional row-major indexed pixels....