1use std::{
2 backtrace::Backtrace,
3 io::{self, Cursor, Write},
4 mem::size_of,
5 path::Path,
6};
7
8use serde::{Deserialize, Serialize};
9use snafu::Snafu;
10
11use super::{
12 raw::{
13 self, Arm9Footer, HmacSha1Signature, RawArm9Error, RawBannerError, RawBuildInfoError, RawFatError, RawFntError,
14 RawHeaderError, RawOverlayError, RomAlignmentsError, TableOffset,
15 },
16 Arm7, Arm9, Arm9AutoloadError, Arm9Error, Arm9HmacSha1KeyError, Arm9Offsets, Arm9OverlaySignaturesError, Autoload, Banner,
17 BannerError, BannerImageError, BuildInfo, FileBuildError, FileParseError, FileSystem, Header, HeaderBuildError, Logo,
18 LogoError, LogoLoadError, LogoSaveError, Overlay, OverlayError, OverlayInfo, OverlayOptions, OverlayTable,
19 RomConfigAutoload, RomConfigUnknownAutoload,
20};
21use crate::{
22 compress::lz77::Lz77DecompressError,
23 crypto::{
24 blowfish::BlowfishKey,
25 hmac_sha1::{HmacSha1, HmacSha1FromBytesError},
26 },
27 io::{create_dir_all, create_file, create_file_and_dirs, open_file, read_file, read_to_string, FileError},
28 rom::{
29 raw::{FileAlloc, MultibootSignature, RawMultibootSignatureError},
30 Arm9WithTcmsOptions, RomConfig,
31 },
32};
33
34pub struct Rom<'a> {
36 header: Header,
37 header_logo: Logo,
38 arm9: Arm9<'a>,
39 arm9_overlay_table: OverlayTable<'a>,
40 arm7: Arm7<'a>,
41 arm7_overlay_table: OverlayTable<'a>,
42 banner: Banner,
43 files: FileSystem<'a>,
44 multiboot_signature: Option<MultibootSignature>,
45
46 path_order: Vec<String>,
47 config: RomConfig,
48}
49
50#[derive(Debug, Snafu)]
52pub enum RomExtractError {
53 #[snafu(transparent)]
55 RawHeader {
56 source: RawHeaderError,
58 },
59 #[snafu(transparent)]
61 Logo {
62 source: LogoError,
64 },
65 #[snafu(transparent)]
67 RawOverlay {
68 source: RawOverlayError,
70 },
71 #[snafu(transparent)]
73 RawFnt {
74 source: RawFntError,
76 },
77 #[snafu(transparent)]
79 RawFat {
80 source: RawFatError,
82 },
83 #[snafu(transparent)]
85 RawBanner {
86 source: RawBannerError,
88 },
89 #[snafu(transparent)]
91 FileParse {
92 source: FileParseError,
94 },
95 #[snafu(transparent)]
97 RawArm9 {
98 source: RawArm9Error,
100 },
101 #[snafu(transparent)]
103 Arm9Autoload {
104 source: Arm9AutoloadError,
106 },
107 #[snafu(transparent)]
109 RawBuildInfo {
110 source: RawBuildInfoError,
112 },
113 #[snafu(transparent)]
115 Arm9 {
116 source: Arm9Error,
118 },
119 #[snafu(transparent)]
121 RomAlignments {
122 source: RomAlignmentsError,
124 },
125 #[snafu(transparent)]
127 Overlay {
128 source: OverlayError,
130 },
131 #[snafu(transparent)]
133 Arm9HmacSha1Key {
134 source: Arm9HmacSha1KeyError,
136 },
137 #[snafu(transparent)]
139 RawMultibootSignature {
140 source: RawMultibootSignatureError,
142 },
143}
144
145#[derive(Snafu, Debug)]
147pub enum RomBuildError {
148 #[snafu(transparent)]
150 Io {
151 source: io::Error,
153 },
154 #[snafu(transparent)]
156 FileBuild {
157 source: FileBuildError,
159 },
160 #[snafu(transparent)]
162 Banner {
163 source: BannerError,
165 },
166 #[snafu(transparent)]
168 HeaderBuild {
169 source: HeaderBuildError,
171 },
172}
173
174#[derive(Snafu, Debug)]
176pub enum RomSaveError {
177 #[snafu(display("blowfish key is required because ARM9 program is encrypted"))]
179 BlowfishKeyNeeded,
180 #[snafu(transparent)]
182 Io {
183 source: io::Error,
185 },
186 #[snafu(transparent)]
188 File {
189 source: FileError,
191 },
192 #[snafu(transparent)]
194 SerdeSaphyrDeserialize {
195 source: serde_saphyr::Error,
197 },
198 #[snafu(transparent)]
200 SerdeSaphyrSerialize {
201 source: serde_saphyr::ser_error::Error,
203 },
204 #[snafu(transparent)]
206 LogoSave {
207 source: LogoSaveError,
209 },
210 #[snafu(transparent)]
212 LogoLoad {
213 source: LogoLoadError,
215 },
216 #[snafu(transparent)]
218 RawBuildInfo {
219 source: RawBuildInfoError,
221 },
222 #[snafu(transparent)]
224 Arm9 {
225 source: Arm9Error,
227 },
228 #[snafu(transparent)]
230 Arm9Autoload {
231 source: Arm9AutoloadError,
233 },
234 #[snafu(transparent)]
236 BannerImage {
237 source: BannerImageError,
239 },
240 #[snafu(transparent)]
242 Lz77Decompress {
243 source: Lz77DecompressError,
245 },
246 #[snafu(transparent)]
248 Overlay {
249 source: OverlayError,
251 },
252 #[snafu(transparent)]
254 HmacSha1FromBytes {
255 source: HmacSha1FromBytesError,
257 },
258 #[snafu(transparent)]
260 Arm9HmacSha1Key {
261 source: Arm9HmacSha1KeyError,
263 },
264 #[snafu(transparent)]
266 Arm9OverlaySignatures {
267 source: Arm9OverlaySignaturesError,
269 },
270 #[snafu(display("HMAC-SHA1 key was not provided for a signed overlay:\n{backtrace}"))]
272 NoHmacSha1Key {
273 backtrace: Backtrace,
275 },
276 #[snafu(display("autoload index {index} not found in config:\n{backtrace}"))]
278 AutoloadNotFound {
279 index: u32,
281 backtrace: Backtrace,
283 },
284}
285
286#[derive(Serialize, Deserialize)]
288pub struct Arm9BuildConfig {
289 #[serde(flatten)]
291 pub offsets: Arm9Offsets,
292 pub encrypted: bool,
294 pub compressed: bool,
296 #[serde(flatten)]
298 pub build_info: BuildInfo,
299}
300
301#[derive(Serialize, Deserialize)]
303pub struct OverlayConfig {
304 #[serde(flatten)]
306 pub info: OverlayInfo,
307 pub signed: bool,
309 pub file_name: String,
311}
312
313#[derive(Serialize, Deserialize)]
315pub struct OverlayTableConfig {
316 pub table_signed: bool,
318 #[serde(skip_serializing_if = "Option::is_none")]
321 pub table_signature: Option<HmacSha1Signature>,
322 pub overlays: Vec<OverlayConfig>,
324}
325
326impl<'a> Rom<'a> {
327 pub fn load<P: AsRef<Path>>(config_path: P, options: RomLoadOptions) -> Result<Self, RomSaveError> {
333 let config_path = config_path.as_ref();
334 log::info!("Loading ROM from {}", config_path.display());
335
336 let config: RomConfig = serde_saphyr::from_reader(open_file(config_path)?)?;
337 let path = config_path.parent().unwrap();
338
339 let (header, header_logo) = if options.load_header {
341 let header: Header = serde_saphyr::from_reader(open_file(path.join(&config.header))?)?;
342 let header_logo = Logo::from_png(path.join(&config.header_logo))?;
343 (header, header_logo)
344 } else {
345 Default::default()
346 };
347
348 let arm9_build_config: Arm9BuildConfig = serde_saphyr::from_reader(open_file(path.join(&config.arm9_config))?)?;
350 let arm9 = read_file(path.join(&config.arm9_bin))?;
351
352 let mut autoloads = vec![];
354
355 let itcm = read_file(path.join(&config.itcm.bin))?;
356 let itcm_info = serde_saphyr::from_reader(open_file(path.join(&config.itcm.config))?)?;
357 let itcm = Autoload::new(itcm, itcm_info);
358 autoloads.push(itcm);
359
360 let dtcm = read_file(path.join(&config.dtcm.bin))?;
361 let dtcm_info = serde_saphyr::from_reader(open_file(path.join(&config.dtcm.config))?)?;
362 let dtcm = Autoload::new(dtcm, dtcm_info);
363 autoloads.push(dtcm);
364
365 for unknown_autoload in &config.unknown_autoloads {
366 let autoload = read_file(path.join(&unknown_autoload.files.bin))?;
367 let autoload_info = serde_saphyr::from_reader(open_file(path.join(&unknown_autoload.files.config))?)?;
368 let autoload = Autoload::new(autoload, autoload_info);
369 autoloads.push(autoload);
370 }
371
372 autoloads.sort_by_key(|autoload| autoload.kind());
373
374 let arm9_hmac_sha1 = if let Some(hmac_sha1_key_file) = &config.arm9_hmac_sha1_key {
376 let hmac_sha1_key = read_file(path.join(hmac_sha1_key_file))?;
377 Some(HmacSha1::try_from(hmac_sha1_key.as_ref())?)
378 } else {
379 None
380 };
381
382 let arm9_overlays = if let Some(arm9_overlays_config) = &config.arm9_overlays {
384 Self::load_overlays(&path.join(arm9_overlays_config), "arm9", arm9_hmac_sha1, &options)?
385 } else {
386 Default::default()
387 };
388
389 let mut arm9 = Arm9::with_autoloads(arm9, &autoloads, arm9_build_config.offsets, Arm9WithTcmsOptions {
391 originally_compressed: arm9_build_config.compressed,
392 originally_encrypted: arm9_build_config.encrypted,
393 })?;
394 arm9_build_config.build_info.assign_to_raw(arm9.build_info_mut()?);
395 arm9.update_overlay_signatures(&arm9_overlays)?;
396 if arm9_build_config.compressed && options.compress {
397 log::info!("Compressing ARM9 program");
398 arm9.compress()?;
399 }
400 if arm9_build_config.encrypted && options.encrypt {
401 let Some(key) = options.key else {
402 return BlowfishKeyNeededSnafu {}.fail();
403 };
404 log::info!("Encrypting ARM9 program");
405 arm9.encrypt(key, header.original.gamecode.to_le_u32())?;
406 }
407
408 let arm7_overlays = if let Some(arm7_overlays_config) = &config.arm7_overlays {
410 Self::load_overlays(&path.join(arm7_overlays_config), "arm7", None, &options)?
411 } else {
412 Default::default()
413 };
414
415 let arm7 = read_file(path.join(&config.arm7_bin))?;
417 let arm7_config = serde_saphyr::from_reader(open_file(path.join(&config.arm7_config))?)?;
418 let arm7 = Arm7::new(arm7, arm7_config);
419
420 let banner = if options.load_banner {
422 let banner_path = path.join(&config.banner);
423 let banner_dir = banner_path.parent().unwrap();
424 let mut banner: Banner = serde_saphyr::from_reader(open_file(&banner_path)?)?;
425 banner.images.load(banner_dir)?;
426 banner
427 } else {
428 Default::default()
429 };
430
431 let num_overlays = arm9_overlays.overlays().len() + arm7_overlays.overlays().len();
433 let (files, path_order) = if options.load_files {
434 log::info!("Loading ROM assets");
435 let files = FileSystem::load(path.join(&config.files_dir), num_overlays)?;
436 let path_order =
437 read_to_string(path.join(&config.path_order))?.trim().lines().map(|l| l.to_string()).collect::<Vec<_>>();
438 (files, path_order)
439 } else {
440 (FileSystem::new(num_overlays), vec![])
441 };
442
443 let multiboot_signature = if let Some(multiboot_signature) = config.multiboot_signature.as_ref() {
445 serde_saphyr::from_reader(open_file(path.join(multiboot_signature))?)?
446 } else {
447 None
448 };
449
450 Ok(Self {
451 header,
452 header_logo,
453 arm9,
454 arm9_overlay_table: arm9_overlays,
455 arm7,
456 arm7_overlay_table: arm7_overlays,
457 banner,
458 files,
459 path_order,
460 multiboot_signature,
461 config,
462 })
463 }
464
465 fn load_overlays(
466 config_path: &Path,
467 processor: &str,
468 hmac_sha1: Option<HmacSha1>,
469 options: &RomLoadOptions,
470 ) -> Result<OverlayTable<'a>, RomSaveError> {
471 let path = config_path.parent().unwrap();
472 let mut overlays = vec![];
473 let overlay_table_config: OverlayTableConfig = serde_saphyr::from_reader(open_file(config_path)?)?;
474 let num_overlays = overlay_table_config.overlays.len();
475 for mut config in overlay_table_config.overlays.into_iter() {
476 let data = read_file(path.join(config.file_name))?;
477 let compressed = config.info.compressed;
478 config.info.compressed = false;
479 let mut overlay = Overlay::new(data, OverlayOptions { info: config.info, originally_compressed: compressed })?;
480
481 if options.compress {
482 if compressed {
483 log::info!("Compressing {processor} overlay {}/{}", overlay.id(), num_overlays - 1);
484 overlay.compress()?;
485 }
486
487 if config.signed {
488 let Some(ref hmac_sha1) = hmac_sha1 else {
489 return NoHmacSha1KeySnafu {}.fail();
490 };
491 overlay.sign(hmac_sha1)?;
492 }
493 }
494
495 overlays.push(overlay);
496 }
497
498 let mut overlay_table = OverlayTable::new(overlays);
499 if overlay_table_config.table_signed {
500 if let Some(signature) = overlay_table_config.table_signature {
501 overlay_table.set_signature(signature);
502 } else {
503 let Some(ref hmac_sha1) = hmac_sha1 else {
504 return NoHmacSha1KeySnafu {}.fail();
505 };
506 overlay_table.sign(hmac_sha1);
507 }
508 }
509
510 Ok(overlay_table)
511 }
512
513 pub fn save<P: AsRef<Path>>(&self, path: P, key: Option<&BlowfishKey>) -> Result<(), RomSaveError> {
519 let path = path.as_ref();
520 create_dir_all(path)?;
521
522 log::info!("Saving ROM to directory {}", path.display());
523
524 serde_saphyr::to_io_writer(&mut create_file_and_dirs(path.join("config.yaml"))?, &self.config)?;
526
527 serde_saphyr::to_io_writer(&mut create_file_and_dirs(path.join(&self.config.header))?, &self.header)?;
529 self.header_logo.save_png(path.join(&self.config.header_logo))?;
530
531 let arm9_build_config = self.arm9_build_config()?;
533 serde_saphyr::to_io_writer(&mut create_file_and_dirs(path.join(&self.config.arm9_config))?, &arm9_build_config)?;
534 let mut plain_arm9 = self.arm9.clone();
535 if plain_arm9.is_encrypted() {
536 let Some(key) = key else {
537 return BlowfishKeyNeededSnafu {}.fail();
538 };
539 log::info!("Decrypting ARM9 program");
540 plain_arm9.decrypt(key, self.header.original.gamecode.to_le_u32())?;
541 }
542 if plain_arm9.is_compressed()? {
543 log::info!("Decompressing ARM9 program");
544 plain_arm9.decompress()?;
545 }
546 create_file_and_dirs(path.join(&self.config.arm9_bin))?.write_all(plain_arm9.code()?)?;
547
548 if let Some(arm9_hmac_sha1_key) = plain_arm9.hmac_sha1_key()? {
550 if let Some(key_file) = &self.config.arm9_hmac_sha1_key {
551 create_file_and_dirs(path.join(key_file))?.write_all(arm9_hmac_sha1_key.as_ref())?;
552 }
553 } else if self.config.arm9_hmac_sha1_key.is_some() {
554 log::warn!("ARM9 HMAC-SHA1 key not found, but config requested it to be saved");
555 }
556
557 for autoload in plain_arm9.autoloads()?.iter() {
559 let (bin_path, config_path) = match autoload.kind() {
560 raw::AutoloadKind::Itcm => (path.join(&self.config.itcm.bin), path.join(&self.config.itcm.config)),
561 raw::AutoloadKind::Dtcm => (path.join(&self.config.dtcm.bin), path.join(&self.config.dtcm.config)),
562 raw::AutoloadKind::Unknown(index) => {
563 let unknown_autoload = self
564 .config
565 .unknown_autoloads
566 .iter()
567 .find(|autoload| autoload.index == index)
568 .ok_or_else(|| AutoloadNotFoundSnafu { index }.build())?;
569 (path.join(&unknown_autoload.files.bin), path.join(&unknown_autoload.files.config))
570 }
571 };
572 create_file_and_dirs(bin_path)?.write_all(autoload.code())?;
573 serde_saphyr::to_io_writer(&mut create_file_and_dirs(config_path)?, autoload.info())?;
574 }
575
576 if let Some(arm9_overlays_config) = &self.config.arm9_overlays {
578 Self::save_overlays(&path.join(arm9_overlays_config), &self.arm9_overlay_table, "arm9")?;
579 }
580
581 create_file_and_dirs(path.join(&self.config.arm7_bin))?.write_all(self.arm7.full_data())?;
583 serde_saphyr::to_io_writer(&mut create_file_and_dirs(path.join(&self.config.arm7_config))?, self.arm7.offsets())?;
584
585 if let Some(arm7_overlays_config) = &self.config.arm7_overlays {
587 Self::save_overlays(&path.join(arm7_overlays_config), &self.arm7_overlay_table, "arm7")?;
588 }
589
590 {
592 let banner_path = path.join(&self.config.banner);
593 let banner_dir = banner_path.parent().unwrap();
594 serde_saphyr::to_io_writer(&mut create_file_and_dirs(&banner_path)?, &self.banner)?;
595 self.banner.images.save_bitmap_file(banner_dir)?;
596 }
597
598 {
600 log::info!("Saving ROM assets");
601 let files_path = path.join(&self.config.files_dir);
602 self.files.traverse_files(["/"], |file, path| {
603 let path = files_path.join(path);
604 create_dir_all(&path).expect("failed to create file directory");
606 create_file(path.join(file.name()))
607 .expect("failed to create file")
608 .write_all(file.contents())
609 .expect("failed to write file");
610 });
611 }
612 let mut path_order_file = create_file_and_dirs(path.join(&self.config.path_order))?;
613 for path in &self.path_order {
614 path_order_file.write_all(path.as_bytes())?;
615 path_order_file.write_all("\n".as_bytes())?;
616 }
617
618 if let Some(multiboot_signature) = &self.multiboot_signature {
620 if let Some(signature_file) = &self.config.multiboot_signature {
621 let file_path = path.join(signature_file);
622 serde_saphyr::to_io_writer(&mut create_file_and_dirs(&file_path)?, multiboot_signature)?;
623 }
624 } else if self.config.multiboot_signature.is_some() {
625 log::warn!("Multiboot signature not found, but config requested it to be saved");
626 }
627
628 Ok(())
629 }
630
631 pub fn arm9_build_config(&self) -> Result<Arm9BuildConfig, RomSaveError> {
633 Ok(Arm9BuildConfig {
634 offsets: *self.arm9.offsets(),
635 encrypted: self.arm9.is_encrypted(),
636 compressed: self.arm9.is_compressed()?,
637 build_info: (*self.arm9.build_info()?).into(),
638 })
639 }
640
641 fn save_overlays(config_path: &Path, overlay_table: &OverlayTable, processor: &str) -> Result<(), RomSaveError> {
642 let overlays = overlay_table.overlays();
643 if !overlays.is_empty() {
644 let overlays_path = config_path.parent().unwrap();
645 create_dir_all(overlays_path)?;
646
647 let mut configs = vec![];
648 for overlay in overlays {
649 let name = format!("ov{:03}", overlay.id());
650
651 let mut plain_overlay = overlay.clone();
652 configs.push(OverlayConfig {
653 info: plain_overlay.info().clone(),
654 file_name: format!("{name}.bin"),
655 signed: overlay.is_signed(),
656 });
657
658 if plain_overlay.is_compressed() {
659 log::info!("Decompressing {processor} overlay {}/{}", overlay.id(), overlays.len() - 1);
660 plain_overlay.decompress()?;
661 }
662 create_file(overlays_path.join(format!("{name}.bin")))?.write_all(plain_overlay.code())?;
663 }
664
665 let overlay_table_config = OverlayTableConfig {
666 table_signed: overlay_table.is_signed(),
667 table_signature: overlay_table.signature(),
668 overlays: configs,
669 };
670 serde_saphyr::to_io_writer(&mut create_file_and_dirs(config_path)?, &overlay_table_config)?;
671 }
672 Ok(())
673 }
674
675 pub fn extract(rom: &'a raw::Rom) -> Result<Self, RomExtractError> {
681 let header = rom.header()?;
682 log::info!("Extracting from {}", header.title);
683
684 let fnt = rom.fnt()?;
685 let fat = rom.fat()?;
686 let banner = rom.banner()?;
687 let file_root = FileSystem::parse(&fnt, fat, rom)?;
688 let path_order = file_root.compute_path_order();
689
690 let arm9 = rom.arm9()?;
691 let mut decompressed_arm9 = arm9.clone();
692 decompressed_arm9.decompress()?;
693
694 let arm9_overlays = rom.arm9_overlay_table_with(&decompressed_arm9)?;
695 let arm9_overlays = OverlayTable::parse_arm9(arm9_overlays, rom, &decompressed_arm9)?;
696 let arm7_overlays = rom.arm7_overlay_table()?;
697 let arm7_overlays = OverlayTable::parse_arm7(arm7_overlays, rom)?;
698
699 let autoloads = decompressed_arm9.autoloads()?;
700 let unknown_autoloads = autoloads
701 .iter()
702 .filter_map(|autoload| {
703 let raw::AutoloadKind::Unknown(index) = autoload.kind() else {
704 return None;
705 };
706 Some(RomConfigUnknownAutoload {
707 index,
708 files: RomConfigAutoload {
709 bin: format!("arm9/unk_autoload_{index}.bin").into(),
710 config: format!("arm9/unk_autoload_{index}.yaml").into(),
711 },
712 })
713 })
714 .collect();
715
716 let has_arm9_hmac_sha1 = decompressed_arm9.hmac_sha1_key()?.is_some();
717
718 let multiboot_signature = rom.multiboot_signature()?.cloned();
719
720 let alignment = rom.alignments()?;
721
722 let config = RomConfig {
723 file_image_padding_value: rom.file_image_padding_value()?,
724 section_padding_value: rom.section_padding_value()?,
725 header: "header.yaml".into(),
726 header_logo: "header_logo.png".into(),
727 arm9_bin: "arm9/arm9.bin".into(),
728 arm9_config: "arm9/arm9.yaml".into(),
729 arm7_bin: "arm7/arm7.bin".into(),
730 arm7_config: "arm7/arm7.yaml".into(),
731 itcm: RomConfigAutoload { bin: "arm9/itcm.bin".into(), config: "arm9/itcm.yaml".into() },
732 unknown_autoloads,
733 dtcm: RomConfigAutoload { bin: "arm9/dtcm.bin".into(), config: "arm9/dtcm.yaml".into() },
734 arm9_overlays: if arm9_overlays.is_empty() { None } else { Some("arm9_overlays/overlays.yaml".into()) },
735 arm7_overlays: if arm7_overlays.is_empty() { None } else { Some("arm7_overlays/overlays.yaml".into()) },
736 banner: "banner/banner.yaml".into(),
737 files_dir: "files/".into(),
738 path_order: "path_order.txt".into(),
739 multiboot_signature: if multiboot_signature.is_none() { None } else { Some("multiboot_signature.yaml".into()) },
740 arm9_hmac_sha1_key: has_arm9_hmac_sha1.then_some("arm9/hmac_sha1_key.bin".into()),
741 alignment,
742 };
743
744 Ok(Self {
745 header: Header::load_raw(header),
746 header_logo: Logo::decompress(&header.logo)?,
747 arm9,
748 arm9_overlay_table: arm9_overlays,
749 arm7: rom.arm7()?,
750 arm7_overlay_table: arm7_overlays,
751 banner: Banner::load_raw(&banner),
752 files: file_root,
753 multiboot_signature,
754 path_order,
755 config,
756 })
757 }
758
759 pub fn build(mut self, key: Option<&BlowfishKey>) -> Result<raw::Rom<'a>, RomBuildError> {
765 let mut context = BuildContext { blowfish_key: key, ..Default::default() };
766
767 let mut cursor = Cursor::new(Vec::with_capacity(128 * 1024)); context.header_offset = Some(cursor.position() as u32);
771 cursor.write_all(&[0u8; size_of::<raw::Header>()])?;
772
773 self.align_section(&mut cursor, self.config.alignment.arm9)?;
775 context.arm9_offset = Some(cursor.position() as u32);
776 context.arm9_autoload_callback = Some(self.arm9.autoload_callback());
777 context.arm9_build_info_offset = Some(self.arm9.build_info_offset());
778 cursor.write_all(self.arm9.full_data())?;
779 let footer = Arm9Footer::new(self.arm9.build_info_offset(), self.arm9.overlay_signatures_offset());
780 cursor.write_all(bytemuck::bytes_of(&footer))?;
781
782 let max_file_id = self.files.max_file_id();
783 let mut file_allocs = vec![FileAlloc::default(); max_file_id as usize + 1];
784
785 if !self.arm9_overlay_table.is_empty() {
786 self.align_section(&mut cursor, self.config.alignment.arm9_overlay_table)?;
788 context.arm9_ovt_offset = Some(TableOffset {
789 offset: cursor.position() as u32,
790 size: (self.arm9_overlay_table.len() * size_of::<raw::Overlay>()) as u32,
791 });
792 let raw_table = self.arm9_overlay_table.build();
793 cursor.write_all(raw_table.as_bytes())?;
794
795 for overlay in self.arm9_overlay_table.overlays() {
797 self.align_section(&mut cursor, self.config.alignment.arm9_overlay)?;
798 let start = cursor.position() as u32;
799 let end = start + overlay.full_data().len() as u32;
800 file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
801
802 cursor.write_all(overlay.full_data())?;
803 }
804 }
805
806 self.align_section(&mut cursor, self.config.alignment.arm7)?;
808 context.arm7_offset = Some(cursor.position() as u32);
809 context.arm7_autoload_callback = Some(self.arm7.autoload_callback());
810 context.arm7_build_info_offset = None;
811 cursor.write_all(self.arm7.full_data())?;
812
813 if !self.arm7_overlay_table.is_empty() {
814 self.align_section(&mut cursor, self.config.alignment.arm7_overlay_table)?;
816 context.arm7_ovt_offset = Some(TableOffset {
817 offset: cursor.position() as u32,
818 size: (self.arm7_overlay_table.len() * size_of::<raw::Overlay>()) as u32,
819 });
820 let raw_table = self.arm7_overlay_table.build();
821 cursor.write_all(raw_table.as_bytes())?;
822
823 for overlay in self.arm7_overlay_table.overlays() {
825 self.align_section(&mut cursor, self.config.alignment.arm7_overlay)?;
826 let start = cursor.position() as u32;
827 let end = start + overlay.full_data().len() as u32;
828 file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
829
830 cursor.write_all(overlay.full_data())?;
831 }
832 }
833
834 self.align_section(&mut cursor, self.config.alignment.file_name_table)?;
836 self.files.sort_for_fnt();
837 let fnt = self.files.build_fnt()?.build()?;
838 context.fnt_offset = Some(TableOffset { offset: cursor.position() as u32, size: fnt.len() as u32 });
839 cursor.write_all(&fnt)?;
840
841 self.align_section(&mut cursor, self.config.alignment.file_allocation_table)?;
843 context.fat_offset =
844 Some(TableOffset { offset: cursor.position() as u32, size: (file_allocs.len() * size_of::<FileAlloc>()) as u32 });
845 cursor.write_all(bytemuck::cast_slice(&file_allocs))?;
846
847 self.align_section(&mut cursor, self.config.alignment.banner)?;
849 let banner = self.banner.build()?;
850 context.banner_offset = Some(TableOffset { offset: cursor.position() as u32, size: banner.full_data().len() as u32 });
851 cursor.write_all(banner.full_data())?;
852
853 self.align_file_image(&mut cursor, self.config.alignment.file_image_block)?;
855 self.files.sort_for_rom();
856 self.files.traverse_files(self.path_order.iter().map(|s| s.as_str()), |file, _| {
857 self.align_file_image(&mut cursor, self.config.alignment.file).expect("failed to align after file");
859
860 let contents = file.contents();
861 let start = cursor.position() as u32;
862 let end = start + contents.len() as u32;
863 file_allocs[file.id() as usize] = FileAlloc { start, end };
864
865 cursor.write_all(contents).expect("failed to write file contents");
866 });
867
868 context.rom_size = Some(cursor.position() as u32);
871 if let Some(multiboot_signature) = &self.multiboot_signature {
872 cursor.write_all(bytemuck::bytes_of(multiboot_signature))?;
873 }
874
875 let padded_rom_size = cursor.position().next_power_of_two().max(128 * 1024) as u32;
877 self.align_file_image(&mut cursor, padded_rom_size)?;
878
879 cursor.set_position(context.fat_offset.unwrap().offset as u64);
881 cursor.write_all(bytemuck::cast_slice(&file_allocs))?;
882
883 cursor.set_position(context.header_offset.unwrap() as u64);
885 let header = self.header.build(&context, &self)?;
886 cursor.write_all(bytemuck::bytes_of(&header))?;
887
888 Ok(raw::Rom::new(cursor.into_inner()))
889 }
890
891 fn align(&self, cursor: &mut Cursor<Vec<u8>>, alignment: u32, padding_value: u8) -> Result<(), RomBuildError> {
892 assert!(alignment.is_power_of_two(), "alignment must be a power of two");
893 let mask = alignment - 1;
894 let padding = (!cursor.position() as u32 + 1) & mask;
895 for _ in 0..padding {
896 cursor.write_all(&[padding_value])?;
897 }
898 Ok(())
899 }
900
901 fn align_section(&self, cursor: &mut Cursor<Vec<u8>>, alignment: u32) -> Result<(), RomBuildError> {
902 self.align(cursor, alignment, self.config.section_padding_value)
903 }
904
905 fn align_file_image(&self, cursor: &mut Cursor<Vec<u8>>, alignment: u32) -> Result<(), RomBuildError> {
906 self.align(cursor, alignment, self.config.file_image_padding_value)
907 }
908
909 pub fn header_logo(&self) -> &Logo {
911 &self.header_logo
912 }
913
914 pub fn header_logo_mut(&mut self) -> &mut Logo {
916 &mut self.header_logo
917 }
918
919 pub fn arm9(&self) -> &Arm9<'a> {
921 &self.arm9
922 }
923
924 pub fn arm9_mut(&mut self) -> &mut Arm9<'a> {
926 &mut self.arm9
927 }
928
929 pub fn arm9_overlay_table(&self) -> &OverlayTable<'a> {
931 &self.arm9_overlay_table
932 }
933
934 pub fn arm9_overlay_table_mut(&mut self) -> &mut OverlayTable<'a> {
936 &mut self.arm9_overlay_table
937 }
938
939 pub fn arm9_overlays(&self) -> &[Overlay<'a>] {
941 self.arm9_overlay_table.overlays()
942 }
943
944 pub fn arm9_overlays_mut(&mut self) -> &mut [Overlay<'a>] {
946 self.arm9_overlay_table.overlays_mut()
947 }
948
949 pub fn arm7(&self) -> &Arm7<'a> {
951 &self.arm7
952 }
953
954 pub fn arm7_mut(&mut self) -> &mut Arm7<'a> {
956 &mut self.arm7
957 }
958
959 pub fn arm7_overlay_table(&self) -> &OverlayTable<'a> {
961 &self.arm7_overlay_table
962 }
963
964 pub fn arm7_overlay_table_mut(&mut self) -> &mut OverlayTable<'a> {
966 &mut self.arm7_overlay_table
967 }
968
969 pub fn arm7_overlays(&self) -> &[Overlay<'a>] {
971 self.arm7_overlay_table.overlays()
972 }
973
974 pub fn arm7_overlays_mut(&mut self) -> &mut [Overlay<'a>] {
976 self.arm7_overlay_table.overlays_mut()
977 }
978
979 pub fn header(&self) -> &Header {
981 &self.header
982 }
983
984 pub fn header_mut(&mut self) -> &mut Header {
986 &mut self.header
987 }
988
989 pub fn config(&self) -> &RomConfig {
991 &self.config
992 }
993
994 pub fn multiboot_signature(&self) -> Option<&MultibootSignature> {
996 self.multiboot_signature.as_ref()
997 }
998}
999
1000#[derive(Default)]
1002pub struct BuildContext<'a> {
1003 pub header_offset: Option<u32>,
1005 pub arm9_offset: Option<u32>,
1007 pub arm7_offset: Option<u32>,
1009 pub fnt_offset: Option<TableOffset>,
1011 pub fat_offset: Option<TableOffset>,
1013 pub arm9_ovt_offset: Option<TableOffset>,
1015 pub arm7_ovt_offset: Option<TableOffset>,
1017 pub banner_offset: Option<TableOffset>,
1019 pub blowfish_key: Option<&'a BlowfishKey>,
1021 pub arm9_autoload_callback: Option<u32>,
1023 pub arm7_autoload_callback: Option<u32>,
1025 pub arm9_build_info_offset: Option<u32>,
1027 pub arm7_build_info_offset: Option<u32>,
1029 pub rom_size: Option<u32>,
1031}
1032
1033pub struct RomLoadOptions<'a> {
1035 pub key: Option<&'a BlowfishKey>,
1037 pub compress: bool,
1039 pub encrypt: bool,
1041 pub load_files: bool,
1043 pub load_header: bool,
1045 pub load_banner: bool,
1047 pub load_multiboot_signature: bool,
1049}
1050
1051impl Default for RomLoadOptions<'_> {
1052 fn default() -> Self {
1053 Self {
1054 key: None,
1055 compress: true,
1056 encrypt: true,
1057 load_files: true,
1058 load_header: true,
1059 load_banner: true,
1060 load_multiboot_signature: true,
1061 }
1062 }
1063}