Skip to main content

ds_rom/rom/raw/
header.rs

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/// ROM header.
17#[repr(C)]
18#[derive(Clone, Copy)]
19pub struct Header {
20    /// Short game title, normally in uppercase letters.
21    pub title: AsciiArray<12>,
22    /// 4-character game code in uppercase letters.
23    pub gamecode: AsciiArray<4>,
24    /// 2-character maker code, normally "01".
25    pub makercode: AsciiArray<2>,
26    /// Unit code, depends on which platform (DS, DSi) this game is for.
27    pub unitcode: u8,
28    /// Encryption seed select.
29    pub seed_select: u8,
30    /// ROM capacity, powers of two starting from 128kB.
31    pub capacity: Capacity,
32    /// Reserved, zero.
33    pub reserved0: [u8; 7],
34    /// DSi-specific flags.
35    pub dsi_flags: DsiFlags,
36    /// Flags for both DS and DSi.
37    pub ds_flags: DsFlags,
38    /// ROM version, usually zero.
39    pub rom_version: u8,
40    /// Autostart, can skip "Health and Safety" screen.
41    pub autostart: u8,
42    /// ARM9 program offset.
43    pub arm9: ProgramOffset,
44    /// ARM7 program offset.
45    pub arm7: ProgramOffset,
46    /// File Name Table (FNT) offset.
47    pub file_names: TableOffset,
48    /// File Allocation Table (FAT) offset.
49    pub file_allocs: TableOffset,
50    /// ARM9 overlay table offset.
51    pub arm9_overlays: TableOffset,
52    /// ARM7 overlay table offset.
53    pub arm7_overlays: TableOffset,
54    /// Port 0x40001a4 setting for normal commands.
55    pub normal_cmd_setting: u32,
56    /// Port 0x40001a4 setting for KEY1 commands.
57    pub key1_cmd_setting: u32,
58    /// Banner offset.
59    pub banner_offset: u32,
60    /// CRC checksum of ARM9 secure area.
61    pub secure_area_crc: u16,
62    /// Delay to wait for secure area.
63    pub secure_area_delay: Delay,
64    /// ARM9 autoload callback.
65    pub arm9_autoload_callback: u32,
66    /// ARM7 autoload callback.
67    pub arm7_autoload_callback: u32,
68    /// Can be set to encrypted "NmMdOnly" to disable secure area de/encryption.
69    pub secure_area_disable: u64,
70    /// Total ROM size. If this is a DSi game, this value excludes the DSi area.
71    pub rom_size_ds: u32,
72    /// Size of header, always 0x4000.
73    pub header_size: u32,
74    /// ARM9 build info offset, see [`super::BuildInfo`].
75    pub arm9_build_info_offset: u32,
76    /// ARM7 build info offset, see [`super::BuildInfo`].
77    pub arm7_build_info_offset: u32,
78    /// DS ROM region end in multiples of 0x80000. Zero for non-DSi games.
79    pub ds_rom_region_end: u16,
80    /// DSi ROM region end in multiples of 0x80000. Zero for non-DSi games.
81    pub dsi_rom_region_end: u16,
82    /// NAND end of ROM area in multiples of 0x20000 (0x80000 on DSi).
83    pub rom_nand_end: u16,
84    /// NAND end of RW area in multiples of 0x20000 (0x80000 on DSi).
85    pub rw_nand_end: u16,
86    /// Reserved, zero.
87    pub reserved1: [u8; 0x18],
88    /// Reserved, zero.
89    pub reserved2: [u8; 0x10],
90    /// Compressed logo, see [`Logo`].
91    pub logo: [u8; 0x9c],
92    /// CRC checksum of [`Self::logo`].
93    pub logo_crc: u16,
94    /// CRC checksum of everything before this member.
95    pub header_crc: u16,
96    /// Debug ROM offset, only for debug builds.
97    pub debug_rom_offset: u32,
98    /// Debug ROM size, only for debug builds.
99    pub debug_size: u32,
100    /// Debug RAM address, only for debug builds.
101    pub debug_ram_addr: u32,
102    /// Reserved, zero
103    pub reserved3: [u8; 4],
104    /// Reserved, zero
105    pub reserved4: [u8; 0x10],
106    // The below fields only exists on games released after the DSi, and are otherwise zero.
107    /// MBK1 to MBK5
108    pub memory_banks_wram: [u32; 5],
109    /// MBK6 to MBK8
110    pub memory_banks_arm9: [u32; 3],
111    /// MBK6 to MBK8
112    pub memory_banks_arm7: [u32; 3],
113    /// MBK9
114    pub memory_bank_9: u32,
115    /// Region flags.
116    pub region_flags: RegionFlags,
117    /// Access control.
118    pub access_control: AccessControl,
119    /// ARM7 SCFG_EXT7 setting.
120    pub arm7_scfg_ext7_setting: u32,
121    /// DSi-exclusive flags.
122    pub dsi_flags_2: DsiFlags2,
123    /// ARM9i program offset.
124    pub arm9i: ProgramOffset,
125    /// ARM7i program offset.
126    pub arm7i: ProgramOffset,
127    /// DS area digest range.
128    pub digest_ds_area: TableOffset,
129    /// DSi area digest range.
130    pub digest_dsi_area: TableOffset,
131    /// Digest sector hashtable offset.
132    pub digest_sector_hashtable: TableOffset,
133    /// Digest block hashtable offset.
134    pub digest_block_hashtable: TableOffset,
135    /// Digest sector size.
136    pub digest_sector_size: u32,
137    /// Digest sector count.
138    pub digest_sector_count: u32,
139    /// Banner size.
140    pub banner_size: u32,
141    /// SD/MMC size of shared2/0000 file
142    pub sd_shared2_0000_size: u8,
143    /// SD/MMC size of shared2/0001 file
144    pub sd_shared2_0001_size: u8,
145    /// EULA version.
146    pub eula_version: u8,
147    /// Use age ratings.
148    pub use_ratings: bool,
149    /// Total ROM size, including DSi area.
150    pub rom_size_dsi: u32,
151    /// SD/MMC size of shared/0002 file
152    pub sd_shared2_0002_size: u8,
153    /// SD/MMC size of shared/0003 file
154    pub sd_shared2_0003_size: u8,
155    /// SD/MMC size of shared/0004 file
156    pub sd_shared2_0004_size: u8,
157    /// SD/MMC size of shared/0005 file
158    pub sd_shared2_0005_size: u8,
159    /// ARM9i build info offset.
160    pub arm9i_build_info_offset: u32,
161    /// ARM7i build info offset.
162    pub arm7i_build_info_offset: u32,
163    /// Modcrypt area 1 offset.
164    pub modcrypt_area_1: TableOffset,
165    /// Modcrypt area 1 offset.
166    pub modcrypt_area_2: TableOffset,
167    /// Same as [`Self::gamecode`] but byte.reversed.
168    pub gamecode_rev: AsciiArray<4>,
169    /// File type.
170    pub file_type: u32,
171    /// SD/MMC public.sav file size.
172    pub sd_public_sav_size: u32,
173    /// SD/MMC private.sav file size.
174    pub sd_private_sav_size: u32,
175    /// Reserved, zero.
176    pub reserved5: [u8; 0xb0],
177    /// Age ratings.
178    pub age_ratings: [u8; 0x10],
179    /// SHA1-HMAC of ARM9 program including secure area.
180    pub sha1_hmac_arm9_with_secure_area: [u8; 0x14],
181    /// SHA1-HMAC of ARM7 program.
182    pub sha1_hmac_arm7: [u8; 0x14],
183    /// SHA1-HMAC of digest section.
184    pub sha1_hmac_digest: [u8; 0x14],
185    /// SHA1-HMAC of banner.
186    pub sha1_hmac_banner: [u8; 0x14],
187    /// SHA1-HMAC of decrypted ARM9i.
188    pub sha1_hmac_arm9i: [u8; 0x14],
189    /// SHA1-HMAC of decrypted ARM7i.
190    pub sha1_hmac_arm7i: [u8; 0x14],
191    /// Unknown SHA1-HMAC, defined by some games.
192    pub sha1_hmac_unk1: [u8; 0x14],
193    /// Unknown SHA1-HMAC, defined by some games.
194    pub sha1_hmac_unk2: [u8; 0x14],
195    /// SHA1-HMAC of ARM9 program excluding secure area.
196    pub sha1_hmac_arm9: [u8; 0x14],
197    /// Reserved, zero.
198    pub reserved6: [u8; 0xa4c],
199    /// Used for passing arguments in debug environment.
200    pub debug_args: [u8; 0x180],
201    /// RSA-SHA1 signature up to [`Self::debug_args`].
202    pub rsa_sha1: [u8; 0x80],
203    /// Reserved, zero.
204    pub reserved7: [u8; 0x3000],
205}
206
207unsafe impl Zeroable for Header {}
208unsafe impl Pod for Header {}
209
210/// Errors related to [`Header`].
211#[derive(Debug, Snafu)]
212pub enum RawHeaderError {
213    /// Occurs when the input is too small to contain a header.
214    #[snafu(display("expected {expected:#x} bytes for header but had only {actual:#x}:\n{backtrace}"))]
215    DataTooSmall {
216        /// Expected size.
217        expected: usize,
218        /// Actual input size.
219        actual: usize,
220        /// Backtrace to the source of the error.
221        backtrace: Backtrace,
222    },
223    /// Occurs when the input is less aligned than [`Header`].
224    #[snafu(display("expected {expected}-alignment for header but got {actual}-alignment:\n{backtrace}"))]
225    Misaligned {
226        /// Expected alignment.
227        expected: usize,
228        /// Actual input alignment.
229        actual: usize,
230        /// Backtrace to the source of the error.
231        backtrace: Backtrace,
232    },
233}
234
235impl Header {
236    /// Returns the version of this [`Header`].
237    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    /// Reinterprets a `&[u8]` as a reference to [`Header`].
267    ///
268    /// # Errors
269    ///
270    /// This function will return an error if the input is too small or not aligned enough.
271    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    /// Reinterprets a `&mut [u8]` as a mutable reference to [`Header`].
279    ///
280    /// # Errors
281    ///
282    /// This function will return an error if the input is too small or not aligned enough.
283    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    /// Creates a [`DisplayHeader`] which implements [`Display`].
291    pub fn display(&self, indent: usize) -> DisplayHeader<'_> {
292        DisplayHeader { header: self, indent }
293    }
294}
295
296/// Can be used to display values inside [`Header`].
297pub 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/// Header version. Used for determining which fields are relevant in the header.
352#[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone, Copy)]
353pub enum HeaderVersion {
354    /// Original, before DSi release.
355    Original,
356    /// DS game after DSi release but no DSi-specific features used.
357    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/// ROM capacity.
370#[derive(Clone, Copy)]
371pub struct Capacity(pub u8);
372
373impl Capacity {
374    /// Calculates the needed capacity from a given ROM size.
375    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/// DSi-specific flags.
391#[bitfield(u8)]
392pub struct DsiFlags {
393    /// If `true`, the ROM has a DSi area.
394    dsi_title: bool,
395    /// If `true`, the ROM is modcrypted.
396    modcrypted: bool,
397    /// If `true`, use debug key, otherwise retail key.
398    modcrypt_debug_key: bool,
399    /// Disable debug?
400    disable_debug: bool,
401    /// Reserved, zero.
402    #[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/// Flags for both DS and DSi.
435#[bitfield(u8)]
436#[derive(Serialize, Deserialize)]
437pub struct DsFlags {
438    /// Permit jump.
439    permit_jump: bool,
440    /// Permit tmpjump.
441    permit_tmpjump: bool,
442    /// Reserved, zero.
443    #[bits(4)]
444    reserved: u8,
445    /// Released in Korea if `true`.
446    korea_region: bool,
447    /// Released in China if `true`.
448    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/// Program offset, used for ARM9, ARM7, ARM9i and ARM7i.
467#[repr(C)]
468#[derive(Clone, Copy, Zeroable, Pod, Default)]
469pub struct ProgramOffset {
470    /// ROM offset to start of program.
471    pub offset: u32,
472    /// Entrypoint function address.
473    pub entry: u32,
474    /// Base RAM address.
475    pub base_addr: u32,
476    /// Program size in the ROM.
477    pub size: u32,
478}
479
480impl ProgramOffset {
481    /// Creates a [`DisplayProgramOffset`] which implements [`Display`].
482    pub fn display(&self, indent: usize) -> DisplayProgramOffset<'_> {
483        DisplayProgramOffset { offset: self, indent }
484    }
485}
486
487/// Can be used to display values inside [`ProgramOffset`].
488pub 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/// Offset to a table in the ROM.
506#[repr(C)]
507#[derive(Clone, Copy, Zeroable, Pod, Default)]
508pub struct TableOffset {
509    /// ROM offset to start of table.
510    pub offset: u32,
511    /// Table size in the ROM.
512    pub size: u32,
513}
514
515impl TableOffset {
516    /// Creates a [`DisplayTableOffset`] which implements [`Display`].
517    pub fn display(&self, indent: usize) -> DisplayTableOffset<'_> {
518        DisplayTableOffset { offset: self, indent }
519    }
520}
521
522/// Can be used to display values inside [`TableOffset`].
523pub 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/// Secure area delay.
539#[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/// Region flags, only used in DSi titles.
549#[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/// Access control flags.
581#[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/// DSi-specific flags.
606#[bitfield(u32)]
607#[derive(Serialize, Deserialize)]
608pub struct DsiFlags2 {
609    /// Touchscreen/Sound Controller (TSC) in DSi (true) or DS (false) mode
610    tsc_dsi_mode: bool,
611    require_eula_agreement: bool,
612    /// If true, use banner.sav to override default banner icon
613    dynamic_icon: bool,
614    /// If true, show Wi-Fi Connection icon in launcher
615    launcher_wfc_icon: bool,
616    /// If true, show DS Wireless icon in launcher
617    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}