ds_rom/rom/
header.rs

1use std::mem::{offset_of, size_of};
2
3use serde::{Deserialize, Serialize};
4use snafu::Snafu;
5
6use super::{
7    raw::{
8        self, AccessControl, Capacity, Delay, DsFlags, DsiFlags, DsiFlags2, HeaderVersion, ProgramOffset, RegionFlags,
9        TableOffset,
10    },
11    BuildContext, Rom,
12};
13use crate::{
14    crc::CRC_16_MODBUS,
15    str::{AsciiArray, AsciiArrayError},
16};
17/// ROM header.
18#[derive(Serialize, Deserialize)]
19pub struct Header {
20    /// Values for the original header version, [`HeaderVersion::Original`].
21    #[serde(flatten)]
22    pub original: HeaderOriginal,
23    /// Values for DS games after DSi release, [`HeaderVersion::DsPostDsi`].
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub ds_post_dsi: Option<HeaderDsPostDsi>,
26}
27
28/// Values for the original header version, [`HeaderVersion::Original`].
29#[derive(Serialize, Deserialize)]
30pub struct HeaderOriginal {
31    /// Short game title, normally in uppercase letters.
32    pub title: String,
33    /// 4-character game code in uppercase letters.
34    pub gamecode: AsciiArray<4>,
35    /// 2-character maker code, normally "01".
36    pub makercode: AsciiArray<2>,
37    /// Unit code, depends on which platform (DS, DSi) this game is for.
38    pub unitcode: u8,
39    /// Encryption seed select.
40    pub seed_select: u8,
41    /// Flags for both DS and DSi.
42    pub ds_flags: DsFlags,
43    /// Autostart, can skip "Health and Safety" screen.
44    pub autostart: u8,
45    /// Port 0x40001a4 setting for normal commands.
46    pub normal_cmd_setting: u32,
47    /// Port 0x40001a4 setting for KEY1 commands.
48    pub key1_cmd_setting: u32,
49    /// Delay to wait for secure area.
50    pub secure_area_delay: Delay,
51    /// NAND end of ROM area in multiples of 0x20000 (0x80000 on DSi).
52    pub rom_nand_end: u16,
53    /// NAND end of RW area in multiples of 0x20000 (0x80000 on DSi).
54    pub rw_nand_end: u16,
55}
56
57/// Values for DS games after DSi release, [`HeaderVersion::DsPostDsi`].
58#[derive(Serialize, Deserialize)]
59pub struct HeaderDsPostDsi {
60    /// DSi-exclusive flags.
61    pub dsi_flags_2: DsiFlags2,
62    /// SHA1-HMAC of banner.
63    pub sha1_hmac_banner: [u8; 0x14],
64    /// Unknown SHA1-HMAC, defined by some games.
65    pub sha1_hmac_unk1: [u8; 0x14],
66    /// Unknown SHA1-HMAC, defined by some games.
67    pub sha1_hmac_unk2: [u8; 0x14],
68    /// RSA-SHA1 signature up to [`raw::Header::debug_args`].
69    pub rsa_sha1: Box<[u8]>,
70}
71
72/// Errors related to [`Header::build`].
73#[derive(Snafu, Debug)]
74pub enum HeaderBuildError {
75    /// See [`AsciiArrayError`].
76    #[snafu(transparent)]
77    AsciiArray {
78        /// Source error.
79        source: AsciiArrayError,
80    },
81}
82
83impl Header {
84    /// Loads from a raw header.
85    pub fn load_raw(header: &raw::Header) -> Self {
86        let version = header.version();
87        Self {
88            original: HeaderOriginal {
89                title: header.title.to_string(),
90                gamecode: header.gamecode,
91                makercode: header.makercode,
92                unitcode: header.unitcode,
93                seed_select: header.seed_select,
94                ds_flags: header.ds_flags,
95                autostart: header.autostart,
96                normal_cmd_setting: header.normal_cmd_setting,
97                key1_cmd_setting: header.key1_cmd_setting,
98                secure_area_delay: header.secure_area_delay,
99                rom_nand_end: header.rom_nand_end,
100                rw_nand_end: header.rw_nand_end,
101            },
102            ds_post_dsi: (version >= HeaderVersion::DsPostDsi).then_some(HeaderDsPostDsi {
103                dsi_flags_2: header.dsi_flags_2,
104                sha1_hmac_banner: header.sha1_hmac_banner,
105                sha1_hmac_unk1: header.sha1_hmac_unk1,
106                sha1_hmac_unk2: header.sha1_hmac_unk2,
107                rsa_sha1: Box::new(header.rsa_sha1),
108            }),
109        }
110    }
111
112    /// Builds a raw header.
113    ///
114    /// # Panics
115    ///
116    /// Panics if a value is missing in the `context`.
117    ///
118    /// # Errors
119    ///
120    /// This function will return an error if the title contains a non-ASCII character.
121    pub fn build(&self, context: &BuildContext, rom: &Rom) -> Result<raw::Header, HeaderBuildError> {
122        let logo = rom.header_logo().compress();
123        let arm9 = rom.arm9();
124        let arm7 = rom.arm7();
125        let arm9_offset = context.arm9_offset.expect("ARM9 offset must be known");
126        let arm7_offset = context.arm7_offset.expect("ARM7 offset must be known");
127        let mut header = raw::Header {
128            title: AsciiArray::from_str(&self.original.title)?,
129            gamecode: self.original.gamecode,
130            makercode: self.original.makercode,
131            unitcode: self.original.unitcode,
132            seed_select: self.original.seed_select,
133            capacity: Capacity::from_size(context.rom_size.expect("ROM size must be known")),
134            reserved0: [0; 7],
135            dsi_flags: DsiFlags::new(),
136            ds_flags: self.original.ds_flags,
137            rom_version: 0,
138            autostart: self.original.autostart,
139            arm9: ProgramOffset {
140                offset: arm9_offset,
141                entry: arm9.entry_function(),
142                base_addr: arm9.base_address(),
143                size: arm9.full_data().len() as u32,
144            },
145            arm7: ProgramOffset {
146                offset: arm7_offset,
147                entry: arm7.entry_function(),
148                base_addr: arm7.base_address(),
149                size: arm7.full_data().len() as u32,
150            },
151            file_names: context.fnt_offset.expect("FNT offset must be known"),
152            file_allocs: context.fat_offset.expect("FAT offset must be known"),
153            arm9_overlays: context.arm9_ovt_offset.unwrap_or_default(),
154            arm7_overlays: context.arm7_ovt_offset.unwrap_or_default(),
155            normal_cmd_setting: self.original.normal_cmd_setting,
156            key1_cmd_setting: self.original.key1_cmd_setting,
157            banner_offset: context.banner_offset.map(|b| b.offset).expect("Banner offset must be known"),
158            secure_area_crc: if let Some(key) = context.blowfish_key {
159                arm9.secure_area_crc(key, self.original.gamecode.to_le_u32())
160            } else {
161                0
162            },
163            secure_area_delay: self.original.secure_area_delay,
164            arm9_autoload_callback: context.arm9_autoload_callback.expect("ARM9 autoload callback must be known"),
165            arm7_autoload_callback: context.arm7_autoload_callback.expect("ARM7 autoload callback must be known"),
166            secure_area_disable: 0,
167            rom_size_ds: context.rom_size.expect("ROM size must be known"),
168            header_size: size_of::<raw::Header>() as u32,
169            arm9_build_info_offset: context.arm9_build_info_offset.map(|offset| offset + arm9_offset).unwrap_or(0),
170            arm7_build_info_offset: context.arm7_build_info_offset.map(|offset| offset + arm7_offset).unwrap_or(0),
171            ds_rom_region_end: 0,
172            dsi_rom_region_end: 0,
173            rom_nand_end: self.original.rom_nand_end,
174            rw_nand_end: self.original.rw_nand_end,
175            reserved1: [0; 0x18],
176            reserved2: [0; 0x10],
177            logo,
178            logo_crc: CRC_16_MODBUS.checksum(&logo),
179            header_crc: 0, // gets updated below
180            debug_rom_offset: 0,
181            debug_size: 0,
182            debug_ram_addr: 0,
183            reserved3: [0; 0x4],
184            reserved4: [0; 0x10],
185            // The below fields are for DSi only and are not supported yet
186            memory_banks_wram: [0; 5],
187            memory_banks_arm9: [0; 3],
188            memory_banks_arm7: [0; 3],
189            memory_bank_9: 0,
190            region_flags: RegionFlags::new(),
191            access_control: AccessControl::new(),
192            arm7_scfg_ext7_setting: 0,
193            dsi_flags_2: DsiFlags2::new(),
194            arm9i: ProgramOffset::default(),
195            arm7i: ProgramOffset::default(),
196            digest_ds_area: TableOffset::default(),
197            digest_dsi_area: TableOffset::default(),
198            digest_sector_hashtable: TableOffset::default(),
199            digest_block_hashtable: TableOffset::default(),
200            digest_sector_size: 0,
201            digest_sector_count: 0,
202            banner_size: 0,
203            sd_shared2_0000_size: 0,
204            sd_shared2_0001_size: 0,
205            eula_version: 0,
206            use_ratings: false,
207            rom_size_dsi: 0,
208            sd_shared2_0002_size: 0,
209            sd_shared2_0003_size: 0,
210            sd_shared2_0004_size: 0,
211            sd_shared2_0005_size: 0,
212            arm9i_build_info_offset: 0,
213            arm7i_build_info_offset: 0,
214            modcrypt_area_1: TableOffset::default(),
215            modcrypt_area_2: TableOffset::default(),
216            gamecode_rev: AsciiArray([0; 4]),
217            file_type: 0,
218            sd_public_sav_size: 0,
219            sd_private_sav_size: 0,
220            reserved5: [0; 0xb0],
221            age_ratings: [0; 0x10],
222            sha1_hmac_arm9_with_secure_area: [0; 0x14],
223            sha1_hmac_arm7: [0; 0x14],
224            sha1_hmac_digest: [0; 0x14],
225            sha1_hmac_banner: [0; 0x14],
226            sha1_hmac_arm9i: [0; 0x14],
227            sha1_hmac_arm7i: [0; 0x14],
228            sha1_hmac_unk1: [0; 0x14],
229            sha1_hmac_unk2: [0; 0x14],
230            sha1_hmac_arm9: [0; 0x14],
231            reserved6: [0; 0xa4c],
232            debug_args: [0; 0x180],
233            rsa_sha1: [0; 0x80],
234            reserved7: [0; 0x3000],
235        };
236
237        if let Some(ds_post_dsi) = &self.ds_post_dsi {
238            header.dsi_flags_2 = ds_post_dsi.dsi_flags_2;
239            header.sha1_hmac_banner = ds_post_dsi.sha1_hmac_banner;
240            header.sha1_hmac_unk1 = ds_post_dsi.sha1_hmac_unk1;
241            header.sha1_hmac_unk2 = ds_post_dsi.sha1_hmac_unk2;
242            header.rsa_sha1.copy_from_slice(&ds_post_dsi.rsa_sha1);
243        }
244
245        header.header_crc = CRC_16_MODBUS.checksum(&bytemuck::bytes_of(&header)[0..offset_of!(raw::Header, header_crc)]);
246        Ok(header)
247    }
248
249    /// Returns the version of this [`Header`].
250    pub fn version(&self) -> HeaderVersion {
251        if self.ds_post_dsi.is_some() {
252            HeaderVersion::DsPostDsi
253        } else {
254            HeaderVersion::Original
255        }
256    }
257}