1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
use std::{
    fmt::Display,
    mem::{align_of, size_of},
};

use bitfield_struct::bitfield;
use bytemuck::{Pod, PodCastError, Zeroable};
use serde::{Deserialize, Serialize};
use snafu::{Backtrace, Snafu};

use crate::{
    rom::Logo,
    str::{AsciiArray, BlobSize},
};

/// ROM header.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct Header {
    /// Short game title, normally in uppercase letters.
    pub title: AsciiArray<12>,
    /// 4-character game code in uppercase letters.
    pub gamecode: AsciiArray<4>,
    /// 2-character maker code, normally "01".
    pub makercode: AsciiArray<2>,
    /// Unit code, depends on which platform (DS, DSi) this game is for.
    pub unitcode: u8,
    /// Encryption seed select.
    pub seed_select: u8,
    /// ROM capacity, powers of two starting from 128kB.
    pub capacity: Capacity,
    /// Reserved, zero.
    pub reserved0: [u8; 7],
    /// DSi-specific flags.
    pub dsi_flags: DsiFlags,
    /// Flags for both DS and DSi.
    pub ds_flags: DsFlags,
    /// ROM version, usually zero.
    pub rom_version: u8,
    /// Autostart, can skip "Health and Safety" screen.
    pub autostart: u8,
    /// ARM9 program offset.
    pub arm9: ProgramOffset,
    /// ARM7 program offset.
    pub arm7: ProgramOffset,
    /// File Name Table (FNT) offset.
    pub file_names: TableOffset,
    /// File Allocation Table (FAT) offset.
    pub file_allocs: TableOffset,
    /// ARM9 overlay table offset.
    pub arm9_overlays: TableOffset,
    /// ARM7 overlay table offset.
    pub arm7_overlays: TableOffset,
    /// Port 0x40001a4 setting for normal commands.
    pub normal_cmd_setting: u32,
    /// Port 0x40001a4 setting for KEY1 commands.
    pub key1_cmd_setting: u32,
    /// Banner offset.
    pub banner_offset: u32,
    /// CRC checksum of ARM9 secure area.
    pub secure_area_crc: u16,
    /// Delay to wait for secure area.
    pub secure_area_delay: Delay,
    /// ARM9 autoload callback.
    pub arm9_autoload_callback: u32,
    /// ARM7 autoload callback.
    pub arm7_autoload_callback: u32,
    /// Can be set to encrypted "NmMdOnly" to disable secure area de/encryption.
    pub secure_area_disable: u64,
    /// Total ROM size. If this is a DSi game, this value excludes the DSi area.
    pub rom_size_ds: u32,
    /// Size of header, always 0x4000.
    pub header_size: u32,
    /// ARM9 build info offset, see [`super::BuildInfo`].
    pub arm9_build_info_offset: u32,
    /// ARM7 build info offset, see [`super::BuildInfo`].
    pub arm7_build_info_offset: u32,
    /// DS ROM region end in multiples of 0x80000. Zero for non-DSi games.
    pub ds_rom_region_end: u16,
    /// DSi ROM region end in multiples of 0x80000. Zero for non-DSi games.
    pub dsi_rom_region_end: u16,
    /// NAND end of ROM area in multiples of 0x20000 (0x80000 on DSi).
    pub rom_nand_end: u16,
    /// NAND end of RW area in multiples of 0x20000 (0x80000 on DSi).
    pub rw_nand_end: u16,
    /// Reserved, zero.
    pub reserved1: [u8; 0x18],
    /// Reserved, zero.
    pub reserved2: [u8; 0x10],
    /// Compressed logo, see [`Logo`].
    pub logo: [u8; 0x9c],
    /// CRC checksum of [`Self::logo`].
    pub logo_crc: u16,
    /// CRC checksum of everything before this member.
    pub header_crc: u16,
    /// Debug ROM offset, only for debug builds.
    pub debug_rom_offset: u32,
    /// Debug ROM size, only for debug builds.
    pub debug_size: u32,
    /// Debug RAM address, only for debug builds.
    pub debug_ram_addr: u32,
    /// Reserved, zero
    pub reserved3: [u8; 4],
    /// Reserved, zero
    pub reserved4: [u8; 0x10],
    // The below fields only exists on games released after the DSi, and are otherwise zero.
    /// MBK1 to MBK5
    pub memory_banks_wram: [u32; 5],
    /// MBK6 to MBK8
    pub memory_banks_arm9: [u32; 3],
    /// MBK6 to MBK8
    pub memory_banks_arm7: [u32; 3],
    /// MBK9
    pub memory_bank_9: u32,
    /// Region flags.
    pub region_flags: RegionFlags,
    /// Access control.
    pub access_control: AccessControl,
    /// ARM7 SCFG_EXT7 setting.
    pub arm7_scfg_ext7_setting: u32,
    /// DSi-exclusive flags.
    pub dsi_flags_2: DsiFlags2,
    /// ARM9i program offset.
    pub arm9i: ProgramOffset,
    /// ARM7i program offset.
    pub arm7i: ProgramOffset,
    /// DS area digest range.
    pub digest_ds_area: TableOffset,
    /// DSi area digest range.
    pub digest_dsi_area: TableOffset,
    /// Digest sector hashtable offset.
    pub digest_sector_hashtable: TableOffset,
    /// Digest block hashtable offset.
    pub digest_block_hashtable: TableOffset,
    /// Digest sector size.
    pub digest_sector_size: u32,
    /// Digest sector count.
    pub digest_sector_count: u32,
    /// Banner size.
    pub banner_size: u32,
    /// SD/MMC size of shared2/0000 file
    pub sd_shared2_0000_size: u8,
    /// SD/MMC size of shared2/0001 file
    pub sd_shared2_0001_size: u8,
    /// EULA version.
    pub eula_version: u8,
    /// Use age ratings.
    pub use_ratings: bool,
    /// Total ROM size, including DSi area.
    pub rom_size_dsi: u32,
    /// SD/MMC size of shared/0002 file
    pub sd_shared2_0002_size: u8,
    /// SD/MMC size of shared/0003 file
    pub sd_shared2_0003_size: u8,
    /// SD/MMC size of shared/0004 file
    pub sd_shared2_0004_size: u8,
    /// SD/MMC size of shared/0005 file
    pub sd_shared2_0005_size: u8,
    /// ARM9i build info offset.
    pub arm9i_build_info_offset: u32,
    /// ARM7i build info offset.
    pub arm7i_build_info_offset: u32,
    /// Modcrypt area 1 offset.
    pub modcrypt_area_1: TableOffset,
    /// Modcrypt area 1 offset.
    pub modcrypt_area_2: TableOffset,
    /// Same as [`Self::gamecode`] but byte.reversed.
    pub gamecode_rev: AsciiArray<4>,
    /// File type.
    pub file_type: u32,
    /// SD/MMC public.sav file size.
    pub sd_public_sav_size: u32,
    /// SD/MMC private.sav file size.
    pub sd_private_sav_size: u32,
    /// Reserved, zero.
    pub reserved5: [u8; 0xb0],
    /// Age ratings.
    pub age_ratings: [u8; 0x10],
    /// SHA1-HMAC of ARM9 program including secure area.
    pub sha1_hmac_arm9_with_secure_area: [u8; 0x14],
    /// SHA1-HMAC of ARM7 program.
    pub sha1_hmac_arm7: [u8; 0x14],
    /// SHA1-HMAC of digest section.
    pub sha1_hmac_digest: [u8; 0x14],
    /// SHA1-HMAC of banner.
    pub sha1_hmac_banner: [u8; 0x14],
    /// SHA1-HMAC of decrypted ARM9i.
    pub sha1_hmac_arm9i: [u8; 0x14],
    /// SHA1-HMAC of decrypted ARM7i.
    pub sha1_hmac_arm7i: [u8; 0x14],
    /// Unknown SHA1-HMAC, defined by some games.
    pub sha1_hmac_unk1: [u8; 0x14],
    /// Unknown SHA1-HMAC, defined by some games.
    pub sha1_hmac_unk2: [u8; 0x14],
    /// SHA1-HMAC of ARM9 program excluding secure area.
    pub sha1_hmac_arm9: [u8; 0x14],
    /// Reserved, zero.
    pub reserved6: [u8; 0xa4c],
    /// Used for passing arguments in debug environment.
    pub debug_args: [u8; 0x180],
    /// RSA-SHA1 signature up to [`Self::debug_args`].
    pub rsa_sha1: [u8; 0x80],
    /// Reserved, zero.
    pub reserved7: [u8; 0x3000],
}

