Skip to main content

ds_rom/rom/raw/
rom.rs

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
17/// A raw DS ROM, see the plain struct [here](super::super::Rom).
18pub struct Rom<'a> {
19    data: Cow<'a, [u8]>,
20}
21
22/// Errors related to [`Rom::arm9`].
23#[derive(Debug, Snafu)]
24pub enum RawArm9Error {
25    /// See [`RawHeaderError`].
26    #[snafu(transparent)]
27    RawHeader {
28        /// Source error.
29        source: RawHeaderError,
30    },
31    /// See [`Arm9FooterError`].
32    #[snafu(transparent)]
33    Arm9Footer {
34        /// Source error.
35        source: Arm9FooterError,
36    },
37    /// See [`RawBuildInfoError`].
38    #[snafu(transparent)]
39    RawBuildInfo {
40        /// Source error.
41        source: RawBuildInfoError,
42    },
43}
44
45/// Errors related to [`Rom::alignments`].
46#[derive(Debug, Snafu)]
47pub enum RomAlignmentsError {
48    /// See [`RawHeaderError`].
49    #[snafu(transparent)]
50    RawHeader {
51        /// Source error.
52        source: RawHeaderError,
53    },
54    /// See [`RawFatError`].
55    #[snafu(transparent)]
56    RawFat {
57        /// Source error.
58        source: RawFatError,
59    },
60    /// See [`RawOverlayError`].
61    #[snafu(transparent)]
62    RawOverlay {
63        /// Source error.
64        source: RawOverlayError,
65    },
66    /// See [`RawFntError`].
67    #[snafu(transparent)]
68    RawBanner {
69        /// Source error.
70        source: RawBannerError,
71    },
72}
73
74impl<'a> Rom<'a> {
75    /// Creates a new ROM from raw data.
76    pub fn new<T: Into<Cow<'a, [u8]>>>(data: T) -> Self {
77        Self { data: data.into() }
78    }
79
80    /// Loads from a ROM file.
81    ///
82    /// # Errors
83    ///
84    /// This function will return an error if an I/O operation fails.
85    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    /// Returns the header of this [`Rom`].
95    ///
96    /// # Errors
97    ///
98    /// See [`Header::borrow_from_slice`].
99    pub fn header(&self) -> Result<&Header, RawHeaderError> {
100        Header::borrow_from_slice(self.data.as_ref())
101    }
102
103    /// Returns the ARM9 program of this [`Rom`].
104    ///
105    /// # Errors
106    ///
107    /// See [`Self::header`].
108    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            // `arm9_build_info_offset` is not an absolute ROM offset in DSi titles
121            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    /// Returns a reference to the ARM9 footer of this [`Rom`].
134    ///
135    /// # Errors
136    ///
137    /// See [`Self::header`] and [`Arm9Footer::borrow_from_slice`].
138    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    /// Returns a mutable reference to the ARM9 footer of this [`Rom`].
147    ///
148    /// # Errors
149    ///
150    /// See [`Self::header`] and [`Arm9Footer::borrow_from_slice_mut`].
151    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    /// Returns the ARM9 overlays of this [`Rom`].
160    ///
161    /// # Errors
162    ///
163    /// See [`Self::header`] and [`Overlay::borrow_from_slice`].
164    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    /// Returns the ARM9 overlay table of this [`Rom`].
177    ///
178    /// # Errors
179    ///
180    /// See [`Self::arm9`] and [`Self::arm9_overlay_table_with`].
181    pub fn arm9_overlay_table(&self) -> Result<OverlayTable<'_>, RawOverlayError> {
182        let arm9 = self.arm9()?;
183        self.arm9_overlay_table_with(&arm9)
184    }
185
186    /// Returns the ARM9 overlay table of this [`Rom`], using the table signature from the provided ARM9 program.
187    ///
188    /// # Errors
189    ///
190    /// See [`Self::arm9_overlays`] and [`Arm9::overlay_table_signature`].
191    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    /// Returns the number of ARM9 overlays in this [`Rom`].
198    ///
199    /// # Errors
200    ///
201    /// See [`Self::header`].
202    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    /// Returns the ARM7 program of this [`Rom`].
210    ///
211    /// # Errors
212    ///
213    /// See [`Self::header`].
214    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    /// Returns the ARM7 overlay table of this [`Rom`].
232    ///
233    /// # Errors
234    ///
235    /// See [`Self::header`] and [`Overlay::borrow_from_slice`].
236    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    /// Returns the ARM7 overlay table of this [`Rom`].
249    ///
250    /// # Errors
251    ///
252    /// See [`Self::arm7_overlays`].
253    pub fn arm7_overlay_table(&self) -> Result<OverlayTable<'_>, RawOverlayError> {
254        let overlays = self.arm7_overlays()?;
255        Ok(OverlayTable::new(overlays, None))
256    }
257
258    /// Returns the number of ARM7 overlays in this [`Rom`].
259    ///
260    /// # Errors
261    ///
262    /// See [`Self::header`].
263    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    /// Returns the FNT of this [`Rom`].
271    ///
272    /// # Errors
273    ///
274    /// See [`Self::header`] and [`Fnt::borrow_from_slice`].
275    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    /// Returns the FAT of this [`Rom`].
284    ///
285    /// # Errors
286    ///
287    /// See [`Self::header`] and [`FileAlloc::borrow_from_slice`].
288    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    /// Returns the banner of this [`Rom`].
298    ///
299    /// # Errors
300    ///
301    /// See [`Self::header`] and [`Banner::borrow_from_slice`].
302    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    /// Returns the multiboot signature of this [`Rom`].
310    ///
311    /// # Errors
312    ///
313    /// See [`Self::header`] and [`MultibootSignature::borrow_from_slice`].
314    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), // signature not found
321            Err(RawMultibootSignatureError::Misaligned { .. }) => Ok(None),   // not aligned by 4
322            Err(e) => Err(e),
323        }
324    }
325
326    /// Returns the padding value in the file image block of this [`Rom`].
327    ///
328    /// # Errors
329    ///
330    /// See [`Self::fat`], [`Self::arm9_overlays`] and [`Self::arm7_overlays`].
331    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        // Get sorted list of adjacent files that are in the file image block (i.e. not overlays)
339        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        // Find a gap between two adjacent files, and return the padding byte between them
348        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    /// Returns the section padding value of this [`Rom`].
355    ///
356    /// # Errors
357    ///
358    /// See [`Self::header`], [`Self::banner`], [`Self::fat`], [`Self::arm9_overlays`] and [`Self::arm7_overlays`].
359    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        // Get sorted list of adjacent sections in the ROM
367        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        // Find a gap between two adjacent sections, and return the padding byte between them
388        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    /// Returns a reference to the data of this [`Rom`].
396    pub fn data(&self) -> &[u8] {
397        &self.data
398    }
399
400    /// Saves this ROM to a new file.
401    ///
402    /// # Errors
403    ///
404    /// This function will return an error if an I/O operation fails.
405    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), FileError> {
406        write_file(path, self.data())
407    }
408
409    /// Returns the alignment of ROM sections.
410    ///
411    /// # Errors
412    ///
413    /// See [`Self::header`], [`Self::fat`], and [`Self::arm9_overlay_table`].
414    pub fn alignments(&self) -> Result<RomConfigAlignment, RomAlignmentsError> {
415        // Collect all overlay files into a set.
416        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        // Get the alignment of the current section, by looking at the address of the next section.
423        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}