ds_rom/rom/
rom.rs

1use std::{
2    io::{self, Cursor, Write},
3    mem::size_of,
4    path::Path,
5};
6
7use serde::{Deserialize, Serialize};
8use snafu::Snafu;
9
10use super::{
11    raw::{
12        self, Arm9Footer, RawArm9Error, RawBannerError, RawBuildInfoError, RawFatError, RawFntError, RawHeaderError,
13        RawOverlayError, TableOffset,
14    },
15    Arm7, Arm9, Arm9AutoloadError, Arm9Error, Arm9Offsets, Autoload, Banner, BannerError, BannerImageError, BuildInfo,
16    FileBuildError, FileParseError, FileSystem, Header, HeaderBuildError, Logo, LogoError, LogoLoadError, LogoSaveError,
17    Overlay, OverlayInfo, RomConfigAutoload,
18};
19use crate::{
20    compress::lz77::Lz77DecompressError,
21    crypto::blowfish::BlowfishKey,
22    io::{create_dir_all, create_file, create_file_and_dirs, open_file, read_file, read_to_string, FileError},
23    rom::{raw::FileAlloc, Arm9WithTcmsOptions, RomConfig},
24};
25
26/// A plain ROM.
27pub struct Rom<'a> {
28    header: Header,
29    header_logo: Logo,
30    arm9: Arm9<'a>,
31    arm9_overlays: Vec<Overlay<'a>>,
32    arm7: Arm7<'a>,
33    arm7_overlays: Vec<Overlay<'a>>,
34    banner: Banner,
35    files: FileSystem<'a>,
36    path_order: Vec<String>,
37    config: RomConfig,
38}
39
40/// Errors related to [`Rom::extract`].
41#[derive(Debug, Snafu)]
42pub enum RomExtractError {
43    /// See [`RawHeaderError`].
44    #[snafu(transparent)]
45    RawHeader {
46        /// Source error.
47        source: RawHeaderError,
48    },
49    /// See [`LogoError`].
50    #[snafu(transparent)]
51    Logo {
52        /// Source error.
53        source: LogoError,
54    },
55    /// See [`RawOverlayError`].
56    #[snafu(transparent)]
57    RawOverlay {
58        /// Source error.
59        source: RawOverlayError,
60    },
61    /// See [`RawFntError`].
62    #[snafu(transparent)]
63    RawFnt {
64        /// Source error.
65        source: RawFntError,
66    },
67    /// See [`RawFatError`].
68    #[snafu(transparent)]
69    RawFat {
70        /// Source error.
71        source: RawFatError,
72    },
73    /// See [`RawBannerError`].
74    #[snafu(transparent)]
75    RawBanner {
76        /// Source error.
77        source: RawBannerError,
78    },
79    /// See [`FileParseError`].
80    #[snafu(transparent)]
81    FileParse {
82        /// Source error.
83        source: FileParseError,
84    },
85    /// See [`RawArm9Error`].
86    #[snafu(transparent)]
87    RawArm9 {
88        /// Source error.
89        source: RawArm9Error,
90    },
91    /// See [`Arm9AutoloadError`]
92    #[snafu(transparent)]
93    Arm9Autoload {
94        /// Source error.
95        source: Arm9AutoloadError,
96    },
97    /// See [`RawBuildInfoError`].
98    #[snafu(transparent)]
99    RawBuildInfo {
100        /// Source error.
101        source: RawBuildInfoError,
102    },
103    /// See [`Arm9Error`].
104    #[snafu(transparent)]
105    Arm9 {
106        /// Source error.
107        source: Arm9Error,
108    },
109}
110
111/// Errors related to [`Rom::build`].
112#[derive(Snafu, Debug)]
113pub enum RomBuildError {
114    /// See [`io::Error`].
115    #[snafu(transparent)]
116    Io {
117        /// Source error.
118        source: io::Error,
119    },
120    /// See [`FileBuildError`].
121    #[snafu(transparent)]
122    FileBuild {
123        /// Source error.
124        source: FileBuildError,
125    },
126    /// See [`BannerError`].
127    #[snafu(transparent)]
128    Banner {
129        /// Source error.
130        source: BannerError,
131    },
132    /// See [`HeaderBuildError`].
133    #[snafu(transparent)]
134    HeaderBuild {
135        /// Source error.
136        source: HeaderBuildError,
137    },
138}
139
140/// Errors related to [`Rom::save`] and [`Rom::load`].
141#[derive(Snafu, Debug)]
142pub enum RomSaveError {
143    /// Occurs when the ROM is encrypted but no Blowfish key was provided.
144    #[snafu(display("blowfish key is required because ARM9 program is encrypted"))]
145    BlowfishKeyNeeded,
146    /// See [`io::Error`].
147    #[snafu(transparent)]
148    Io {
149        /// Source error.
150        source: io::Error,
151    },
152    /// See [`FileError`].
153    #[snafu(transparent)]
154    File {
155        /// Source error.
156        source: FileError,
157    },
158    /// See [`serde_yml::Error`].
159    #[snafu(transparent)]
160    SerdeJson {
161        /// Source error.
162        source: serde_yml::Error,
163    },
164    /// See [`LogoSaveError`].
165    #[snafu(transparent)]
166    LogoSave {
167        /// Source error.
168        source: LogoSaveError,
169    },
170    /// See [`LogoLoadError`].
171    #[snafu(transparent)]
172    LogoLoad {
173        /// Source error.
174        source: LogoLoadError,
175    },
176    /// See [`RawBuildInfoError`].
177    #[snafu(transparent)]
178    RawBuildInfo {
179        /// Source error.
180        source: RawBuildInfoError,
181    },
182    /// See [`Arm9Error`].
183    #[snafu(transparent)]
184    Arm9 {
185        /// Source error.
186        source: Arm9Error,
187    },
188    /// See [`Arm9AutoloadError`].
189    #[snafu(transparent)]
190    Arm9Autoload {
191        /// Source error.
192        source: Arm9AutoloadError,
193    },
194    /// See [`BannerImageError`].
195    #[snafu(transparent)]
196    BannerImage {
197        /// Source error.
198        source: BannerImageError,
199    },
200    /// See [`Lz77DecompressError`].
201    #[snafu(transparent)]
202    Lz77Decompress {
203        /// Source error.
204        source: Lz77DecompressError,
205    },
206}
207
208/// Config file for the ARM9 main module.
209#[derive(Serialize, Deserialize)]
210pub struct Arm9BuildConfig {
211    /// Various offsets within the ARM9 module.
212    #[serde(flatten)]
213    pub offsets: Arm9Offsets,
214    /// Whether this module is encrypted in the ROM.
215    pub encrypted: bool,
216    /// Whether this module is compressed in the ROM.
217    pub compressed: bool,
218    /// Build info for this module.
219    #[serde(flatten)]
220    pub build_info: BuildInfo,
221}
222
223/// Overlay configuration, extending [`OverlayInfo`] with more fields.
224#[derive(Serialize, Deserialize)]
225pub struct OverlayConfig {
226    /// See [`OverlayInfo`].
227    #[serde(flatten)]
228    pub info: OverlayInfo,
229    /// Name of binary file.
230    pub file_name: String,
231}
232
233impl<'a> Rom<'a> {
234    /// Loads a ROM from a path generated by [`Self::save`].
235    ///
236    /// # Errors
237    ///
238    /// This function will return an error if there's a file missing or the file has an invalid format.
239    pub fn load<P: AsRef<Path>>(config_path: P, options: RomLoadOptions) -> Result<Self, RomSaveError> {
240        let config_path = config_path.as_ref();
241        log::info!("Loading ROM from {}", config_path.display());
242
243        let config: RomConfig = serde_yml::from_reader(open_file(config_path)?)?;
244        let path = config_path.parent().unwrap();
245
246        // --------------------- Load header ---------------------
247        let header: Header = serde_yml::from_reader(open_file(path.join(&config.header))?)?;
248        let header_logo = Logo::from_png(path.join(&config.header_logo))?;
249
250        // --------------------- Load ARM9 program ---------------------
251        let arm9_build_config: Arm9BuildConfig = serde_yml::from_reader(open_file(path.join(&config.arm9_config))?)?;
252        let arm9 = read_file(path.join(&config.arm9_bin))?;
253
254        // --------------------- Load autoloads ---------------------
255        let mut autoloads = vec![];
256
257        let itcm = read_file(path.join(&config.itcm.bin))?;
258        let itcm_info = serde_yml::from_reader(open_file(path.join(&config.itcm.config))?)?;
259        let itcm = Autoload::new(itcm, itcm_info);
260        autoloads.push(itcm);
261
262        let dtcm = read_file(path.join(&config.dtcm.bin))?;
263        let dtcm_info = serde_yml::from_reader(open_file(path.join(&config.dtcm.config))?)?;
264        let dtcm = Autoload::new(dtcm, dtcm_info);
265        autoloads.push(dtcm);
266
267        for unknown_autoload in &config.unknown_autoloads {
268            let autoload = read_file(path.join(&unknown_autoload.bin))?;
269            let autoload_info = serde_yml::from_reader(open_file(path.join(&unknown_autoload.config))?)?;
270            let autoload = Autoload::new(autoload, autoload_info);
271            autoloads.push(autoload);
272        }
273
274        // --------------------- Build ARM9 program ---------------------
275        let mut arm9 = Arm9::with_autoloads(arm9, &autoloads, arm9_build_config.offsets, Arm9WithTcmsOptions {
276            originally_compressed: arm9_build_config.compressed,
277            originally_encrypted: arm9_build_config.encrypted,
278        })?;
279        arm9_build_config.build_info.assign_to_raw(arm9.build_info_mut()?);
280        if arm9_build_config.compressed && options.compress {
281            log::info!("Compressing ARM9 program");
282            arm9.compress()?;
283        }
284        if arm9_build_config.encrypted && options.encrypt {
285            let Some(key) = options.key else {
286                return BlowfishKeyNeededSnafu {}.fail();
287            };
288            log::info!("Encrypting ARM9 program");
289            arm9.encrypt(key, header.original.gamecode.to_le_u32())?;
290        }
291
292        // --------------------- Load ARM9 overlays ---------------------
293        let arm9_overlays = if let Some(arm9_overlays_config) = &config.arm9_overlays {
294            Self::load_overlays(&path.join(arm9_overlays_config), "arm9", &options)?
295        } else {
296            vec![]
297        };
298
299        // --------------------- Load ARM7 program ---------------------
300        let arm7 = read_file(path.join(&config.arm7_bin))?;
301        let arm7_config = serde_yml::from_reader(open_file(path.join(&config.arm7_config))?)?;
302        let arm7 = Arm7::new(arm7, arm7_config);
303
304        // --------------------- Load ARM7 overlays ---------------------
305        let arm7_overlays = if let Some(arm7_overlays_config) = &config.arm7_overlays {
306            Self::load_overlays(&path.join(arm7_overlays_config), "arm7", &options)?
307        } else {
308            vec![]
309        };
310
311        // --------------------- Load banner ---------------------
312        let banner_path = path.join(&config.banner);
313        let banner_dir = banner_path.parent().unwrap();
314        let mut banner: Banner = serde_yml::from_reader(open_file(&banner_path)?)?;
315        banner.images.load(banner_dir)?;
316
317        // --------------------- Load files ---------------------
318        let num_overlays = arm9_overlays.len() + arm7_overlays.len();
319        let (files, path_order) = if options.load_files {
320            log::info!("Loading ROM assets");
321            let files = FileSystem::load(path.join(&config.files_dir), num_overlays)?;
322            let path_order =
323                read_to_string(path.join(&config.path_order))?.trim().lines().map(|l| l.to_string()).collect::<Vec<_>>();
324            (files, path_order)
325        } else {
326            (FileSystem::new(num_overlays), vec![])
327        };
328
329        Ok(Self { header, header_logo, arm9, arm9_overlays, arm7, arm7_overlays, banner, files, path_order, config })
330    }
331
332    fn load_overlays(config_path: &Path, processor: &str, options: &RomLoadOptions) -> Result<Vec<Overlay<'a>>, RomSaveError> {
333        let path = config_path.parent().unwrap();
334        let mut overlays = vec![];
335        let overlay_configs: Vec<OverlayConfig> = serde_yml::from_reader(open_file(config_path)?)?;
336        let num_overlays = overlay_configs.len();
337        for mut config in overlay_configs.into_iter() {
338            let data = read_file(path.join(config.file_name))?;
339            let compressed = config.info.compressed;
340            config.info.compressed = false;
341            let mut overlay = Overlay::new(data, config.info, compressed);
342            if compressed && options.compress {
343                log::info!("Compressing {processor} overlay {}/{}", overlay.id(), num_overlays - 1);
344                overlay.compress()?;
345            }
346            overlays.push(overlay);
347        }
348        Ok(overlays)
349    }
350
351    /// Saves this ROM to a path as separate files.
352    ///
353    /// # Errors
354    ///
355    /// This function will return an error if a file could not be created or the a component of the ROM has an invalid format.
356    pub fn save<P: AsRef<Path>>(&self, path: P, key: Option<&BlowfishKey>) -> Result<(), RomSaveError> {
357        let path = path.as_ref();
358        create_dir_all(path)?;
359
360        log::info!("Saving ROM to directory {}", path.display());
361
362        // --------------------- Save config ---------------------
363        serde_yml::to_writer(create_file_and_dirs(path.join("config.yaml"))?, &self.config)?;
364
365        // --------------------- Save header ---------------------
366        serde_yml::to_writer(create_file_and_dirs(path.join(&self.config.header))?, &self.header)?;
367        self.header_logo.save_png(path.join(&self.config.header_logo))?;
368
369        // --------------------- Save ARM9 program ---------------------
370        let arm9_build_config = self.arm9_build_config()?;
371        serde_yml::to_writer(create_file_and_dirs(path.join(&self.config.arm9_config))?, &arm9_build_config)?;
372        let mut plain_arm9 = self.arm9.clone();
373        if plain_arm9.is_encrypted() {
374            let Some(key) = key else {
375                return BlowfishKeyNeededSnafu {}.fail();
376            };
377            log::info!("Decrypting ARM9 program");
378            plain_arm9.decrypt(key, self.header.original.gamecode.to_le_u32())?;
379        }
380        if plain_arm9.is_compressed()? {
381            log::info!("Decompressing ARM9 program");
382            plain_arm9.decompress()?;
383        }
384        create_file_and_dirs(path.join(&self.config.arm9_bin))?.write(plain_arm9.code()?)?;
385
386        // --------------------- Save autoloads ---------------------
387        let mut unknown_autoloads = self.config.unknown_autoloads.iter();
388        for autoload in plain_arm9.autoloads()?.iter() {
389            let (bin_path, config_path) = match autoload.kind() {
390                raw::AutoloadKind::Itcm => (path.join(&self.config.itcm.bin), path.join(&self.config.itcm.config)),
391                raw::AutoloadKind::Dtcm => (path.join(&self.config.dtcm.bin), path.join(&self.config.dtcm.config)),
392                raw::AutoloadKind::Unknown(_) => {
393                    let unknown_autoload = unknown_autoloads.next().expect("no more autoloads in config, was it removed?");
394                    (path.join(&unknown_autoload.bin), path.join(&unknown_autoload.config))
395                }
396            };
397            create_file_and_dirs(bin_path)?.write(autoload.code())?;
398            serde_yml::to_writer(create_file_and_dirs(config_path)?, autoload.info())?;
399        }
400
401        // --------------------- Save ARM9 overlays ---------------------
402        if let Some(arm9_overlays_config) = &self.config.arm9_overlays {
403            Self::save_overlays(&path.join(arm9_overlays_config), &self.arm9_overlays, "arm9")?;
404        }
405
406        // --------------------- Save ARM7 program ---------------------
407        create_file_and_dirs(path.join(&self.config.arm7_bin))?.write(self.arm7.full_data())?;
408        serde_yml::to_writer(create_file_and_dirs(path.join(&self.config.arm7_config))?, self.arm7.offsets())?;
409
410        // --------------------- Save ARM7 overlays ---------------------
411        if let Some(arm7_overlays_config) = &self.config.arm7_overlays {
412            Self::save_overlays(&path.join(arm7_overlays_config), &self.arm7_overlays, "arm7")?;
413        }
414
415        // --------------------- Save banner ---------------------
416        {
417            let banner_path = path.join(&self.config.banner);
418            let banner_dir = banner_path.parent().unwrap();
419            serde_yml::to_writer(create_file_and_dirs(&banner_path)?, &self.banner)?;
420            self.banner.images.save_bitmap_file(banner_dir)?;
421        }
422
423        // --------------------- Save files ---------------------
424        {
425            log::info!("Saving ROM assets");
426            let files_path = path.join(&self.config.files_dir);
427            self.files.traverse_files(["/"], |file, path| {
428                let path = files_path.join(path);
429                // TODO: Rewrite traverse_files as an iterator so these errors can be returned
430                create_dir_all(&path).expect("failed to create file directory");
431                create_file(&path.join(file.name()))
432                    .expect("failed to create file")
433                    .write(file.contents())
434                    .expect("failed to write file");
435            });
436        }
437        let mut path_order_file = create_file_and_dirs(path.join(&self.config.path_order))?;
438        for path in &self.path_order {
439            path_order_file.write(path.as_bytes())?;
440            path_order_file.write("\n".as_bytes())?;
441        }
442
443        Ok(())
444    }
445
446    /// Generates a build config for ARM9, which normally goes into arm9.yaml.
447    pub fn arm9_build_config(&self) -> Result<Arm9BuildConfig, RomSaveError> {
448        Ok(Arm9BuildConfig {
449            offsets: *self.arm9.offsets(),
450            encrypted: self.arm9.is_encrypted(),
451            compressed: self.arm9.is_compressed()?,
452            build_info: self.arm9.build_info()?.clone().into(),
453        })
454    }
455
456    fn save_overlays(config_path: &Path, overlays: &[Overlay], processor: &str) -> Result<(), RomSaveError> {
457        if !overlays.is_empty() {
458            let overlays_path = config_path.parent().unwrap();
459            create_dir_all(overlays_path)?;
460
461            let mut configs = vec![];
462            for overlay in overlays {
463                let name = format!("ov{:03}", overlay.id());
464
465                let mut plain_overlay = overlay.clone();
466                configs.push(OverlayConfig { info: plain_overlay.info().clone(), file_name: format!("{name}.bin") });
467
468                if plain_overlay.is_compressed() {
469                    log::info!("Decompressing {processor} overlay {}/{}", overlay.id(), overlays.len() - 1);
470                    plain_overlay.decompress()?;
471                }
472                create_file(overlays_path.join(format!("{name}.bin")))?.write(plain_overlay.code())?;
473            }
474            serde_yml::to_writer(create_file(config_path)?, &configs)?;
475        }
476        Ok(())
477    }
478
479    /// Extracts from a raw ROM.
480    ///
481    /// # Errors
482    ///
483    /// This function will return an error if a component is missing from the raw ROM.
484    pub fn extract(rom: &'a raw::Rom) -> Result<Self, RomExtractError> {
485        let header = rom.header()?;
486        let fnt = rom.fnt()?;
487        let fat = rom.fat()?;
488        let banner = rom.banner()?;
489        let file_root = FileSystem::parse(&fnt, fat, rom)?;
490        let path_order = file_root.compute_path_order();
491
492        let arm9_overlays =
493            rom.arm9_overlay_table()?.iter().map(|ov| Overlay::parse(ov, fat, rom)).collect::<Result<Vec<_>, _>>()?;
494        let arm7_overlays =
495            rom.arm7_overlay_table()?.iter().map(|ov| Overlay::parse(ov, fat, rom)).collect::<Result<Vec<_>, _>>()?;
496
497        let arm9 = rom.arm9()?;
498
499        let num_unknown_autoloads = if arm9.is_compressed()? {
500            let mut decompressed_arm9 = arm9.clone();
501            decompressed_arm9.decompress()?;
502            decompressed_arm9.num_unknown_autoloads()?
503        } else {
504            arm9.num_unknown_autoloads()?
505        };
506        let unknown_autoloads = (0..num_unknown_autoloads)
507            .map(|index| RomConfigAutoload {
508                bin: format!("arm9/unk_autoload_{index}.bin").into(),
509                config: format!("arm9/unk_autoload_{index}.yaml").into(),
510            })
511            .collect();
512
513        let config = RomConfig {
514            padding_value: rom.padding_value()?,
515            header: "header.yaml".into(),
516            header_logo: "header_logo.png".into(),
517            arm9_bin: "arm9/arm9.bin".into(),
518            arm9_config: "arm9/arm9.yaml".into(),
519            arm7_bin: "arm7/arm7.bin".into(),
520            arm7_config: "arm7/arm7.yaml".into(),
521            itcm: RomConfigAutoload { bin: "arm9/itcm.bin".into(), config: "arm9/itcm.yaml".into() },
522            unknown_autoloads,
523            dtcm: RomConfigAutoload { bin: "arm9/dtcm.bin".into(), config: "arm9/dtcm.yaml".into() },
524            arm9_overlays: if arm9_overlays.is_empty() { None } else { Some("arm9_overlays/overlays.yaml".into()) },
525            arm7_overlays: if arm7_overlays.is_empty() { None } else { Some("arm7_overlays/overlays.yaml".into()) },
526            banner: "banner/banner.yaml".into(),
527            files_dir: "files/".into(),
528            path_order: "path_order.txt".into(),
529        };
530
531        Ok(Self {
532            header: Header::load_raw(&header),
533            header_logo: Logo::decompress(&header.logo)?,
534            arm9,
535            arm9_overlays,
536            arm7: rom.arm7()?,
537            arm7_overlays,
538            banner: Banner::load_raw(&banner),
539            files: file_root,
540            path_order,
541            config,
542        })
543    }
544
545    /// Builds a raw ROM.
546    ///
547    /// # Errors
548    ///
549    /// This function will return an error if an I/O operation fails or a component fails to build.
550    pub fn build(mut self, key: Option<&BlowfishKey>) -> Result<raw::Rom<'a>, RomBuildError> {
551        let mut context = BuildContext::default();
552        context.blowfish_key = key;
553
554        let mut cursor = Cursor::new(Vec::with_capacity(128 * 1024)); // smallest possible ROM
555
556        // --------------------- Write header placeholder ---------------------
557        context.header_offset = Some(cursor.position() as u32);
558        cursor.write(&[0u8; size_of::<raw::Header>()])?;
559        self.align(&mut cursor)?;
560
561        // --------------------- Write ARM9 program ---------------------
562        context.arm9_offset = Some(cursor.position() as u32);
563        context.arm9_autoload_callback = Some(self.arm9.autoload_callback());
564        context.arm9_build_info_offset = Some(self.arm9.build_info_offset());
565        cursor.write(self.arm9.full_data())?;
566        let footer = Arm9Footer::new(self.arm9.build_info_offset());
567        cursor.write(bytemuck::bytes_of(&footer))?;
568        self.align(&mut cursor)?;
569
570        let max_file_id = self.files.max_file_id();
571        let mut file_allocs = vec![FileAlloc::default(); max_file_id as usize + 1];
572
573        if !self.arm9_overlays.is_empty() {
574            // --------------------- Write ARM9 overlay table ---------------------
575            context.arm9_ovt_offset = Some(TableOffset {
576                offset: cursor.position() as u32,
577                size: (self.arm9_overlays.len() * size_of::<raw::Overlay>()) as u32,
578            });
579            for overlay in &self.arm9_overlays {
580                let raw = overlay.build();
581                cursor.write(bytemuck::bytes_of(&raw))?;
582            }
583            self.align(&mut cursor)?;
584
585            // --------------------- Write ARM9 overlays ---------------------
586            for overlay in &self.arm9_overlays {
587                let start = cursor.position() as u32;
588                let end = start + overlay.full_data().len() as u32;
589                file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
590
591                cursor.write(overlay.full_data())?;
592                self.align(&mut cursor)?;
593            }
594        }
595
596        // --------------------- Write ARM7 program ---------------------
597        context.arm7_offset = Some(cursor.position() as u32);
598        context.arm7_autoload_callback = Some(self.arm7.autoload_callback());
599        context.arm7_build_info_offset = None;
600        cursor.write(self.arm7.full_data())?;
601        self.align(&mut cursor)?;
602
603        if !self.arm7_overlays.is_empty() {
604            // --------------------- Write ARM7 overlay table ---------------------
605            context.arm7_ovt_offset = Some(TableOffset {
606                offset: cursor.position() as u32,
607                size: (self.arm7_overlays.len() * size_of::<raw::Overlay>()) as u32,
608            });
609            for overlay in &self.arm7_overlays {
610                let raw = overlay.build();
611                cursor.write(bytemuck::bytes_of(&raw))?;
612            }
613            self.align(&mut cursor)?;
614
615            // --------------------- Write ARM7 overlays ---------------------
616            for overlay in &self.arm7_overlays {
617                let start = cursor.position() as u32;
618                let end = start + overlay.full_data().len() as u32;
619                file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
620
621                cursor.write(overlay.full_data())?;
622                self.align(&mut cursor)?;
623            }
624        }
625
626        // --------------------- Write file name table (FNT) ---------------------
627        self.files.sort_for_fnt();
628        let fnt = self.files.build_fnt()?.build()?;
629        context.fnt_offset = Some(TableOffset { offset: cursor.position() as u32, size: fnt.len() as u32 });
630        cursor.write(&fnt)?;
631        self.align(&mut cursor)?;
632
633        // --------------------- Write file allocation table (FAT) placeholder ---------------------
634        context.fat_offset =
635            Some(TableOffset { offset: cursor.position() as u32, size: (file_allocs.len() * size_of::<FileAlloc>()) as u32 });
636        cursor.write(bytemuck::cast_slice(&file_allocs))?;
637        self.align(&mut cursor)?;
638
639        // --------------------- Write banner ---------------------
640        let banner = self.banner.build()?;
641        context.banner_offset = Some(TableOffset { offset: cursor.position() as u32, size: banner.full_data().len() as u32 });
642        cursor.write(banner.full_data())?;
643        self.align(&mut cursor)?;
644
645        // --------------------- Write files ---------------------
646        self.files.sort_for_rom();
647        self.files.traverse_files(self.path_order.iter().map(|s| s.as_str()), |file, _| {
648            // TODO: Rewrite traverse_files as an iterator so these errors can be returned
649            self.align(&mut cursor).expect("failed to align before file");
650
651            let contents = file.contents();
652            let start = cursor.position() as u32;
653            let end = start + contents.len() as u32;
654            file_allocs[file.id() as usize] = FileAlloc { start, end };
655
656            cursor.write(contents).expect("failed to write file contents");
657        });
658
659        // --------------------- Write padding ---------------------
660        context.rom_size = Some(cursor.position() as u32);
661        while !cursor.position().is_power_of_two() && cursor.position() >= 128 * 1024 {
662            cursor.write(&[self.config.padding_value])?;
663        }
664
665        // --------------------- Update FAT ---------------------
666        cursor.set_position(context.fat_offset.unwrap().offset as u64);
667        cursor.write(&bytemuck::cast_slice(&file_allocs))?;
668
669        // --------------------- Update header ---------------------
670        cursor.set_position(context.header_offset.unwrap() as u64);
671        let header = self.header.build(&context, &self)?;
672        cursor.write(bytemuck::bytes_of(&header))?;
673
674        Ok(raw::Rom::new(cursor.into_inner()))
675    }
676
677    fn align(&self, cursor: &mut Cursor<Vec<u8>>) -> Result<(), RomBuildError> {
678        let padding = (!cursor.position() + 1) & 0x1ff;
679        for _ in 0..padding {
680            cursor.write(&[self.config.padding_value])?;
681        }
682        Ok(())
683    }
684
685    /// Returns a reference to the header logo of this [`Rom`].
686    pub fn header_logo(&self) -> &Logo {
687        &self.header_logo
688    }
689
690    /// Returns a reference to the ARM9 program of this [`Rom`].
691    pub fn arm9(&self) -> &Arm9 {
692        &self.arm9
693    }
694
695    /// Returns a reference to the ARM9 overlays of this [`Rom`].
696    pub fn arm9_overlays(&self) -> &[Overlay] {
697        &self.arm9_overlays
698    }
699
700    /// Returns a reference to the ARM7 program of this [`Rom`].
701    pub fn arm7(&self) -> &Arm7 {
702        &self.arm7
703    }
704
705    /// Returns a reference to the ARM7 overlays of this [`Rom`].
706    pub fn arm7_overlays(&self) -> &[Overlay] {
707        &self.arm7_overlays
708    }
709
710    /// Returns a reference to the header of this [`Rom`].
711    pub fn header(&self) -> &Header {
712        &self.header
713    }
714
715    /// Returns the [`RomConfig`] consisting of paths to extracted files.
716    pub fn config(&self) -> &RomConfig {
717        &self.config
718    }
719}
720
721/// Build context, generated during [`Rom::build`] and later passed to [`Header::build`] to fill in the header.
722#[derive(Default)]
723pub struct BuildContext<'a> {
724    /// Header offset.
725    pub header_offset: Option<u32>,
726    /// ARM9 program offset.
727    pub arm9_offset: Option<u32>,
728    /// ARM7 program offset.
729    pub arm7_offset: Option<u32>,
730    /// FNT offset.
731    pub fnt_offset: Option<TableOffset>,
732    /// FAT offset.
733    pub fat_offset: Option<TableOffset>,
734    /// ARM9 overlay table offset.
735    pub arm9_ovt_offset: Option<TableOffset>,
736    /// ARM7 overlay table offset.
737    pub arm7_ovt_offset: Option<TableOffset>,
738    /// Banner offset.
739    pub banner_offset: Option<TableOffset>,
740    /// Blowfish key.
741    pub blowfish_key: Option<&'a BlowfishKey>,
742    /// ARM9 autoload callback.
743    pub arm9_autoload_callback: Option<u32>,
744    /// ARM7 autoload callback.
745    pub arm7_autoload_callback: Option<u32>,
746    /// ARM9 build info offset.
747    pub arm9_build_info_offset: Option<u32>,
748    /// ARM7 build info offset.
749    pub arm7_build_info_offset: Option<u32>,
750    /// Total ROM size.
751    pub rom_size: Option<u32>,
752}
753
754/// Options for [`Rom::load`].
755pub struct RomLoadOptions<'a> {
756    /// Blowfish encryption key.
757    pub key: Option<&'a BlowfishKey>,
758    /// If true (default), compress ARM9 and overlays if they are configured with `compressed: true`.
759    pub compress: bool,
760    /// If true (default), encrypt ARM9 if it's configured with `encrypted: true`.
761    pub encrypt: bool,
762    /// If true (default), load asset files.
763    pub load_files: bool,
764}
765
766impl<'a> Default for RomLoadOptions<'a> {
767    fn default() -> Self {
768        Self { key: None, compress: true, encrypt: true, load_files: true }
769    }
770}