unsafe impl Zeroable for Header {}
unsafe impl Pod for Header {}

/// Errors related to [`Header`].
#[derive(Debug, Snafu)]
pub enum RawHeaderError {
    /// Occurs when the input is too small to contain a header.
    #[snafu(display("expected {expected:#x} bytes for header but had only {actual:#x}:\n{backtrace}"))]
    DataTooSmall {
        /// Expected size.
        expected: usize,
        /// Actual input size.
        actual: usize,
        /// Backtrace to the source of the error.
        backtrace: Backtrace,
    },
    /// Occurs when the input is less aligned than [`Header`].
    #[snafu(display("expected {expected}-alignment for header but got {actual}-alignment:\n{backtrace}"))]
    Misaligned {
        /// Expected alignment.
        expected: usize,
        /// Actual input alignment.
        actual: usize,
        /// Backtrace to the source of the error.
        backtrace: Backtrace,
    },
}

impl Header {
    /// Returns the version of this [`Header`].
    pub fn version(&self) -> HeaderVersion {
        if self.dsi_flags_2.0 != 0 {
            HeaderVersion::DsPostDsi
        } else {
            HeaderVersion::Original
        }
    }

    fn check_size(data: &'_ [u8]) -> Result<(), RawHeaderError> {
        let size = size_of::<Self>();
        if data.len() < size {
            DataTooSmallSnafu { expected: size, actual: data.len() }.fail()
        } else {
            Ok(())
        }
    }

