ds_rom/rom/raw/
banner.rs

1use std::{borrow::Cow, fmt::Display, ops::Range};
2
3use bitfield_struct::bitfield;
4use bytemuck::{Pod, PodCastError, Zeroable};
5use serde::{Deserialize, Serialize};
6use snafu::{Backtrace, Snafu};
7
8use super::RawHeaderError;
9use crate::str::Unicode16Array;
10
11/// Banner for displaying an icon and title on the home menu. This is the raw struct, see the plain one [here](super::super::Banner).
12pub struct Banner<'a> {
13    version: BannerVersion,
14    data: Cow<'a, [u8]>,
15}
16
17/// Errors related to [`Banner`].
18#[derive(Debug, Snafu)]
19pub enum RawBannerError {
20    /// See [`RawHeaderError`].
21    #[snafu(transparent)]
22    RawHeader {
23        /// Source error.
24        source: RawHeaderError,
25    },
26    /// Occurs when the input banner has an unknown version. Should not occur unless there's an undocumented banner version
27    /// we're unaware of.
28    #[snafu(display("unknown banner version {version}:\n{backtrace}"))]
29    UnknownVersion {
30        /// Input banner version.
31        version: u16,
32        /// Backtrace to the source of the error.
33        backtrace: Backtrace,
34    },
35    /// Occurs when the input is not the right size according to its version number.
36    #[snafu(display("banner version {version:x} must be {expected} bytes but got {actual} bytes"))]
37    InvalidSize {
38        /// Version number.
39        version: u16,
40        /// Expected size for this version.
41        expected: usize,
42        /// Actual input size.
43        actual: usize,
44        /// Backtrace to the source of the error.
45        backtrace: Backtrace,
46    },
47    /// Occurs when the input is less aligned than the banner
48    #[snafu(display("expected {expected}-alignment but got {actual}-alignment:\n{backtrace}"))]
49    Misaligned {
50        /// Expected alignment.
51        expected: usize,
52        /// Actual alignment.
53        actual: usize,
54        /// Backtrace to the source of the error.
55        backtrace: Backtrace,
56    },
57}
58
59impl<'a> Banner<'a> {
60    /// Creates a new [`Banner`].
61    pub fn new(version: BannerVersion) -> Self {
62        let size = version.banner_size();
63        let mut data = vec![0u8; size];
64        data[0..2].copy_from_slice(&(version as u16).to_le_bytes());
65        Self { version, data: data.into() }
66    }
67
68    fn handle_pod_cast<T>(result: Result<T, PodCastError>) -> T {
69        match result {
70            Ok(build_info) => build_info,
71            Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => unreachable!(),
72            Err(PodCastError::AlignmentMismatch) => panic!(),
73            Err(PodCastError::OutputSliceWouldHaveSlop) => panic!(),
74            Err(PodCastError::SizeMismatch) => unreachable!(),
75        }
76    }
77
78    /// Reinterprets a `&[u8]` as a reference to [`Banner`].
79    ///
80    /// # Errors
81    ///
82    /// This function will return an error if the input has an unknown banner version, or has the wrong size for its version,
83    /// or is not aligned enough.
84    pub fn borrow_from_slice(data: &'a [u8]) -> Result<Self, RawBannerError> {
85        let addr = data as *const [u8] as *const () as usize;
86        if addr % 2 != 0 {
87            return MisalignedSnafu { expected: 2usize, actual: 1usize << addr.trailing_zeros() as usize }.fail();
88        }
89
90        let version_value = u16::from_le_bytes([data[0], data[1]]);
91        let Some(version) = BannerVersion::from_u16(version_value) else {
92            return UnknownVersionSnafu { version: version_value }.fail();
93        };
94        let size = version.banner_size();
95        if data.len() < size {
96            return InvalidSizeSnafu { version: version_value, expected: size, actual: data.len() }.fail();
97        }
98        let data = &data[..size];
99
100        let mut bitmap = [0u8; 0x200];
101        bitmap.copy_from_slice(&data[0x20..0x220]);
102
103        let mut palette = [0u16; 16];
104        for i in 0..16 {
105            palette[i] = u16::from_le_bytes([data[0x220 + i * 2], data[0x221 + i * 2]]);
106        }
107
108        Ok(Self { version, data: Cow::Borrowed(data) })
109    }
110
111    /// Returns the version of this [`Banner`].
112    pub fn version(&self) -> BannerVersion {
113        self.version
114    }
115
116    /// Returns the CRC checksum at the given index.
117    pub fn crc(&self, index: usize) -> u16 {
118        u16::from_le_bytes([self.data[2 + index * 2], self.data[3 + index * 2]])
119    }
120
121    /// Returns a mutable CRC checksum at the given index.
122    pub fn crc_mut(&mut self, index: usize) -> &mut u16 {
123        let start = 2 + index * 2;
124        let end = start + 2;
125        let data = &mut self.data.to_mut()[start..end];
126        Self::handle_pod_cast(bytemuck::try_from_bytes_mut(data))
127    }
128
129    /// Returns a reference to the bitmap of this [`Banner`].
130    pub fn bitmap(&self) -> &BannerBitmap {
131        let data = &self.data[0x20..0x220];
132        Self::handle_pod_cast(bytemuck::try_from_bytes(data))
133    }
134
135    /// Returns a mutable reference to the bitmap of this [`Banner`].
136    pub fn bitmap_mut(&mut self) -> &mut BannerBitmap {
137        let data = &mut self.data.to_mut()[0x20..0x220];
138        Self::handle_pod_cast(bytemuck::try_from_bytes_mut(data))
139    }
140
141    /// Returns a reference to the palette of this [`Banner`].
142    pub fn palette(&self) -> &BannerPalette {
143        let data = &self.data[0x220..0x240];
144        Self::handle_pod_cast(bytemuck::try_from_bytes(data))
145    }
146
147    /// Returns a mutable reference to the palette of this [`Banner`].
148    pub fn palette_mut(&mut self) -> &mut BannerPalette {
149        let data = &mut self.data.to_mut()[0x220..0x240];
150        Self::handle_pod_cast(bytemuck::try_from_bytes_mut(data))
151    }
152
153    /// Returns a title for the given language, or `None` the language is not supported by this banner version.
154    pub fn title(&self, language: Language) -> Option<&Unicode16Array<0x80>> {
155        if !self.version.supports_language(language) {
156            None
157        } else {
158            let start = 0x240 + language as usize * 0x100;
159            let end = start + 0x100;
160            let data = &self.data[start..end];
161            Some(Self::handle_pod_cast(bytemuck::try_from_bytes(data)))
162        }
163    }
164
165    /// Returns a mutable title for the given language, or `None` the language is not supported by this banner version.
166    pub fn title_mut(&mut self, language: Language) -> Option<&mut Unicode16Array<0x80>> {
167        if !self.version.supports_language(language) {
168            None
169        } else {
170            let start = 0x240 + language as usize * 0x100;
171            let end = start + 0x100;
172            let data = &mut self.data.to_mut()[start..end];
173            Some(Self::handle_pod_cast(bytemuck::try_from_bytes_mut(data)))
174        }
175    }
176
177    /// Returns a reference to the animation of this [`Banner`], if it exists in this banner version.
178    pub fn animation(&self) -> Option<&BannerAnimation> {
179        if !self.version.has_animation() {
180            None
181        } else {
182            let data = &self.data[0x1240..0x23c0];
183            Some(Self::handle_pod_cast(bytemuck::try_from_bytes(data)))
184        }
185    }
186
187    /// Returns a mutable reference to the animation of this [`Banner`], if it exists in this banner version.
188    pub fn animation_mut(&mut self) -> Option<&mut BannerAnimation> {
189        if !self.version.has_animation() {
190            None
191        } else {
192            let data = &mut self.data.to_mut()[0x1240..0x23c0];
193            Some(Self::handle_pod_cast(bytemuck::try_from_bytes_mut(data)))
194        }
195    }
196
197    /// Returns a reference to the full data of this [`Banner`].
198    pub fn full_data(&self) -> &[u8] {
199        &self.data
200    }
201
202    /// Creates a [`DisplayBanner`] which implements [`Display`].
203    pub fn display(&self, indent: usize) -> DisplayBanner {
204        DisplayBanner { banner: self, indent }
205    }
206}
207
208/// Can be used to display values inside [`Banner`].
209pub struct DisplayBanner<'a> {
210    banner: &'a Banner<'a>,
211    indent: usize,
212}
213
214macro_rules! write_title {
215    ($f:ident, $fmt:literal, $banner:ident, $language:expr) => {
216        if let Some(title) = $banner.title($language) {
217            writeln!($f, $fmt, '\n', title, '\n')
218        } else {
219            Ok(())
220        }
221    };
222}
223
224impl<'a> Display for DisplayBanner<'a> {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        let i = format!("{:indent$}", "", indent = self.indent);
227        let banner = &self.banner;
228        writeln!(f, "{i}Version ......... : {}", banner.version)?;
229        writeln!(f, "{i}Original CRC .... : {:#x}", banner.crc(BannerVersion::Original.crc_index()))?;
230        write_title!(f, "{i}Japanese Title .. : {}{}{}", banner, Language::Japanese)?;
231        write_title!(f, "{i}English Title ... : {}{}{}", banner, Language::English)?;
232        write_title!(f, "{i}French Title .... : {}{}{}", banner, Language::French)?;
233        write_title!(f, "{i}German Title .... : {}{}{}", banner, Language::German)?;
234        write_title!(f, "{i}Italian Title ... : {}{}{}", banner, Language::Italian)?;
235        write_title!(f, "{i}Spanish Title ... : {}{}{}", banner, Language::Spanish)?;
236        if banner.version >= BannerVersion::China {
237            writeln!(f, "{i}China CRC ....... : {:#x}", banner.crc(BannerVersion::China.crc_index()))?;
238            write_title!(f, "{i}Chinese Title ... : {}{}{}", banner, Language::Chinese)?;
239        }
240        if banner.version >= BannerVersion::Korea {
241            writeln!(f, "{i}Korea CRC ....... : {:#x}", banner.crc(BannerVersion::Korea.crc_index()))?;
242            write_title!(f, "{i}Korean Title .... : {}{}{}", banner, Language::Korean)?;
243        }
244        if banner.version >= BannerVersion::Animated {
245            writeln!(f, "{i}Animation CRC ... : {:#x}", banner.crc(BannerVersion::Animated.crc_index()))?;
246        }
247        writeln!(f, "{i}Bitmap .......... :\n{}", banner.bitmap().display(banner.palette()))?;
248        Ok(())
249    }
250}
251
252/// Known banner versions.
253#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
254pub enum BannerVersion {
255    /// Original version with titles in Japanese, English, French, German, Italian and Spanish.
256    Original = 1,
257    /// Inherits from [`BannerVersion::Original`] and adds Chinese.
258    China = 2,
259    /// Inherits from [`BannerVersion::China`] and adds Korean.
260    Korea = 3,
261    /// Inherits from [`BannerVersion::Korea`] and adds an animated icon.
262    Animated = 0x103,
263}
264
265impl BannerVersion {
266    fn from_u16(value: u16) -> Option<Self> {
267        match value {
268            1 => Some(Self::Original),
269            2 => Some(Self::China),
270            3 => Some(Self::Korea),
271            0x103 => Some(Self::Animated),
272            _ => None,
273        }
274    }
275
276    /// Returns whether this version has a Chinese title.
277    pub fn has_chinese(self) -> bool {
278        self >= Self::China
279    }
280
281    /// Returns whether this version has a Korean title.
282    pub fn has_korean(self) -> bool {
283        self >= Self::Korea
284    }
285
286    /// Returns whether this version has an animated icon.
287    pub fn has_animation(self) -> bool {
288        self >= Self::Animated
289    }
290
291    /// Returns whether this version supports the given language.
292    pub fn supports_language(self, language: Language) -> bool {
293        match language {
294            Language::Japanese => true,
295            Language::English => true,
296            Language::French => true,
297            Language::German => true,
298            Language::Italian => true,
299            Language::Spanish => true,
300            Language::Chinese => self.has_chinese(),
301            Language::Korean => self.has_korean(),
302        }
303    }
304
305    /// Returns the CRC index of this version.
306    pub fn crc_index(self) -> usize {
307        match self {
308            BannerVersion::Original => 0,
309            BannerVersion::China => 1,
310            BannerVersion::Korea => 2,
311            BannerVersion::Animated => 3,
312        }
313    }
314
315    /// Returns the CRC checksum range of this version.
316    pub fn crc_range(self) -> Range<usize> {
317        match self {
318            BannerVersion::Original => 0x20..0x840,
319            BannerVersion::China => 0x20..0x940,
320            BannerVersion::Korea => 0x20..0xa40,
321            BannerVersion::Animated => 0x1240..0x23c0,
322        }
323    }
324
325    /// Returns the banner size of this version.
326    pub fn banner_size(self) -> usize {
327        match self {
328            BannerVersion::Original => 0x840,
329            BannerVersion::China => 0x940,
330            BannerVersion::Korea => 0x1240,
331            BannerVersion::Animated => 0x23c0,
332        }
333    }
334}
335
336impl Display for BannerVersion {
337    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338        match self {
339            BannerVersion::Original => write!(f, "Original"),
340            BannerVersion::China => write!(f, "China"),
341            BannerVersion::Korea => write!(f, "Korea"),
342            BannerVersion::Animated => write!(f, "DSi Animated"),
343        }
344    }
345}
346
347/// Languages present in the banner.
348#[derive(Clone, Copy, Debug)]
349pub enum Language {
350    /// Japanese.
351    Japanese = 0,
352    /// English.
353    English = 1,
354    /// French.
355    French = 2,
356    /// German.
357    German = 3,
358    /// Italian.
359    Italian = 4,
360    /// Spanish.
361    Spanish = 5,
362    /// Chinese.
363    Chinese = 6,
364    /// Korean.
365    Korean = 7,
366}
367
368impl Display for Language {
369    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
370        match self {
371            Language::Japanese => write!(f, "Japanese"),
372            Language::English => write!(f, "English"),
373            Language::French => write!(f, "French"),
374            Language::German => write!(f, "German"),
375            Language::Italian => write!(f, "Italian"),
376            Language::Spanish => write!(f, "Spanish"),
377            Language::Chinese => write!(f, "Chinese"),
378            Language::Korean => write!(f, "Korean"),
379        }
380    }
381}
382
383/// Contains a palette for a banner bitmap, where each color is 15-bit BGR.
384#[repr(C)]
385#[derive(Clone, Copy, Zeroable, Pod, Default)]
386pub struct BannerPalette(pub [u16; 16]);
387
388impl BannerPalette {
389    /// Returns the color from 24-bit `(r, g, b)` at the given index.
390    pub fn get_color(&self, index: usize) -> (u8, u8, u8) {
391        if index < self.0.len() {
392            let color = self.0[index];
393            let b = (((color >> 10) & 31) << 3) as u8;
394            let g = (((color >> 5) & 31) << 3) as u8;
395            let r = ((color & 31) << 3) as u8;
396            (r, g, b)
397        } else {
398            (0, 0, 0)
399        }
400    }
401
402    /// Sets the color from 24-bit `(r, g, b)` at the given index.
403    pub fn set_color(&mut self, index: usize, r: u8, g: u8, b: u8) {
404        let r = r as u16 >> 3;
405        let g = g as u16 >> 3;
406        let b = b as u16 >> 3;
407        let color = r | (g << 5) | (b << 10);
408        self.0[index] = color;
409    }
410}
411
412impl Display for BannerPalette {
413    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414        for i in 0..16 {
415            let (r, g, b) = self.get_color(i);
416            write!(f, "\x1b[38;2;{r};{g};{b}m█")?;
417        }
418        write!(f, "\x1b[0m")
419    }
420}
421
422/// A bitmap in the banner.
423#[repr(C)]
424#[derive(Clone, Copy, Zeroable, Pod)]
425pub struct BannerBitmap(pub [u8; 0x200]);
426
427impl BannerBitmap {
428    /// Creates a [`DisplayBannerBitmap`] which implements [`Display`].
429    pub fn display<'a>(&'a self, palette: &'a BannerPalette) -> DisplayBannerBitmap<'a> {
430        DisplayBannerBitmap { bitmap: self, palette }
431    }
432
433    fn get_index(x: usize, y: usize) -> (usize, usize) {
434        // 8x8 pixel tiles in a 4x4 grid
435        let index = (y / 8 * 0x80) + (x / 8 * 0x20) + (y % 8 * 4) + (x / 2 % 4);
436        // 4 bits per pixel
437        let offset = (x % 2) * 4;
438        (index, offset)
439    }
440
441    /// Gets a palette index at the given coordinates.
442    pub fn get_pixel(&self, x: usize, y: usize) -> usize {
443        let (index, offset) = Self::get_index(x, y);
444        if index < self.0.len() {
445            (self.0[index] as usize >> offset) & 0xf
446        } else {
447            0
448        }
449    }
450
451    /// Sets a palette index at the given coordinates.
452    pub fn set_pixel(&mut self, x: usize, y: usize, value: u8) {
453        let (index, offset) = Self::get_index(x, y);
454        if index < self.0.len() {
455            self.0[index] = (self.0[index] & !(0xf << offset)) | (value << offset);
456        }
457    }
458}
459
460impl Default for BannerBitmap {
461    fn default() -> Self {
462        Self([0; 0x200])
463    }
464}
465
466/// Can be used to display a [`BannerBitmap`].
467pub struct DisplayBannerBitmap<'a> {
468    bitmap: &'a BannerBitmap,
469    palette: &'a BannerPalette,
470}
471
472impl<'a> Display for DisplayBannerBitmap<'a> {
473    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474        for y in (0..32).step_by(2) {
475            for x in 0..32 {
476                let (tr, tg, tb) = self.palette.get_color(self.bitmap.get_pixel(x, y));
477                let (br, bg, bb) = self.palette.get_color(self.bitmap.get_pixel(x, y + 1));
478
479                write!(f, "\x1b[38;2;{tr};{tg};{tb}m\x1b[48;2;{br};{bg};{bb}m▀")?;
480            }
481            writeln!(f, "\x1b[0m")?;
482        }
483        Ok(())
484    }
485}
486
487/// An animated banner icon.
488#[repr(C)]
489#[derive(Clone, Copy, Zeroable, Pod)]
490pub struct BannerAnimation {
491    /// Up to 8 bitmaps.
492    pub bitmaps: [BannerBitmap; 8],
493    /// Up to 8 palettes.
494    pub palettes: [BannerPalette; 8],
495    /// Up to 64 keyframes.
496    pub keyframes: [BannerKeyframe; 64],
497}
498
499/// A keyframe for [`BannerAnimation`].
500#[bitfield(u16)]
501pub struct BannerKeyframe {
502    /// How long to show this keyframe for, in frames.
503    pub frame_duration: u8,
504    /// Which of the 8 bitmaps to show.
505    #[bits(3)]
506    pub bitmap_index: u8,
507    /// Which of the 8 palettes to use.
508    #[bits(3)]
509    pub palette_index: u8,
510    /// Flips the bitmap horizontally.
511    pub flip_horizontally: bool,
512    /// Flips the bitmap vertically.
513    pub flip_vertically: bool,
514}
515
516unsafe impl Zeroable for BannerKeyframe {}
517unsafe impl Pod for BannerKeyframe {}