1use std::{
2 fmt::Display,
3 mem::{align_of, size_of},
4};
5
6use bitfield_struct::bitfield;
7use bytemuck::{Pod, PodCastError, Zeroable};
8use serde::{Deserialize, Serialize};
9use snafu::{Backtrace, Snafu};
10
11use crate::{
12 rom::Logo,
13 str::{AsciiArray, BlobSize},
14};
15
16#[repr(C)]
18#[derive(Clone, Copy)]
19pub struct Header {
20 pub title: AsciiArray<12>,
22 pub gamecode: AsciiArray<4>,
24 pub makercode: AsciiArray<2>,
26 pub unitcode: u8,
28 pub seed_select: u8,
30 pub capacity: Capacity,
32 pub reserved0: [u8; 7],
34 pub dsi_flags: DsiFlags,
36 pub ds_flags: DsFlags,
38 pub rom_version: u8,
40 pub autostart: u8,
42 pub arm9: ProgramOffset,
44 pub arm7: ProgramOffset,
46 pub file_names: TableOffset,
48 pub file_allocs: TableOffset,
50 pub arm9_overlays: TableOffset,
52 pub arm7_overlays: TableOffset,
54 pub normal_cmd_setting: u32,
56 pub key1_cmd_setting: u32,
58 pub banner_offset: u32,
60 pub secure_area_crc: u16,
62 pub secure_area_delay: Delay,
64 pub arm9_autoload_callback: u32,
66 pub arm7_autoload_callback: u32,
68 pub secure_area_disable: u64,
70 pub rom_size_ds: u32,
72 pub header_size: u32,
74 pub arm9_build_info_offset: u32,
76 pub arm7_build_info_offset: u32,
78 pub ds_rom_region_end: u16,
80 pub dsi_rom_region_end: u16,
82 pub rom_nand_end: u16,
84 pub rw_nand_end: u16,
86 pub reserved1: [u8; 0x18],
88 pub reserved2: [u8; 0x10],
90 pub logo: [u8; 0x9c],
92 pub logo_crc: u16,
94 pub header_crc: u16,
96 pub debug_rom_offset: u32,
98 pub debug_size: u32,
100 pub debug_ram_addr: u32,
102 pub reserved3: [u8; 4],
104 pub reserved4: [u8; 0x10],
106 pub memory_banks_wram: [u32; 5],
109 pub memory_banks_arm9: [u32; 3],
111 pub memory_banks_arm7: [u32; 3],
113 pub memory_bank_9: u32,
115 pub region_flags: RegionFlags,
117 pub access_control: AccessControl,
119 pub arm7_scfg_ext7_setting: u32,
121 pub dsi_flags_2: DsiFlags2,
123 pub arm9i: ProgramOffset,
125 pub arm7i: ProgramOffset,
127 pub digest_ds_area: TableOffset,
129 pub digest_dsi_area: TableOffset,
131 pub digest_sector_hashtable: TableOffset,
133 pub digest_block_hashtable: TableOffset,
135 pub digest_sector_size: u32,
137 pub digest_sector_count: u32,
139 pub banner_size: u32,
141 pub sd_shared2_0000_size: u8,
143 pub sd_shared2_0001_size: u8,
145 pub eula_version: u8,
147 pub use_ratings: bool,
149 pub rom_size_dsi: u32,
151 pub sd_shared2_0002_size: u8,
153 pub sd_shared2_0003_size: u8,
155 pub sd_shared2_0004_size: u8,
157 pub sd_shared2_0005_size: u8,
159 pub arm9i_build_info_offset: u32,
161 pub arm7i_build_info_offset: u32,
163 pub modcrypt_area_1: TableOffset,
165 pub modcrypt_area_2: TableOffset,
167 pub gamecode_rev: AsciiArray<4>,
169 pub file_type: u32,
171 pub sd_public_sav_size: u32,
173 pub sd_private_sav_size: u32,
175 pub reserved5: [u8; 0xb0],
177 pub age_ratings: [u8; 0x10],
179 pub sha1_hmac_arm9_with_secure_area: [u8; 0x14],
181 pub sha1_hmac_arm7: [u8; 0x14],
183 pub sha1_hmac_digest: [u8; 0x14],
185 pub sha1_hmac_banner: [u8; 0x14],
187 pub sha1_hmac_arm9i: [u8; 0x14],
189 pub sha1_hmac_arm7i: [u8; 0x14],
191 pub sha1_hmac_unk1: [u8; 0x14],
193 pub sha1_hmac_unk2: [u8; 0x14],
195 pub sha1_hmac_arm9: [u8; 0x14],
197 pub reserved6: [u8; 0xa4c],
199 pub debug_args: [u8; 0x180],
201 pub rsa_sha1: [u8; 0x80],
203 pub reserved7: [u8; 0x3000],
205}
206
207unsafe impl Zeroable for Header {}
208unsafe impl Pod for Header {}
209
210#[derive(Debug, Snafu)]
212pub enum RawHeaderError {
213 #[snafu(display("expected {expected:#x} bytes for header but had only {actual:#x}:\n{backtrace}"))]
215 DataTooSmall {
216 expected: usize,
218 actual: usize,
220 backtrace: Backtrace,
222 },
223 #[snafu(display("expected {expected}-alignment for header but got {actual}-alignment:\n{backtrace}"))]
225 Misaligned {
226 expected: usize,
228 actual: usize,
230 backtrace: Backtrace,
232 },
233}
234
235impl Header {
236 pub fn version(&self) -> HeaderVersion {
238 if self.dsi_flags_2.0 != 0 {
239 HeaderVersion::DsPostDsi
240 } else {
241 HeaderVersion::Original
242 }
243 }
244
245 fn check_size(data: &'_ [u8]) -> Result<(), RawHeaderError> {
246 let size = size_of::<Self>();
247 if data.len() < size {
248 DataTooSmallSnafu { expected: size, actual: data.len() }.fail()
249 } else {
250 Ok(())
251 }
252 }
253
254 fn handle_pod_cast<T>(result: Result<T, PodCastError>, addr: usize) -> Result<T, RawHeaderError> {
255 match result {
256 Ok(build_info) => Ok(build_info),
257 Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => {
258 MisalignedSnafu { expected: align_of::<Self>(), actual: 1usize << addr.trailing_zeros() }.fail()
259 }
260 Err(PodCastError::AlignmentMismatch) => panic!(),
261 Err(PodCastError::OutputSliceWouldHaveSlop) => panic!(),
262 Err(PodCastError::SizeMismatch) => unreachable!(),
263 }
264 }
265
266 pub fn borrow_from_slice(data: &'_ [u8]) -> Result<&'_ Self, RawHeaderError> {
272 let size = size_of::<Self>();
273 Self::check_size(data)?;
274 let addr = data as *const [u8] as *const () as usize;
275 Self::handle_pod_cast(bytemuck::try_from_bytes(&data[..size]), addr)
276 }
277
278 pub fn borrow_from_slice_mut(data: &'_ mut [u8]) -> Result<&'_ mut Self, RawHeaderError> {
284 let size = size_of::<Self>();
285 Self::check_size(data)?;
286 let addr = data as *const [u8] as *const () as usize;
287 Self::handle_pod_cast(bytemuck::try_from_bytes_mut(&mut data[..size]), addr)
288 }
289
290 pub fn display(&self, indent: usize) -> DisplayHeader<'_> {
292 DisplayHeader { header: self, indent }
293 }
294}
295
296pub struct DisplayHeader<'a> {
298 header: &'a Header,
299 indent: usize,
300}
301
302impl Display for DisplayHeader<'_> {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 let i = " ".repeat(self.indent);
305 let header = &self.header;
306 writeln!(f, "{i}Header version .......... : {}", header.version())?;
307 writeln!(f, "{i}Title ................... : {}", header.title)?;
308 writeln!(f, "{i}Gamecode ................ : {}", header.gamecode)?;
309 writeln!(f, "{i}Makercode ............... : {}", header.makercode)?;
310 writeln!(f, "{i}Unitcode ................ : {}", header.unitcode)?;
311 writeln!(f, "{i}DS flags ................ : {}", header.ds_flags)?;
312 writeln!(f, "{i}DSi flags ............... : {}", header.dsi_flags)?;
313 writeln!(f, "{i}Capacity ................ : {}", header.capacity)?;
314 writeln!(f, "{i}ROM size (DS) ........... : {} ({:#x})", BlobSize(header.rom_size_ds as usize), header.rom_size_ds)?;
315 writeln!(f, "{i}ROM version ............. : {}", header.rom_version)?;
316 write!(f, "{i}ARM9 program\n{}", header.arm9.display(self.indent + 2))?;
317 writeln!(f, "{i}ARM9 autoload callback .. : {:#x}", header.arm9_autoload_callback)?;
318 writeln!(f, "{i}ARM9 build info offset .. : {:#x}", header.arm9_build_info_offset)?;
319 write!(f, "{i}ARM7 program\n{}", header.arm7.display(self.indent + 2))?;
320 writeln!(f, "{i}ARM7 autoload callback .. : {:#x}", header.arm7_autoload_callback)?;
321 writeln!(f, "{i}ARM7 build info offset .. : {:#x}", header.arm7_build_info_offset)?;
322 write!(f, "{i}File name table\n{}", header.file_names.display(self.indent + 2))?;
323 write!(f, "{i}File allocation table\n{}", header.file_allocs.display(self.indent + 2))?;
324 writeln!(f, "{i}Banner\n{i} Offset: {:#x}", header.banner_offset)?;
325 writeln!(f, "{i}Normal cmd setting ...... : {:#x}", header.normal_cmd_setting)?;
326 writeln!(f, "{i}KEY1 cmd setting ........ : {:#x}", header.key1_cmd_setting)?;
327 writeln!(f, "{i}Seed select ............. : {:#x}", header.seed_select)?;
328 writeln!(f, "{i}Autostart ............... : {:#x}", header.autostart)?;
329 writeln!(f, "{i}Secure area disable ..... : {:#x}", header.secure_area_disable)?;
330 writeln!(f, "{i}Secure area delay ....... : {} ({:#x})", header.secure_area_delay, header.secure_area_delay.0)?;
331 writeln!(f, "{i}Secure area CRC ......... : {:#x}", header.secure_area_crc)?;
332 writeln!(f, "{i}Logo CRC ................ : {:#x}", header.logo_crc)?;
333 writeln!(f, "{i}Header CRC .............. : {:#x}", header.header_crc)?;
334 write!(f, "{i}Logo .................... : ")?;
335 match Logo::decompress(&self.header.logo) {
336 Ok(logo) => writeln!(f, "\n{logo}")?,
337 Err(_) => writeln!(f, "Failed to decompress")?,
338 };
339 writeln!(f, "{i}DS ROM region end ....... : {:#x}", header.ds_rom_region_end)?;
340 writeln!(f, "{i}DSi ROM region end ...... : {:#x}", header.dsi_rom_region_end)?;
341 writeln!(f, "{i}ROM NAND end ............ : {:#x}", header.rom_nand_end)?;
342 writeln!(f, "{i}RW NAND end ............. : {:#x}", header.rw_nand_end)?;
343 writeln!(f, "{i}Debug ROM offset ........ : {:#x}", header.debug_rom_offset)?;
344 writeln!(f, "{i}Debug size .............. : {:#x}", header.debug_size)?;
345 writeln!(f, "{i}Debug RAM address ....... : {:#x}", header.debug_ram_addr)?;
346 writeln!(f, "{i}Header size ............. : {:#x}", header.header_size)?;
347 Ok(())
348 }
349}
350
351#[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone, Copy)]
353pub enum HeaderVersion {
354 Original,
356 DsPostDsi,
358}
359
360impl Display for HeaderVersion {
361 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362 match self {
363 HeaderVersion::Original => write!(f, "Original"),
364 HeaderVersion::DsPostDsi => write!(f, "DS after DSi release"),
365 }
366 }
367}
368
369#[derive(Clone, Copy)]
371pub struct Capacity(pub u8);
372
373impl Capacity {
374 pub fn from_size(size: u32) -> Self {
376 let bits = 32 - size.leading_zeros() as u8;
377 Self(bits.saturating_sub(17))
378 }
379}
380
381impl Display for Capacity {
382 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
383 match self.0 {
384 0..=2 => write!(f, "{}kB", 128 << self.0),
385 3.. => write!(f, "{}MB", 1 << (self.0 - 3)),
386 }
387 }
388}
389
390#[bitfield(u8)]
392pub struct DsiFlags {
393 dsi_title: bool,
395 modcrypted: bool,
397 modcrypt_debug_key: bool,
399 disable_debug: bool,
401 #[bits(4)]
403 reserved: u8,
404}
405
406macro_rules! write_flag {
407 ($f:ident, $comma:ident, $flag:expr, $name:literal) => {
408 #[allow(unused_assignments)]
409 if $flag {
410 if $comma {
411 write!($f, ", ")?;
412 }
413 write!($f, $name)?;
414 $comma = true;
415 }
416 };
417}
418
419impl Display for DsiFlags {
420 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
421 if self.0 == 0x00 {
422 write!(f, "Normal")
423 } else {
424 let mut comma = false;
425 write_flag!(f, comma, self.dsi_title(), "DSi title");
426 write_flag!(f, comma, self.modcrypted(), "Modcrypted");
427 write_flag!(f, comma, self.modcrypt_debug_key(), "Modcrypt debug key");
428 write_flag!(f, comma, self.disable_debug(), "Disable debug");
429 Ok(())
430 }
431 }
432}
433
434#[bitfield(u8)]
436#[derive(Serialize, Deserialize)]
437pub struct DsFlags {
438 permit_jump: bool,
440 permit_tmpjump: bool,
442 #[bits(4)]
444 reserved: u8,
445 korea_region: bool,
447 china_region: bool,
449}
450
451impl Display for DsFlags {
452 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
453 if self.0 == 0x00 {
454 write!(f, "Normal")
455 } else {
456 let mut comma = false;
457 write_flag!(f, comma, self.china_region(), "China");
458 write_flag!(f, comma, self.korea_region(), "Korea");
459 write_flag!(f, comma, self.permit_jump(), "Permit jump");
460 write_flag!(f, comma, self.permit_tmpjump(), "Permit tmpjump");
461 Ok(())
462 }
463 }
464}
465
466#[repr(C)]
468#[derive(Clone, Copy, Zeroable, Pod, Default)]
469pub struct ProgramOffset {
470 pub offset: u32,
472 pub entry: u32,
474 pub base_addr: u32,
476 pub size: u32,
478}
479
480impl ProgramOffset {
481 pub fn display(&self, indent: usize) -> DisplayProgramOffset<'_> {
483 DisplayProgramOffset { offset: self, indent }
484 }
485}
486
487pub struct DisplayProgramOffset<'a> {
489 offset: &'a ProgramOffset,
490 indent: usize,
491}
492
493impl Display for DisplayProgramOffset<'_> {
494 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
495 let i = " ".repeat(self.indent);
496 let offset = &self.offset;
497 writeln!(f, "{i}Offset ........ : {:#x}", offset.offset)?;
498 writeln!(f, "{i}Entrypoint .... : {:#x}", offset.entry)?;
499 writeln!(f, "{i}Base address .. : {:#x}", offset.base_addr)?;
500 writeln!(f, "{i}Size .......... : {:#x}", offset.size)?;
501 Ok(())
502 }
503}
504
505#[repr(C)]
507#[derive(Clone, Copy, Zeroable, Pod, Default)]
508pub struct TableOffset {
509 pub offset: u32,
511 pub size: u32,
513}
514
515impl TableOffset {
516 pub fn display(&self, indent: usize) -> DisplayTableOffset<'_> {
518 DisplayTableOffset { offset: self, indent }
519 }
520}
521
522pub struct DisplayTableOffset<'a> {
524 offset: &'a TableOffset,
525 indent: usize,
526}
527
528impl Display for DisplayTableOffset<'_> {
529 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
530 let i = " ".repeat(self.indent);
531 let offset = &self.offset;
532 writeln!(f, "{i}Offset .. : {:#x}", offset.offset)?;
533 writeln!(f, "{i}Size .... : {:#x}", offset.size)?;
534 Ok(())
535 }
536}
537
538#[derive(Clone, Copy, Serialize, Deserialize, Default)]
540pub struct Delay(pub u16);
541
542impl Display for Delay {
543 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
544 write!(f, "{:.1}ms", self.0 as f32 / 131.072)
545 }
546}
547
548#[bitfield(u32)]
550pub struct RegionFlags {
551 japan: bool,
552 usa: bool,
553 europe: bool,
554 australia: bool,
555 china: bool,
556 korea: bool,
557 #[bits(26)]
558 reserved: u32,
559}
560
561impl Display for RegionFlags {
562 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
563 if self.0 == 0x00 {
564 write!(f, "None")
565 } else if self.0 == 0xffffffff {
566 write!(f, "Region free")
567 } else {
568 let mut comma = false;
569 write_flag!(f, comma, self.japan(), "Japan");
570 write_flag!(f, comma, self.usa(), "USA");
571 write_flag!(f, comma, self.europe(), "Europe");
572 write_flag!(f, comma, self.australia(), "Australia");
573 write_flag!(f, comma, self.china(), "China");
574 write_flag!(f, comma, self.korea(), "Korea");
575 Ok(())
576 }
577 }
578}
579
580#[bitfield(u32)]
582pub struct AccessControl {
583 common_client_key: bool,
584 aes_slot_b: bool,
585 aes_slot_c: bool,
586 sd_card: bool,
587 nand_access: bool,
588 card_power_on: bool,
589 shared2_file: bool,
590 sign_jpeg_for_launcher: bool,
591 card_ds_mode: bool,
592 ssl_client_cert: bool,
593 sign_jpeg_for_user: bool,
594 photo_read: bool,
595 photo_write: bool,
596 sd_card_read: bool,
597 sd_card_write: bool,
598 card_save_read: bool,
599 card_save_write: bool,
600 #[bits(14)]
601 reserved: u32,
602 debugger_common_client_key: bool,
603}
604
605#[bitfield(u32)]
607#[derive(Serialize, Deserialize)]
608pub struct DsiFlags2 {
609 tsc_dsi_mode: bool,
611 require_eula_agreement: bool,
612 dynamic_icon: bool,
614 launcher_wfc_icon: bool,
616 launcher_wireless_icon: bool,
618 has_icon_sha1: bool,
619 has_header_rsa: bool,
620 developer_app: bool,
621 #[bits(24)]
622 reserved: u32,
623}