    fn handle_pod_cast<T>(result: Result<T, PodCastError>, addr: usize) -> Result<T, RawHeaderError> {
        match result {
            Ok(build_info) => Ok(build_info),
            Err(PodCastError::TargetAlignmentGreaterAndInputNotAligned) => {
                MisalignedSnafu { expected: align_of::<Self>(), actual: 1usize << addr.trailing_zeros() }.fail()
            }
            Err(PodCastError::AlignmentMismatch) => panic!(),
            Err(PodCastError::OutputSliceWouldHaveSlop) => panic!(),
            Err(PodCastError::SizeMismatch) => unreachable!(),
        }
    }

    /// Reinterprets a `&[u8]` as a reference to [`Header`].
    ///
    /// # Errors
    ///
    /// This function will return an error if the input is too small or not aligned enough.
    pub fn borrow_from_slice(data: &'_ [u8]) -> Result<&'_ Self, RawHeaderError> {
        let size = size_of::<Self>();
        Self::check_size(data)?;
        let addr = data as *const [u8] as *const () as usize;
        Self::handle_pod_cast(bytemuck::try_from_bytes(&data[..size]), addr)
    }

    /// Reinterprets a `&mut [u8]` as a mutable reference to [`Header`].
    ///
    /// # Errors
    ///
    /// This function will return an error if the input is too small or not aligned enough.
    pub fn borrow_from_slice_mut(data: &'_ mut [u8]) -> Result<&'_ mut Self, RawHeaderError> {
        let size = size_of::<Self>();
        Self::check_size(data)?;
        let addr = data as *const [u8] as *const () as usize;
        Self::handle_pod_cast(bytemuck::try_from_bytes_mut(&mut data[..size]), addr)
    }

    /// Creates a [`DisplayHeader`] which implements [`Display`].
    pub fn display(&self, indent: usize) -> DisplayHeader {
        DisplayHeader { header: self, indent }
    }
}

/// Can be used to display values inside [`Header`].
pub struct DisplayHeader<'a> {
    header: &'a Header,
    indent: usize,
}

impl<'a> Display for DisplayHeader<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let i = format!("{:indent$}", "", indent = self.indent);
        let header = &self.header;
        writeln!(f, "{i}Header version .......... : {}", header.version())?;
        writeln!(f, "{i}Title ................... : {}", header.title)?;
        writeln!(f, "{i}Gamecode ................ : {}", header.gamecode)?;
        writeln!(f, "{i}Makercode ............... : {}", header.makercode)?;
        writeln!(f, "{i}Unitcode ................ : {}", header.unitcode)?;
        writeln!(f, "{i}DS flags ................ : {}", header.ds_flags)?;
        writeln!(f, "{i}DSi flags ............... : {}", header.dsi_flags)?;
        writeln!(f, "{i}Capacity ................ : {}", header.capacity)?;
        writeln!(f, "{i}ROM size (DS) ........... : {} ({:#x})", BlobSize(header.rom_size_ds as usize), header.rom_size_ds)?;
        writeln!(f, "{i}ROM version ............. : {}", header.rom_version)?;
        write!(f, "{i}ARM9 program\n{}", header.arm9.display(self.indent + 2))?;
        writeln!(f, "{i}ARM9 autoload callback .. : {:#x}", header.arm9_autoload_callback)?;
        writeln!(f, "{i}ARM9 build info offset .. : {:#x}", header.arm9_build_info_offset)?;
        write!(f, "{i}ARM7 program\n{}", header.arm7.display(self.indent + 2))?;
        writeln!(f, "{i}ARM7 autoload callback .. : {:#x}", header.arm7_autoload_callback)?;
        writeln!(f, "{i}ARM7 build info offset .. : {:#x}", header.arm7_build_info_offset)?;
        write!(f, "{i}File name table\n{}", header.file_names.display(self.indent + 2))?;
        write!(f, "{i}File allocation table\n{}", header.file_allocs.display(self.indent + 2))?;
        writeln!(f, "{i}Banner\n{i}  Offset: {:#x}", header.banner_offset)?;
        writeln!(f, "{i}Normal cmd setting ...... : {:#x}", header.normal_cmd_setting)?;
        writeln!(f, "{i}KEY1 cmd setting ........ : {:#x}", header.key1_cmd_setting)?;
        writeln!(f, "{i}Seed select ............. : {:#x}", header.seed_select)?;
        writeln!(f, "{i}Autostart ............... : {:#x}", header.autostart)?;
        writeln!(f, "{i}Secure area disable ..... : {:#x}", header.secure_area_disable)?;
        writeln!(f, "{i}Secure area delay ....... : {} ({:#x})", header.secure_area_delay, header.secure_area_delay.0)?;
        writeln!(f, "{i}Secure area CRC ......... : {:#x}", header.secure_area_crc)?;
        writeln!(f, "{i}Logo CRC ................ : {:#x}", header.logo_crc)?;
        writeln!(f, "{i}Header CRC .............. : {:#x}", header.header_crc)?;
        write!(f, "{i}Logo .................... : ")?;
        match Logo::decompress(&self.header.logo) {
            Ok(logo) => writeln!(f, "\n{logo}")?,
            Err(_) => writeln!(f, "Failed to decompress")?,
        };
        writeln!(f, "{i}DS ROM region end ....... : {:#x}", header.ds_rom_region_end)?;
        writeln!(f, "{i}DSi ROM region end ...... : {:#x}", header.dsi_rom_region_end)?;
        writeln!(f, "{i}ROM NAND end ............ : {:#x}", header.rom_nand_end)?;
        writeln!(f, "{i}RW NAND end ............. : {:#x}", header.rw_nand_end)?;
        writeln!(f, "{i}Debug ROM offset ........ : {:#x}", header.debug_rom_offset)?;
        writeln!(f, "{i}Debug size .............. : {:#x}", header.debug_size)?;
        writeln!(f, "{i}Debug RAM address ....... : {:#x}", header.debug_ram_addr)?;
        writeln!(f, "{i}Header size ............. : {:#x}", header.header_size)?;
        Ok(())
    }
}

