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
11pub struct Banner<'a> {
13 version: BannerVersion,
14 data: Cow<'a, [u8]>,
15}
16
17#[derive(Debug, Snafu)]
19pub enum RawBannerError {
20 #[snafu(transparent)]
22 RawHeader {
23 source: RawHeaderError,
25 },
26 #[snafu(display("unknown banner version {version}:\n{backtrace}"))]
29 UnknownVersion {
30 version: u16,
32 backtrace: Backtrace,
34 },
35 #[snafu(display("banner version {version:x} must be {expected} bytes but got {actual} bytes"))]
37 InvalidSize {
38 version: u16,
40 expected: usize,
42 actual: usize,
44 backtrace: Backtrace,
46 },
47 #[snafu(display("expected {expected}-alignment but got {actual}-alignment:\n{backtrace}"))]
49 Misaligned {
50 expected: usize,
52 actual: usize,
54 backtrace: Backtrace,
56 },
57}
58
59impl<'a> Banner<'a> {
60 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 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.is_multiple_of(2) {
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 pub fn version(&self) -> BannerVersion {
113 self.version
114 }
115
116 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 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 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 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 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 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 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 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 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 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 pub fn full_data(&self) -> &[u8] {
199 &self.data
200 }
201
202 pub fn display(&self, indent: usize) -> DisplayBanner<'_> {
204 DisplayBanner { banner: self, indent }
205 }
206}
207
208pub 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 Display for DisplayBanner<'_> {
225 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226 let i = " ".repeat(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}Palette ......... :\n{}", banner.palette())?;
248 writeln!(f, "{i}Bitmap .......... :\n{}", banner.bitmap().display(banner.palette()))?;
249 Ok(())
250 }
251}
252
253#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize, Default)]
255pub enum BannerVersion {
256 #[default]
258 Original = 1,
259 China = 2,
261 Korea = 3,
263 Animated = 0x103,
265}
266
267impl BannerVersion {
268 fn from_u16(value: u16) -> Option<Self> {
269 match value {
270 1 => Some(Self::Original),
271 2 => Some(Self::China),
272 3 => Some(Self::Korea),
273 0x103 => Some(Self::Animated),
274 _ => None,
275 }
276 }
277
278 pub fn has_chinese(self) -> bool {
280 self >= Self::China
281 }
282
283 pub fn has_korean(self) -> bool {
285 self >= Self::Korea
286 }
287
288 pub fn has_animation(self) -> bool {
290 self >= Self::Animated
291 }
292
293 pub fn supports_language(self, language: Language) -> bool {
295 match language {
296 Language::Japanese => true,
297 Language::English => true,
298 Language::French => true,
299 Language::German => true,
300 Language::Italian => true,
301 Language::Spanish => true,
302 Language::Chinese => self.has_chinese(),
303 Language::Korean => self.has_korean(),
304 }
305 }
306
307 pub fn crc_index(self) -> usize {
309 match self {
310 BannerVersion::Original => 0,
311 BannerVersion::China => 1,
312 BannerVersion::Korea => 2,
313 BannerVersion::Animated => 3,
314 }
315 }
316
317 pub fn crc_range(self) -> Range<usize> {
319 match self {
320 BannerVersion::Original => 0x20..0x840,
321 BannerVersion::China => 0x20..0x940,
322 BannerVersion::Korea => 0x20..0xa40,
323 BannerVersion::Animated => 0x1240..0x23c0,
324 }
325 }
326
327 pub fn banner_size(self) -> usize {
329 match self {
330 BannerVersion::Original => 0x840,
331 BannerVersion::China => 0x940,
332 BannerVersion::Korea => 0x1240,
333 BannerVersion::Animated => 0x23c0,
334 }
335 }
336}
337
338impl Display for BannerVersion {
339 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340 match self {
341 BannerVersion::Original => write!(f, "Original"),
342 BannerVersion::China => write!(f, "China"),
343 BannerVersion::Korea => write!(f, "Korea"),
344 BannerVersion::Animated => write!(f, "DSi Animated"),
345 }
346 }
347}
348
349#[derive(Clone, Copy, Debug)]
351pub enum Language {
352 Japanese = 0,
354 English = 1,
356 French = 2,
358 German = 3,
360 Italian = 4,
362 Spanish = 5,
364 Chinese = 6,
366 Korean = 7,
368}
369
370impl Display for Language {
371 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
372 match self {
373 Language::Japanese => write!(f, "Japanese"),
374 Language::English => write!(f, "English"),
375 Language::French => write!(f, "French"),
376 Language::German => write!(f, "German"),
377 Language::Italian => write!(f, "Italian"),
378 Language::Spanish => write!(f, "Spanish"),
379 Language::Chinese => write!(f, "Chinese"),
380 Language::Korean => write!(f, "Korean"),
381 }
382 }
383}
384
385#[repr(C)]
387#[derive(Clone, Copy, Zeroable, Pod, Default)]
388pub struct BannerPalette(pub [u16; 16]);
389
390impl BannerPalette {
391 pub fn get_color(&self, index: usize) -> [u8; 4] {
393 if index < self.0.len() {
394 let color = self.0[index];
395 let b = (((color >> 10) & 31) << 3) as u8;
396 let g = (((color >> 5) & 31) << 3) as u8;
397 let r = ((color & 31) << 3) as u8;
398 let a = if index > 0 { u8::MAX } else { 0 };
399 [r, g, b, a]
400 } else {
401 [0, 0, 0, 0]
402 }
403 }
404
405 pub fn set_color(&mut self, index: usize, r: u8, g: u8, b: u8) {
407 let r = r as u16 >> 3;
408 let g = g as u16 >> 3;
409 let b = b as u16 >> 3;
410 let color = r | (g << 5) | (b << 10);
411 self.0[index] = color;
412 }
413}
414
415impl Display for BannerPalette {
416 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417 for i in 0..16 {
418 let [r, g, b, a] = self.get_color(i);
419 if a == 0 {
420 write!(f, " ")?;
421 } else {
422 write!(f, "\x1b[38;2;{r};{g};{b}m█")?;
423 }
424 }
425 write!(f, "\x1b[0m")
426 }
427}
428
429#[repr(C)]
431#[derive(Clone, Copy, Zeroable, Pod)]
432pub struct BannerBitmap(pub [u8; 0x200]);
433
434impl BannerBitmap {
435 pub fn display<'a>(&'a self, palette: &'a BannerPalette) -> DisplayBannerBitmap<'a> {
437 DisplayBannerBitmap { bitmap: self, palette }
438 }
439
440 fn get_index(x: usize, y: usize) -> (usize, usize) {
441 let index = (y / 8 * 0x80) + (x / 8 * 0x20) + (y % 8 * 4) + (x / 2 % 4);
443 let offset = (x % 2) * 4;
445 (index, offset)
446 }
447
448 pub fn get_pixel(&self, x: usize, y: usize) -> usize {
450 let (index, offset) = Self::get_index(x, y);
451 if index < self.0.len() {
452 (self.0[index] as usize >> offset) & 0xf
453 } else {
454 0
455 }
456 }
457
458 pub fn set_pixel(&mut self, x: usize, y: usize, value: u8) {
460 let (index, offset) = Self::get_index(x, y);
461 if index < self.0.len() {
462 self.0[index] = (self.0[index] & !(0xf << offset)) | (value << offset);
463 }
464 }
465}
466
467impl Default for BannerBitmap {
468 fn default() -> Self {
469 Self([0; 0x200])
470 }
471}
472
473pub struct DisplayBannerBitmap<'a> {
475 bitmap: &'a BannerBitmap,
476 palette: &'a BannerPalette,
477}
478
479impl Display for DisplayBannerBitmap<'_> {
480 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
481 for y in (0..32).step_by(2) {
482 for x in 0..32 {
483 let [tr, tg, tb, ta] = self.palette.get_color(self.bitmap.get_pixel(x, y));
484 let [br, bg, bb, ba] = self.palette.get_color(self.bitmap.get_pixel(x, y + 1));
485
486 match (ta, ba) {
487 (0, 0) => write!(f, " ")?,
488 (_, 0) => write!(f, "\x1b[38;2;{tr};{tg};{tb}m▀")?,
489 (0, _) => write!(f, "\x1b[38;2;{tr};{tg};{tb}m▄")?,
490 (_, _) => write!(f, "\x1b[38;2;{tr};{tg};{tb}m\x1b[48;2;{br};{bg};{bb}m▀")?,
491 }
492 }
493 writeln!(f, "\x1b[0m")?;
494 }
495 Ok(())
496 }
497}
498
499#[repr(C)]
501#[derive(Clone, Copy, Zeroable, Pod)]
502pub struct BannerAnimation {
503 pub bitmaps: [BannerBitmap; 8],
505 pub palettes: [BannerPalette; 8],
507 pub keyframes: [BannerKeyframe; 64],
509}
510
511#[bitfield(u16)]
513pub struct BannerKeyframe {
514 pub frame_duration: u8,
516 #[bits(3)]
518 pub bitmap_index: u8,
519 #[bits(3)]
521 pub palette_index: u8,
522 pub flip_horizontally: bool,
524 pub flip_vertically: bool,
526}
527
528unsafe impl Zeroable for BannerKeyframe {}
529unsafe impl Pod for BannerKeyframe {}