1use std::{
2 mem::{offset_of, size_of},
3 str::FromStr,
4};
5
6use serde::{Deserialize, Serialize};
7use snafu::Snafu;
8
9use super::{
10 raw::{
11 self, AccessControl, Capacity, Delay, DsFlags, DsiFlags, DsiFlags2, HeaderVersion, ProgramOffset, RegionFlags,
12 TableOffset,
13 },
14 BuildContext, Rom,
15};
16use crate::{
17 crc::CRC_16_MODBUS,
18 str::{AsciiArray, AsciiArrayError},
19};
20#[derive(Serialize, Deserialize, Default)]
22pub struct Header {
23 #[serde(flatten)]
25 pub original: HeaderOriginal,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub ds_post_dsi: Option<HeaderDsPostDsi>,
29}
30
31#[derive(Serialize, Deserialize, Default)]
33pub struct HeaderOriginal {
34 pub title: String,
36 pub gamecode: AsciiArray<4>,
38 pub makercode: AsciiArray<2>,
40 pub unitcode: u8,
42 pub seed_select: u8,
44 pub ds_flags: DsFlags,
46 pub rom_version: u8,
48 pub autostart: u8,
50 pub normal_cmd_setting: u32,
52 pub key1_cmd_setting: u32,
54 pub secure_area_delay: Delay,
56 pub rom_nand_end: u16,
58 pub rw_nand_end: u16,
60 pub has_arm9_build_info_offset: bool,
62}
63
64#[derive(Serialize, Deserialize)]
66pub struct HeaderDsPostDsi {
67 pub dsi_flags_2: DsiFlags2,
69 pub sha1_hmac_banner: [u8; 0x14],
71 pub sha1_hmac_unk1: [u8; 0x14],
73 pub sha1_hmac_unk2: [u8; 0x14],
75 pub rsa_sha1: Box<[u8]>,
77}
78
79#[derive(Snafu, Debug)]
81pub enum HeaderBuildError {
82 #[snafu(transparent)]
84 AsciiArray {
85 source: AsciiArrayError,
87 },
88}
89
90impl Header {
91 pub fn load_raw(header: &raw::Header) -> Self {
93 let version = header.version();
94 Self {
95 original: HeaderOriginal {
96 title: header.title.to_string(),
97 gamecode: header.gamecode,
98 makercode: header.makercode,
99 unitcode: header.unitcode,
100 seed_select: header.seed_select,
101 ds_flags: header.ds_flags,
102 rom_version: header.rom_version,
103 autostart: header.autostart,
104 normal_cmd_setting: header.normal_cmd_setting,
105 key1_cmd_setting: header.key1_cmd_setting,
106 secure_area_delay: header.secure_area_delay,
107 rom_nand_end: header.rom_nand_end,
108 rw_nand_end: header.rw_nand_end,
109 has_arm9_build_info_offset: header.arm9_build_info_offset != 0,
110 },
111 ds_post_dsi: (version >= HeaderVersion::DsPostDsi).then_some(HeaderDsPostDsi {
112 dsi_flags_2: header.dsi_flags_2,
113 sha1_hmac_banner: header.sha1_hmac_banner,
114 sha1_hmac_unk1: header.sha1_hmac_unk1,
115 sha1_hmac_unk2: header.sha1_hmac_unk2,
116 rsa_sha1: Box::new(header.rsa_sha1),
117 }),
118 }
119 }
120
121 pub fn build(&self, context: &BuildContext, rom: &Rom) -> Result<raw::Header, HeaderBuildError> {
131 let logo = rom.header_logo().compress();
132 let arm9 = rom.arm9();
133 let arm7 = rom.arm7();
134 let arm9_offset = context.arm9_offset.expect("ARM9 offset must be known");
135 let arm7_offset = context.arm7_offset.expect("ARM7 offset must be known");
136 let mut header = raw::Header {
137 title: AsciiArray::from_str(&self.original.title)?,
138 gamecode: self.original.gamecode,
139 makercode: self.original.makercode,
140 unitcode: self.original.unitcode,
141 seed_select: self.original.seed_select,
142 capacity: Capacity::from_size(context.rom_size.expect("ROM size must be known")),
143 reserved0: [0; 7],
144 dsi_flags: DsiFlags::new(),
145 ds_flags: self.original.ds_flags,
146 rom_version: self.original.rom_version,
147 autostart: self.original.autostart,
148 arm9: ProgramOffset {
149 offset: arm9_offset,
150 entry: arm9.entry_function(),
151 base_addr: arm9.base_address(),
152 size: arm9.full_data().len() as u32,
153 },
154 arm7: ProgramOffset {
155 offset: arm7_offset,
156 entry: arm7.entry_function(),
157 base_addr: arm7.base_address(),
158 size: arm7.full_data().len() as u32,
159 },
160 file_names: context.fnt_offset.expect("FNT offset must be known"),
161 file_allocs: context.fat_offset.expect("FAT offset must be known"),
162 arm9_overlays: context.arm9_ovt_offset.unwrap_or_default(),
163 arm7_overlays: context.arm7_ovt_offset.unwrap_or_default(),
164 normal_cmd_setting: self.original.normal_cmd_setting,
165 key1_cmd_setting: self.original.key1_cmd_setting,
166 banner_offset: context.banner_offset.map(|b| b.offset).expect("Banner offset must be known"),
167 secure_area_crc: if let Some(key) = context.blowfish_key {
168 arm9.secure_area_crc(key, self.original.gamecode.to_le_u32())
169 } else {
170 0
171 },
172 secure_area_delay: self.original.secure_area_delay,
173 arm9_autoload_callback: context.arm9_autoload_callback.expect("ARM9 autoload callback must be known"),
174 arm7_autoload_callback: context.arm7_autoload_callback.expect("ARM7 autoload callback must be known"),
175 secure_area_disable: 0,
176 rom_size_ds: context.rom_size.expect("ROM size must be known"),
177 header_size: size_of::<raw::Header>() as u32,
178 arm9_build_info_offset: if self.original.has_arm9_build_info_offset {
179 context.arm9_build_info_offset.map(|offset| offset + arm9_offset).unwrap_or(0)
180 } else {
181 0
182 },
183 arm7_build_info_offset: context.arm7_build_info_offset.map(|offset| offset + arm7_offset).unwrap_or(0),
184 ds_rom_region_end: 0,
185 dsi_rom_region_end: 0,
186 rom_nand_end: self.original.rom_nand_end,
187 rw_nand_end: self.original.rw_nand_end,
188 reserved1: [0; 0x18],
189 reserved2: [0; 0x10],
190 logo,
191 logo_crc: CRC_16_MODBUS.checksum(&logo),
192 header_crc: 0, debug_rom_offset: 0,
194 debug_size: 0,
195 debug_ram_addr: 0,
196 reserved3: [0; 0x4],
197 reserved4: [0; 0x10],
198 memory_banks_wram: [0; 5],
200 memory_banks_arm9: [0; 3],
201 memory_banks_arm7: [0; 3],
202 memory_bank_9: 0,
203 region_flags: RegionFlags::new(),
204 access_control: AccessControl::new(),
205 arm7_scfg_ext7_setting: 0,
206 dsi_flags_2: DsiFlags2::new(),
207 arm9i: ProgramOffset::default(),
208 arm7i: ProgramOffset::default(),
209 digest_ds_area: TableOffset::default(),
210 digest_dsi_area: TableOffset::default(),
211 digest_sector_hashtable: TableOffset::default(),
212 digest_block_hashtable: TableOffset::default(),
213 digest_sector_size: 0,
214 digest_sector_count: 0,
215 banner_size: 0,
216 sd_shared2_0000_size: 0,
217 sd_shared2_0001_size: 0,
218 eula_version: 0,
219 use_ratings: false,
220 rom_size_dsi: 0,
221 sd_shared2_0002_size: 0,
222 sd_shared2_0003_size: 0,
223 sd_shared2_0004_size: 0,
224 sd_shared2_0005_size: 0,
225 arm9i_build_info_offset: 0,
226 arm7i_build_info_offset: 0,
227 modcrypt_area_1: TableOffset::default(),
228 modcrypt_area_2: TableOffset::default(),
229 gamecode_rev: AsciiArray([0; 4]),
230 file_type: 0,
231 sd_public_sav_size: 0,
232 sd_private_sav_size: 0,
233 reserved5: [0; 0xb0],
234 age_ratings: [0; 0x10],
235 sha1_hmac_arm9_with_secure_area: [0; 0x14],
236 sha1_hmac_arm7: [0; 0x14],
237 sha1_hmac_digest: [0; 0x14],
238 sha1_hmac_banner: [0; 0x14],
239 sha1_hmac_arm9i: [0; 0x14],
240 sha1_hmac_arm7i: [0; 0x14],
241 sha1_hmac_unk1: [0; 0x14],
242 sha1_hmac_unk2: [0; 0x14],
243 sha1_hmac_arm9: [0; 0x14],
244 reserved6: [0; 0xa4c],
245 debug_args: [0; 0x180],
246 rsa_sha1: [0; 0x80],
247 reserved7: [0; 0x3000],
248 };
249
250 if let Some(ds_post_dsi) = &self.ds_post_dsi {
251 header.dsi_flags_2 = ds_post_dsi.dsi_flags_2;
252 header.sha1_hmac_banner = ds_post_dsi.sha1_hmac_banner;
253 header.sha1_hmac_unk1 = ds_post_dsi.sha1_hmac_unk1;
254 header.sha1_hmac_unk2 = ds_post_dsi.sha1_hmac_unk2;
255 header.rsa_sha1.copy_from_slice(&ds_post_dsi.rsa_sha1);
256 }
257
258 header.header_crc = CRC_16_MODBUS.checksum(&bytemuck::bytes_of(&header)[0..offset_of!(raw::Header, header_crc)]);
259 Ok(header)
260 }
261
262 pub fn version(&self) -> HeaderVersion {
264 if self.ds_post_dsi.is_some() {
265 HeaderVersion::DsPostDsi
266 } else {
267 HeaderVersion::Original
268 }
269 }
270}