/// Header version. Used for determining which fields are relevant in the header.
#[derive(PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone, Copy)]
pub enum HeaderVersion {
    /// Original, before DSi release.
    Original,
    /// DS game after DSi release but no DSi-specific features used.
    DsPostDsi,
}

impl Display for HeaderVersion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            HeaderVersion::Original => write!(f, "Original"),
            HeaderVersion::DsPostDsi => write!(f, "DS after DSi release"),
        }
    }
}

/// ROM capacity.
#[derive(Clone, Copy)]
pub struct Capacity(pub u8);

impl Capacity {
    /// Calculates the needed capacity from a given ROM size.
    pub fn from_size(size: u32) -> Self {
        let bits = 32 - size.leading_zeros() as u8;
        Self(bits.saturating_sub(17))
    }
}

impl Display for Capacity {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.0 {
            0..=2 => write!(f, "{}kB", 128 << self.0),
            3.. => write!(f, "{}MB", 1 << (self.0 - 3)),
        }
    }
}

/// DSi-specific flags.
#[bitfield(u8)]
pub struct DsiFlags {
    /// If `true`, the ROM has a DSi area.
    dsi_title: bool,
    /// If `true`, the ROM is modcrypted.
    modcrypted: bool,
    /// If `true`, use debug key, otherwise retail key.
    modcrypt_debug_key: bool,
    /// Disable debug?
    disable_debug: bool,
    /// Reserved, zero.
    #[bits(4)]
    reserved: u8,
}

