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#[derive(Serialize, Deserialize)]
19pub struct Header {
20 #[serde(flatten)]
22 pub original: HeaderOriginal,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub ds_post_dsi: Option<HeaderDsPostDsi>,
26}
27
28#[derive(Serialize, Deserialize)]
30pub struct HeaderOriginal {
31 pub title: String,
33 pub gamecode: AsciiArray<4>,
35 pub makercode: AsciiArray<2>,
37 pub unitcode: u8,
39 pub seed_select: u8,
41 pub ds_flags: DsFlags,
43 pub autostart: u8,
45 pub normal_cmd_setting: u32,
47 pub key1_cmd_setting: u32,
49 pub secure_area_delay: Delay,
51 pub rom_nand_end: u16,
53 pub rw_nand_end: u16,
55}
56
57#[derive(Serialize, Deserialize)]
59pub struct HeaderDsPostDsi {
60 pub dsi_flags_2: DsiFlags2,
62 pub sha1_hmac_banner: [u8; 0x14],
64 pub sha1_hmac_unk1: [u8; 0x14],
66 pub sha1_hmac_unk2: [u8; 0x14],
68 pub rsa_sha1: Box<[u8]>,
70}
71
72#[derive(Snafu, Debug)]
74pub enum HeaderBuildError {
75 #[snafu(transparent)]
77 AsciiArray {
78 source: AsciiArrayError,
80 },
81}
82
83impl Header {
84 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 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, debug_rom_offset: 0,
181 debug_size: 0,
182 debug_ram_addr: 0,
183 reserved3: [0; 0x4],
184 reserved4: [0; 0x10],
185 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 pub fn version(&self) -> HeaderVersion {
251 if self.ds_post_dsi.is_some() {
252 HeaderVersion::DsPostDsi
253 } else {
254 HeaderVersion::Original
255 }
256 }
257}