1use std::{borrow::Cow, collections::BTreeSet, io::Read, mem::size_of, path::Path};
2
3use snafu::Snafu;
4
5use super::{
6 Arm9Footer, Arm9FooterError, Banner, FileAlloc, Fnt, Header, Overlay, OverlayTable, RawBannerError, RawBuildInfoError,
7 RawFatError, RawFntError, RawHeaderError, RawOverlayError,
8};
9use crate::{
10 io::{open_file, write_file, FileError},
11 rom::{
12 raw::{MultibootSignature, RawMultibootSignatureError},
13 Arm7, Arm7Offsets, Arm9, Arm9Offsets, RomConfigAlignment,
14 },
15};
16
17pub struct Rom<'a> {
19 data: Cow<'a, [u8]>,
20}
21
22#[derive(Debug, Snafu)]
24pub enum RawArm9Error {
25 #[snafu(transparent)]
27 RawHeader {
28 source: RawHeaderError,
30 },
31 #[snafu(transparent)]
33 Arm9Footer {
34 source: Arm9FooterError,
36 },
37 #[snafu(transparent)]
39 RawBuildInfo {
40 source: RawBuildInfoError,
42 },
43}
44
45#[derive(Debug, Snafu)]
47pub enum RomAlignmentsError {
48 #[snafu(transparent)]
50 RawHeader {
51 source: RawHeaderError,
53 },
54 #[snafu(transparent)]
56 RawFat {
57 source: RawFatError,
59 },
60 #[snafu(transparent)]
62 RawOverlay {
63 source: RawOverlayError,
65 },
66 #[snafu(transparent)]
68 RawBanner {
69 source: RawBannerError,
71 },
72}
73
74impl<'a> Rom<'a> {
75 pub fn new<T: Into<Cow<'a, [u8]>>>(data: T) -> Self {
77 Self { data: data.into() }
78 }
79
80 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, FileError> {
86 let mut file = open_file(path)?;
87 let size = file.metadata()?.len();
88 let mut buf = vec![0; size as usize];
89 file.read_exact(&mut buf)?;
90 let data: Cow<[u8]> = buf.into();
91 Ok(Self::new(data))
92 }
93
94 pub fn header(&self) -> Result<&Header, RawHeaderError> {
100 Header::borrow_from_slice(self.data.as_ref())
101 }
102
103 pub fn arm9(&self) -> Result<Arm9<'_>, RawArm9Error> {
109 let header = self.header()?;
110 let start = header.arm9.offset as usize;
111 let end = start + header.arm9.size as usize;
112 let data = &self.data[start..end];
113
114 let footer = self.arm9_footer()?;
115 let build_info_offset = if header.arm9_build_info_offset == 0 {
116 footer.build_info_offset
117 } else if header.arm9_build_info_offset > header.arm9.offset {
118 header.arm9_build_info_offset - header.arm9.offset
119 } else {
120 header.arm9_build_info_offset
122 };
123
124 Ok(Arm9::new(Cow::Borrowed(data), Arm9Offsets {
125 base_address: header.arm9.base_addr,
126 entry_function: header.arm9.entry,
127 build_info: build_info_offset,
128 autoload_callback: header.arm9_autoload_callback,
129 overlay_signatures: footer.overlay_signatures_offset,
130 })?)
131 }
132
133 pub fn arm9_footer(&self) -> Result<&Arm9Footer, Arm9FooterError> {
139 let header = self.header()?;
140 let start = (header.arm9.offset + header.arm9.size) as usize;
141 let end = start + size_of::<Arm9Footer>();
142 let data = &self.data[start..end];
143 Arm9Footer::borrow_from_slice(data)
144 }
145
146 pub fn arm9_footer_mut(&mut self) -> Result<&mut Arm9Footer, Arm9FooterError> {
152 let header = self.header()?;
153 let start = (header.arm9.offset + header.arm9.size) as usize;
154 let end = start + size_of::<Arm9Footer>();
155 let data = &mut self.data.to_mut()[start..end];
156 Arm9Footer::borrow_from_slice_mut(data)
157 }
158
159 pub fn arm9_overlays(&self) -> Result<&[Overlay], RawOverlayError> {
165 let header = self.header()?;
166 let start = header.arm9_overlays.offset as usize;
167 let end = start + header.arm9_overlays.size as usize;
168 if start == 0 && end == 0 {
169 Ok(&[])
170 } else {
171 let data = &self.data[start..end];
172 Ok(Overlay::borrow_from_slice(data)?)
173 }
174 }
175
176 pub fn arm9_overlay_table(&self) -> Result<OverlayTable<'_>, RawOverlayError> {
182 let arm9 = self.arm9()?;
183 self.arm9_overlay_table_with(&arm9)
184 }
185
186 pub fn arm9_overlay_table_with(&self, arm9: &Arm9) -> Result<OverlayTable<'_>, RawOverlayError> {
192 let overlays = self.arm9_overlays()?;
193 let signature = arm9.overlay_table_signature()?.cloned();
194 Ok(OverlayTable::new(overlays, signature))
195 }
196
197 pub fn num_arm9_overlays(&self) -> Result<usize, RawHeaderError> {
203 let header = self.header()?;
204 let start = header.arm9_overlays.offset as usize;
205 let end = start + header.arm9_overlays.size as usize;
206 Ok((end - start) / size_of::<Overlay>())
207 }
208
209 pub fn arm7(&self) -> Result<Arm7<'_>, RawHeaderError> {
215 let header = self.header()?;
216 let start = header.arm7.offset as usize;
217 let end = start + header.arm7.size as usize;
218 let data = &self.data[start..end];
219
220 let build_info_offset =
221 if header.arm7_build_info_offset == 0 { 0 } else { header.arm7_build_info_offset - header.arm7.offset };
222
223 Ok(Arm7::new(Cow::Borrowed(data), Arm7Offsets {
224 base_address: header.arm7.base_addr,
225 entry_function: header.arm7.entry,
226 build_info: build_info_offset,
227 autoload_callback: header.arm7_autoload_callback,
228 }))
229 }
230
231 pub fn arm7_overlays(&self) -> Result<&[Overlay], RawOverlayError> {
237 let header = self.header()?;
238 let start = header.arm7_overlays.offset as usize;
239 let end = start + header.arm7_overlays.size as usize;
240 if start == 0 && end == 0 {
241 Ok(&[])
242 } else {
243 let data = &self.data[start..end];
244 Ok(Overlay::borrow_from_slice(data)?)
245 }
246 }
247
248 pub fn arm7_overlay_table(&self) -> Result<OverlayTable<'_>, RawOverlayError> {
254 let overlays = self.arm7_overlays()?;
255 Ok(OverlayTable::new(overlays, None))
256 }
257
258 pub fn num_arm7_overlays(&self) -> Result<usize, RawHeaderError> {
264 let header = self.header()?;
265 let start = header.arm7_overlays.offset as usize;
266 let end = start + header.arm7_overlays.size as usize;
267 Ok((end - start) / size_of::<Overlay>())
268 }
269
270 pub fn fnt(&self) -> Result<Fnt<'_>, RawFntError> {
276 let header = self.header()?;
277 let start = header.file_names.offset as usize;
278 let end = start + header.file_names.size as usize;
279 let data = &self.data[start..end];
280 Fnt::borrow_from_slice(data)
281 }
282
283 pub fn fat(&self) -> Result<&[FileAlloc], RawFatError> {
289 let header = self.header()?;
290 let start = header.file_allocs.offset as usize;
291 let end = start + header.file_allocs.size as usize;
292 let data = &self.data[start..end];
293 let allocs = FileAlloc::borrow_from_slice(data)?;
294 Ok(allocs)
295 }
296
297 pub fn banner(&self) -> Result<Banner<'_>, RawBannerError> {
303 let header = self.header()?;
304 let start = header.banner_offset as usize;
305 let data = &self.data[start..];
306 Banner::borrow_from_slice(data)
307 }
308
309 pub fn multiboot_signature(&self) -> Result<Option<&MultibootSignature>, RawMultibootSignatureError> {
315 let header = self.header()?;
316 let start = header.rom_size_ds as usize;
317 let data = &self.data[start..];
318 match MultibootSignature::borrow_from_slice(data) {
319 Ok(s) => Ok(Some(s)),
320 Err(RawMultibootSignatureError::InvalidMagic { .. }) => Ok(None), Err(RawMultibootSignatureError::Misaligned { .. }) => Ok(None), Err(e) => Err(e),
323 }
324 }
325
326 pub fn file_image_padding_value(&self) -> Result<u8, RomAlignmentsError> {
332 let fat = self.fat()?;
333 let arm9_overlays = self.arm9_overlays()?;
334 let arm7_overlays = self.arm7_overlays()?;
335 let arm9_overlay_files = arm9_overlays.iter().map(|overlay| overlay.file_id).collect::<BTreeSet<u32>>();
336 let arm7_overlay_files = arm7_overlays.iter().map(|overlay| overlay.file_id).collect::<BTreeSet<u32>>();
337
338 let mut files: Vec<&FileAlloc> = fat
340 .iter()
341 .enumerate()
342 .filter(|(i, _)| !arm9_overlay_files.contains(&(*i as u32)) && !arm7_overlay_files.contains(&(*i as u32)))
343 .map(|(_, file)| file)
344 .collect();
345 files.sort_by_key(|file| file.start);
346
347 let Some(gap) = files.windows(2).find(|pair| pair[0].end != pair[1].start) else {
349 return Ok(0xff);
350 };
351 Ok(self.data[gap[0].end as usize])
352 }
353
354 pub fn section_padding_value(&self) -> Result<u8, RomAlignmentsError> {
360 let header = self.header()?;
361 let banner = self.banner()?;
362 let fat = self.fat()?;
363 let arm9_overlays = self.arm9_overlays()?;
364 let arm7_overlays = self.arm7_overlays()?;
365
366 let mut sections = vec![
368 header.arm9.offset..header.arm9.offset + header.arm9.size + size_of::<Arm9Footer>() as u32,
369 header.arm7.offset..header.arm7.offset + header.arm7.size,
370 header.file_names.offset..header.file_names.offset + header.file_names.size,
371 header.file_allocs.offset..header.file_allocs.offset + header.file_allocs.size,
372 header.arm9_overlays.offset..header.arm9_overlays.offset + header.arm9_overlays.size,
373 header.arm7_overlays.offset..header.arm7_overlays.offset + header.arm7_overlays.size,
374 ];
375 sections.push(header.banner_offset..header.banner_offset + banner.version().banner_size() as u32);
376 arm9_overlays.iter().for_each(|overlay| {
377 let file = &fat[overlay.file_id as usize];
378 sections.push(file.start..file.end);
379 });
380 arm7_overlays.iter().for_each(|overlay| {
381 let file = &fat[overlay.file_id as usize];
382 sections.push(file.start..file.end);
383 });
384 sections.retain(|section| section.start != section.end);
385 sections.sort_by_key(|section| section.start);
386
387 let Some(gap) = sections.windows(2).find(|pair| pair[0].end != pair[1].start) else {
389 return Ok(0xff);
390 };
391 log::debug!("Gap between sections: {:#010x} - {:#010x}", gap[0].end, gap[1].start);
392 Ok(self.data[gap[0].end as usize])
393 }
394
395 pub fn data(&self) -> &[u8] {
397 &self.data
398 }
399
400 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), FileError> {
406 write_file(path, self.data())
407 }
408
409 pub fn alignments(&self) -> Result<RomConfigAlignment, RomAlignmentsError> {
415 fn get_overlay_files(overlay_table: &[Overlay]) -> BTreeSet<u32> {
417 overlay_table.iter().map(|overlay| overlay.file_id).collect()
418 }
419
420 const DEFAULT_ALIGNMENT: u32 = 0x4;
421
422 fn get_alignment(next_section: u32) -> u32 {
424 if next_section.trailing_zeros() >= 9 {
425 0x200
426 } else {
427 DEFAULT_ALIGNMENT
428 }
429 }
430
431 let fat = self.fat()?;
432 let arm9_overlays = self.arm9_overlays()?;
433 let arm7_overlays = self.arm7_overlays()?;
434 let arm9_overlay_files = get_overlay_files(arm9_overlays);
435 let arm7_overlay_files = get_overlay_files(arm7_overlays);
436 let header = self.header()?;
437
438 let arm9 = get_alignment(header.arm9.offset);
439 let arm9_overlay_table = get_alignment(header.arm9_overlays.offset);
440 let arm9_overlay = arm9_overlays
441 .iter()
442 .map(|overlay| get_alignment(fat[overlay.file_id as usize].start))
443 .min()
444 .unwrap_or(DEFAULT_ALIGNMENT);
445 let arm7 = get_alignment(header.arm7.offset);
446 let arm7_overlay_table = get_alignment(header.arm7_overlays.offset);
447 let arm7_overlay = arm7_overlays
448 .iter()
449 .map(|overlay| get_alignment(fat[overlay.file_id as usize].start))
450 .min()
451 .unwrap_or(DEFAULT_ALIGNMENT);
452 let file_name_table = get_alignment(header.file_names.offset);
453 let file_allocation_table = get_alignment(header.file_allocs.offset);
454 let banner = get_alignment(header.banner_offset);
455
456 let file_iter = fat
457 .iter()
458 .enumerate()
459 .filter(|(i, _)| !arm9_overlay_files.contains(&(*i as u32)) && !arm7_overlay_files.contains(&(*i as u32)))
460 .map(|(_, file)| file);
461
462 let file_image_block = file_iter.clone().map(|file| file.start).min().map(get_alignment).unwrap_or(DEFAULT_ALIGNMENT);
463 let file = file_iter.clone().map(|file| get_alignment(file.start)).min().unwrap_or(DEFAULT_ALIGNMENT);
464
465 Ok(RomConfigAlignment {
466 arm9,
467 arm9_overlay_table,
468 arm9_overlay,
469 arm7,
470 arm7_overlay_table,
471 arm7_overlay,
472 file_name_table,
473 file_allocation_table,
474 banner,
475 file_image_block,
476 file,
477 })
478 }
479}