macro_rules! write_flag {
    ($f:ident, $comma:ident, $flag:expr, $name:literal) => {
        #[allow(unused_assignments)]
        if $flag {
            if $comma {
                write!($f, ", ")?;
            }
            write!($f, $name)?;
            $comma = true;
        }
    };
}

impl Display for DsiFlags {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.0 == 0x00 {
            write!(f, "Normal")
        } else {
            let mut comma = false;
            write_flag!(f, comma, self.dsi_title(), "DSi title");
            write_flag!(f, comma, self.modcrypted(), "Modcrypted");
            write_flag!(f, comma, self.modcrypt_debug_key(), "Modcrypt debug key");
            write_flag!(f, comma, self.disable_debug(), "Disable debug");
            Ok(())
        }
    }
}

/// Flags for both DS and DSi.
#[bitfield(u8)]
#[derive(Serialize, Deserialize)]
pub struct DsFlags {
    /// Permit jump.
    permit_jump: bool,
    /// Permit tmpjump.
    permit_tmpjump: bool,
    /// Reserved, zero.
    #[bits(4)]
    reserved: u8,
    /// Released in Korea if `true`.
    korea_region: bool,
    /// Released in China if `true`.
    china_region: bool,
}

impl Display for DsFlags {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.0 == 0x00 {
            write!(f, "Normal")
        } else {
            let mut comma = false;
            write_flag!(f, comma, self.china_region(), "China");
            write_flag!(f, comma, self.korea_region(), "Korea");
            write_flag!(f, comma, self.permit_jump(), "Permit jump");
            write_flag!(f, comma, self.permit_tmpjump(), "Permit tmpjump");
            Ok(())
        }
    }
}

/// Program offset, used for ARM9, ARM7, ARM9i and ARM7i.
#[repr(C)]
#[derive(Clone, Copy, Zeroable, Pod, Default)]
pub struct ProgramOffset {
    /// ROM offset to start of program.
    pub offset: u32,
    /// Entrypoint function address.
    pub entry: u32,
    /// Base RAM address.
    pub base_addr: u32,
    /// Program size in the ROM.
    pub size: u32,
}

impl ProgramOffset {
    /// Creates a [`DisplayProgramOffset`] which implements [`Display`].
    pub fn display(&self, indent: usize) -> DisplayProgramOffset {
        DisplayProgramOffset { offset: self, indent }
    }
}

/// Can be used to display values inside [`ProgramOffset`].
pub struct DisplayProgramOffset<'a> {
    offset: &'a ProgramOffset,
    indent: usize,
}

