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::{Arm7, Arm7Offsets, Arm9, Arm9Offsets, RomConfigAlignment},
12};
13
14pub struct Rom<'a> {
16 data: Cow<'a, [u8]>,
17}
18
19#[derive(Debug, Snafu)]
21pub enum RawArm9Error {
22 #[snafu(transparent)]
24 RawHeader {
25 source: RawHeaderError,
27 },
28 #[snafu(transparent)]
30 Arm9Footer {
31 source: Arm9FooterError,
33 },
34 #[snafu(transparent)]
36 RawBuildInfo {
37 source: RawBuildInfoError,
39 },
40}
41
42#[derive(Debug, Snafu)]
44pub enum RomAlignmentsError {
45 #[snafu(transparent)]
47 RawHeader {
48 source: RawHeaderError,
50 },
51 #[snafu(transparent)]
53 RawFat {
54 source: RawFatError,
56 },
57 #[snafu(transparent)]
59 RawOverlay {
60 source: RawOverlayError,
62 },
63}
64
65impl<'a> Rom<'a> {
66 pub fn new<T: Into<Cow<'a, [u8]>>>(data: T) -> Self {
68 Self { data: data.into() }
69 }
70
71 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, FileError> {
77 let mut file = open_file(path)?;
78 let size = file.metadata()?.len();
79 let mut buf = vec![0; size as usize];
80 file.read_exact(&mut buf)?;
81 let data: Cow<[u8]> = buf.into();
82 Ok(Self::new(data))
83 }
84
85 pub fn header(&self) -> Result<&Header, RawHeaderError> {
91 Header::borrow_from_slice(self.data.as_ref())
92 }
93
94 pub fn arm9(&self) -> Result<Arm9, RawArm9Error> {
100 let header = self.header()?;
101 let start = header.arm9.offset as usize;
102 let end = start + header.arm9.size as usize;
103 let data = &self.data[start..end];
104
105 let footer = self.arm9_footer()?;
106 let build_info_offset = if header.arm9_build_info_offset == 0 {
107 footer.build_info_offset
108 } else if header.arm9_build_info_offset > header.arm9.offset {
109 header.arm9_build_info_offset - header.arm9.offset
110 } else {
111 header.arm9_build_info_offset
113 };
114
115 Ok(Arm9::new(
116 Cow::Borrowed(data),
117 Arm9Offsets {
118 base_address: header.arm9.base_addr,
119 entry_function: header.arm9.entry,
120 build_info: build_info_offset,
121 autoload_callback: header.arm9_autoload_callback,
122 overlay_signatures: footer.overlay_signatures_offset,
123 },
124 )?)
125 }
126
127 pub fn arm9_footer(&self) -> Result<&Arm9Footer, Arm9FooterError> {
133 let header = self.header()?;
134 let start = (header.arm9.offset + header.arm9.size) as usize;
135 let end = start + size_of::<Arm9Footer>();
136 let data = &self.data[start..end];
137 Arm9Footer::borrow_from_slice(data)
138 }
139
140 pub fn arm9_footer_mut(&mut self) -> Result<&mut Arm9Footer, Arm9FooterError> {
146 let header = self.header()?;
147 let start = (header.arm9.offset + header.arm9.size) as usize;
148 let end = start + size_of::<Arm9Footer>();
149 let data = &mut self.data.to_mut()[start..end];
150 Arm9Footer::borrow_from_slice_mut(data)
151 }
152
153 pub fn arm9_overlays(&self) -> Result<&[Overlay], RawOverlayError> {
159 let header = self.header()?;
160 let start = header.arm9_overlays.offset as usize;
161 let end = start + header.arm9_overlays.size as usize;
162 if start == 0 && end == 0 {
163 Ok(&[])
164 } else {
165 let data = &self.data[start..end];
166 Ok(Overlay::borrow_from_slice(data)?)
167 }
168 }
169
170 pub fn arm9_overlay_table(&self) -> Result<OverlayTable, RawOverlayError> {
176 let arm9 = self.arm9()?;
177 self.arm9_overlay_table_with(&arm9)
178 }
179
180 pub fn arm9_overlay_table_with(&self, arm9: &Arm9) -> Result<OverlayTable, RawOverlayError> {
186 let overlays = self.arm9_overlays()?;
187 let signature = arm9.overlay_table_signature()?.cloned();
188 Ok(OverlayTable::new(overlays, signature))
189 }
190
191 pub fn num_arm9_overlays(&self) -> Result<usize, RawHeaderError> {
197 let header = self.header()?;
198 let start = header.arm9_overlays.offset as usize;
199 let end = start + header.arm9_overlays.size as usize;
200 Ok((end - start) / size_of::<Overlay>())
201 }
202
203 pub fn arm7(&self) -> Result<Arm7, RawHeaderError> {
209 let header = self.header()?;
210 let start = header.arm7.offset as usize;
211 let end = start + header.arm7.size as usize;
212 let data = &self.data[start..end];
213
214 let build_info_offset =
215 if header.arm7_build_info_offset == 0 { 0 } else { header.arm7_build_info_offset - header.arm7.offset };
216
217 Ok(Arm7::new(
218 Cow::Borrowed(data),
219 Arm7Offsets {
220 base_address: header.arm7.base_addr,
221 entry_function: header.arm7.entry,
222 build_info: build_info_offset,
223 autoload_callback: header.arm7_autoload_callback,
224 },
225 ))
226 }
227
228 pub fn arm7_overlays(&self) -> Result<&[Overlay], RawOverlayError> {
234 let header = self.header()?;
235 let start = header.arm7_overlays.offset as usize;
236 let end = start + header.arm7_overlays.size as usize;
237 if start == 0 && end == 0 {
238 Ok(&[])
239 } else {
240 let data = &self.data[start..end];
241 Ok(Overlay::borrow_from_slice(data)?)
242 }
243 }
244
245 pub fn arm7_overlay_table(&self) -> Result<OverlayTable, RawOverlayError> {
251 let overlays = self.arm7_overlays()?;
252 Ok(OverlayTable::new(overlays, None))
253 }
254
255 pub fn num_arm7_overlays(&self) -> Result<usize, RawHeaderError> {
261 let header = self.header()?;
262 let start = header.arm7_overlays.offset as usize;
263 let end = start + header.arm7_overlays.size as usize;
264 Ok((end - start) / size_of::<Overlay>())
265 }
266
267 pub fn fnt(&self) -> Result<Fnt, RawFntError> {
273 let header = self.header()?;
274 let start = header.file_names.offset as usize;
275 let end = start + header.file_names.size as usize;
276 let data = &self.data[start..end];
277 Fnt::borrow_from_slice(data)
278 }
279
280 pub fn fat(&self) -> Result<&[FileAlloc], RawFatError> {
286 let header = self.header()?;
287 let start = header.file_allocs.offset as usize;
288 let end = start + header.file_allocs.size as usize;
289 let data = &self.data[start..end];
290 let allocs = FileAlloc::borrow_from_slice(data)?;
291 Ok(allocs)
292 }
293
294 pub fn banner(&self) -> Result<Banner, RawBannerError> {
300 let header = self.header()?;
301 let start = header.banner_offset as usize;
302 let data = &self.data[start..];
303 Banner::borrow_from_slice(data)
304 }
305
306 pub fn padding_value(&self) -> Result<u8, RawBannerError> {
312 let header = self.header()?;
313 let banner = self.banner()?;
314
315 let end = header.banner_offset as usize + banner.version().banner_size();
322 Ok(self.data[end])
323 }
324
325 pub fn data(&self) -> &[u8] {
327 &self.data
328 }
329
330 pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), FileError> {
336 write_file(path, self.data())
337 }
338
339 pub fn alignments(&self) -> Result<RomConfigAlignment, RomAlignmentsError> {
345 fn get_overlay_files(overlay_table: &[Overlay]) -> BTreeSet<u32> {
347 overlay_table.iter().map(|overlay| overlay.file_id).collect()
348 }
349
350 const DEFAULT_ALIGNMENT: u32 = 0x4;
351
352 fn get_alignment(next_section: u32) -> u32 {
354 if next_section.trailing_zeros() >= 9 {
355 0x200
356 } else {
357 DEFAULT_ALIGNMENT
358 }
359 }
360
361 let fat = self.fat()?;
362 let arm9_overlays = self.arm9_overlays()?;
363 let arm7_overlays = self.arm7_overlays()?;
364 let arm9_overlay_files = get_overlay_files(arm9_overlays);
365 let arm7_overlay_files = get_overlay_files(arm7_overlays);
366 let header = self.header()?;
367
368 let arm9 = get_alignment(header.arm9.offset);
369 let arm9_overlay_table = get_alignment(header.arm9_overlays.offset);
370 let arm9_overlay = arm9_overlays
371 .iter()
372 .map(|overlay| get_alignment(fat[overlay.file_id as usize].start))
373 .min()
374 .unwrap_or(DEFAULT_ALIGNMENT);
375 let arm7 = get_alignment(header.arm7.offset);
376 let arm7_overlay_table = get_alignment(header.arm7_overlays.offset);
377 let arm7_overlay = arm7_overlays
378 .iter()
379 .map(|overlay| get_alignment(fat[overlay.file_id as usize].start))
380 .min()
381 .unwrap_or(DEFAULT_ALIGNMENT);
382 let file_name_table = get_alignment(header.file_names.offset);
383 let file_allocation_table = get_alignment(header.file_allocs.offset);
384 let banner = get_alignment(header.banner_offset);
385
386 let file_iter = fat
387 .iter()
388 .enumerate()
389 .filter(|(i, _)| !arm9_overlay_files.contains(&(*i as u32)) && !arm7_overlay_files.contains(&(*i as u32)))
390 .map(|(_, file)| file);
391
392 let file_image_block = file_iter.clone().map(|file| file.start).min().map(get_alignment).unwrap_or(DEFAULT_ALIGNMENT);
393 let file = file_iter.clone().map(|file| get_alignment(file.start)).min().unwrap_or(DEFAULT_ALIGNMENT);
394
395 Ok(RomConfigAlignment {
396 arm9,
397 arm9_overlay_table,
398 arm9_overlay,
399 arm7,
400 arm7_overlay_table,
401 arm7_overlay,
402 file_name_table,
403 file_allocation_table,
404 banner,
405 file_image_block,
406 file,
407 })
408 }
409}