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 % 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 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<'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#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
254pub enum BannerVersion {
255 Original = 1,
257 China = 2,
259 Korea = 3,
261 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 pub fn has_chinese(self) -> bool {
278 self >= Self::China
279 }
280
281 pub fn has_korean(self) -> bool {
283 self >= Self::Korea
284 }
285
286 pub fn has_animation(self) -> bool {
288 self >= Self::Animated
289 }
290
291 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 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 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 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#[derive(Clone, Copy, Debug)]
349pub enum Language {
350 Japanese = 0,
352 English = 1,
354 French = 2,
356 German = 3,
358 Italian = 4,
360 Spanish = 5,
362 Chinese = 6,
364 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#[repr(C)]
385#[derive(Clone, Copy, Zeroable, Pod, Default)]
386pub struct BannerPalette(pub [u16; 16]);
387
388impl BannerPalette {
389 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 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#[repr(C)]
424#[derive(Clone, Copy, Zeroable, Pod)]
425pub struct BannerBitmap(pub [u8; 0x200]);
426
427impl BannerBitmap {
428 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 let index = (y / 8 * 0x80) + (x / 8 * 0x20) + (y % 8 * 4) + (x / 2 % 4);
436 let offset = (x % 2) * 4;
438 (index, offset)
439 }
440
441 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 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
466pub 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#[repr(C)]
489#[derive(Clone, Copy, Zeroable, Pod)]
490pub struct BannerAnimation {
491 pub bitmaps: [BannerBitmap; 8],
493 pub palettes: [BannerPalette; 8],
495 pub keyframes: [BannerKeyframe; 64],
497}
498
499#[bitfield(u16)]
501pub struct BannerKeyframe {
502 pub frame_duration: u8,
504 #[bits(3)]
506 pub bitmap_index: u8,
507 #[bits(3)]
509 pub palette_index: u8,
510 pub flip_horizontally: bool,
512 pub flip_vertically: bool,
514}
515
516unsafe impl Zeroable for BannerKeyframe {}
517unsafe impl Pod for BannerKeyframe {}