impl<'a> Display for DisplayProgramOffset<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let i = format!("{:indent$}", "", indent = self.indent);
        let offset = &self.offset;
        writeln!(f, "{i}Offset ........ : {:#x}", offset.offset)?;
        writeln!(f, "{i}Entrypoint .... : {:#x}", offset.entry)?;
        writeln!(f, "{i}Base address .. : {:#x}", offset.base_addr)?;
        writeln!(f, "{i}Size .......... : {:#x}", offset.size)?;
        Ok(())
    }
}

/// Offset to a table in the ROM.
#[repr(C)]
#[derive(Clone, Copy, Zeroable, Pod, Default)]
pub struct TableOffset {
    /// ROM offset to start of table.
    pub offset: u32,
    /// Table size in the ROM.
    pub size: u32,
}

impl TableOffset {
    /// Creates a [`DisplayTableOffset`] which implements [`Display`].
    pub fn display(&self, indent: usize) -> DisplayTableOffset {
        DisplayTableOffset { offset: self, indent }
    }
}

/// Can be used to display values inside [`TableOffset`].
pub struct DisplayTableOffset<'a> {
    offset: &'a TableOffset,
    indent: usize,
}

impl<'a> Display for DisplayTableOffset<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let i = format!("{:indent$}", "", indent = self.indent);
        let offset = &self.offset;
        writeln!(f, "{i}Offset .. : {:#x}", offset.offset)?;
        writeln!(f, "{i}Size .... : {:#x}", offset.size)?;
        Ok(())
    }
}

/// Secure area delay.
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct Delay(pub u16);

impl Display for Delay {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:.1}ms", self.0 as f32 / 131.072)
    }
}

/// Region flags, only used in DSi titles.
#[bitfield(u32)]
pub struct RegionFlags {
    japan: bool,
    usa: bool,
    europe: bool,
    australia: bool,
    china: bool,
    korea: bool,
    #[bits(26)]
    reserved: u32,
}

impl Display for RegionFlags {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.0 == 0x00 {
            write!(f, "None")
        } else if self.0 == 0xffffffff {
            write!(f, "Region free")
        } else {
            let mut comma = false;
            write_flag!(f, comma, self.japan(), "Japan");
            write_flag!(f, comma, self.usa(), "USA");
            write_flag!(f, comma, self.europe(), "Europe");
            write_flag!(f, comma, self.australia(), "Australia");
            write_flag!(f, comma, self.china(), "China");
            write_flag!(f, comma, self.korea(), "Korea");
            Ok(())
        }
    }
}

/// Access control flags.
#[bitfield(u32)]
pub struct AccessControl {
    common_client_key: bool,
    aes_slot_b: bool,
    aes_slot_c: bool,
    sd_card: bool,
    nand_access: bool,
    card_power_on: bool,
    shared2_file: bool,
    sign_jpeg_for_launcher: bool,
    card_ds_mode: bool,
    ssl_client_cert: bool,
    sign_jpeg_for_user: bool,
    photo_read: bool,
    photo_write: bool,
    sd_card_read: bool,
    sd_card_write: bool,
    card_save_read: bool,
    card_save_write: bool,
    #[bits(14)]
    reserved: u32,
    debugger_common_client_key: bool,
}

/// DSi-specific flags.
#[bitfield(u32)]
#[derive(Serialize, Deserialize)]
pub struct DsiFlags2 {
    /// Touchscreen/Sound Controller (TSC) in DSi (true) or DS (false) mode
    tsc_dsi_mode: bool,
    require_eula_agreement: bool,
    /// If true, use banner.sav to override default banner icon
    dynamic_icon: bool,
    /// If true, show Wi-Fi Connection icon in launcher
    launcher_wfc_icon: bool,
    /// If true, show DS Wireless icon in launcher
    launcher_wireless_icon: bool,
    has_icon_sha1: bool,
    has_header_rsa: bool,
    developer_app: bool,
    #[bits(24)]
    reserved: u32,
}