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::{Arm7, Arm7Offsets, Arm9, Arm9Offsets, RomConfigAlignment},
12};
13
14/// A raw DS ROM, see the plain struct [here](super::super::Rom).
15pub struct Rom<'a> {
16    data: Cow<'a, [u8]>,
17}
18
19/// Errors related to [`Rom::arm9`].
20#[derive(Debug, Snafu)]
21pub enum RawArm9Error {
22    /// See [`RawHeaderError`].
23    #[snafu(transparent)]
24    RawHeader {
25        /// Source error.
26        source: RawHeaderError,
27    },
28    /// See [`Arm9FooterError`].
29    #[snafu(transparent)]
30    Arm9Footer {
31        /// Source error.
32        source: Arm9FooterError,
33    },
34    /// See [`RawBuildInfoError`].
35    #[snafu(transparent)]
36    RawBuildInfo {
37        /// Source error.
38        source: RawBuildInfoError,
39    },
40}
41
42/// Errors related to [`Rom::alignments`].
43#[derive(Debug, Snafu)]
44pub enum RomAlignmentsError {
45    /// See [`RawHeaderError`].
46    #[snafu(transparent)]
47    RawHeader {
48        /// Source error.
49        source: RawHeaderError,
50    },
51    /// See [`RawFatError`].
52    #[snafu(transparent)]
53    RawFat {
54        /// Source error.
55        source: RawFatError,
56    },
57    /// See [`RawOverlayError`].
58    #[snafu(transparent)]
59    RawOverlay {
60        /// Source error.
61        source: RawOverlayError,
62    },
63}
64
65impl<'a> Rom<'a> {
66    /// Creates a new ROM from raw data.
67    pub fn new<T: Into<Cow<'a, [u8]>>>(data: T) -> Self {
68        Self { data: data.into() }
69    }
70
71    /// Loads from a ROM file.
72    ///
73    /// # Errors
74    ///
75    /// This function will return an error if an I/O operation fails.
76    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    /// Returns the header of this [`Rom`].
86    ///
87    /// # Errors
88    ///
89    /// See [`Header::borrow_from_slice`].
90    pub fn header(&self) -> Result<&Header, RawHeaderError> {
91        Header::borrow_from_slice(self.data.as_ref())
92    }
93
94    /// Returns the ARM9 program of this [`Rom`].
95    ///
96    /// # Errors
97    ///
98    /// See [`Self::header`].
99    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            // `arm9_build_info_offset` is not an absolute ROM offset in DSi titles
112            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    /// Returns a reference to the ARM9 footer of this [`Rom`].
128    ///
129    /// # Errors
130    ///
131    /// See [`Self::header`] and [`Arm9Footer::borrow_from_slice`].
132    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    /// Returns a mutable reference to the ARM9 footer of this [`Rom`].
141    ///
142    /// # Errors
143    ///
144    /// See [`Self::header`] and [`Arm9Footer::borrow_from_slice_mut`].
145    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    /// Returns the ARM9 overlays of this [`Rom`].
154    ///
155    /// # Errors
156    ///
157    /// See [`Self::header`] and [`Overlay::borrow_from_slice`].
158    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    /// Returns the ARM9 overlay table of this [`Rom`].
171    ///
172    /// # Errors
173    ///
174    /// See [`Self::arm9`] and [`Self::arm9_overlay_table_with`].
175    pub fn arm9_overlay_table(&self) -> Result<OverlayTable, RawOverlayError> {
176        let arm9 = self.arm9()?;
177        self.arm9_overlay_table_with(&arm9)
178    }
179
180    /// Returns the ARM9 overlay table of this [`Rom`], using the table signature from the provided ARM9 program.
181    ///
182    /// # Errors
183    ///
184    /// See [`Self::arm9_overlays`] and [`Arm9::overlay_table_signature`].
185    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    /// Returns the number of ARM9 overlays in this [`Rom`].
192    ///
193    /// # Errors
194    ///
195    /// See [`Self::header`].
196    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    /// Returns the ARM7 program of this [`Rom`].
204    ///
205    /// # Errors
206    ///
207    /// See [`Self::header`].
208    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    /// Returns the ARM7 overlay table of this [`Rom`].
229    ///
230    /// # Errors
231    ///
232    /// See [`Self::header`] and [`Overlay::borrow_from_slice`].
233    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    /// Returns the ARM7 overlay table of this [`Rom`].
246    ///
247    /// # Errors
248    ///
249    /// See [`Self::arm7_overlays`].
250    pub fn arm7_overlay_table(&self) -> Result<OverlayTable, RawOverlayError> {
251        let overlays = self.arm7_overlays()?;
252        Ok(OverlayTable::new(overlays, None))
253    }
254
255    /// Returns the number of ARM7 overlays in this [`Rom`].
256    ///
257    /// # Errors
258    ///
259    /// See [`Self::header`].
260    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    /// Returns the FNT of this [`Rom`].
268    ///
269    /// # Errors
270    ///
271    /// See [`Self::header`] and [`Fnt::borrow_from_slice`].
272    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    /// Returns the FAT of this [`Rom`].
281    ///
282    /// # Errors
283    ///
284    /// See [`Self::header`] and [`FileAlloc::borrow_from_slice`].
285    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    /// Returns the banner of this [`Rom`].
295    ///
296    /// # Errors
297    ///
298    /// See [`Self::header`] and [`Banner::borrow_from_slice`].
299    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    /// Returns the padding value between sections of this [`Rom`].
307    ///
308    /// # Errors
309    ///
310    /// See [`Self::header`] and [`Self::banner`].
311    pub fn padding_value(&self) -> Result<u8, RawBannerError> {
312        let header = self.header()?;
313        let banner = self.banner()?;
314
315        // The banner has a known size which is never a multiple of 512,
316        // so it can't coincide with the start of another section.
317        //
318        // Therefore, we can use the first byte after the banner to determine
319        // the padding value.
320
321        let end = header.banner_offset as usize + banner.version().banner_size();
322        Ok(self.data[end])
323    }
324
325    /// Returns a reference to the data of this [`Rom`].
326    pub fn data(&self) -> &[u8] {
327        &self.data
328    }
329
330    /// Saves this ROM to a new file.
331    ///
332    /// # Errors
333    ///
334    /// This function will return an error if an I/O operation fails.
335    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), FileError> {
336        write_file(path, self.data())
337    }
338
339    /// Returns the alignment of ROM sections.
340    ///
341    /// # Errors
342    ///
343    /// See [`Self::header`], [`Self::fat`], and [`Self::arm9_overlay_table`].
344    pub fn alignments(&self) -> Result<RomConfigAlignment, RomAlignmentsError> {
345        // Collect all overlay files into a set.
346        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        // Get the alignment of the current section, by looking at the address of the next section.
353        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}