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::{raw::FileAlloc, Arm9WithTcmsOptions, RomConfig},
29};
30
31pub struct Rom<'a> {
33 header: Header,
34 header_logo: Logo,
35 arm9: Arm9<'a>,
36 arm9_overlay_table: OverlayTable<'a>,
37 arm7: Arm7<'a>,
38 arm7_overlay_table: OverlayTable<'a>,
39 banner: Banner,
40 files: FileSystem<'a>,
41 path_order: Vec<String>,
42 config: RomConfig,
43}
44
45#[derive(Debug, Snafu)]
47pub enum RomExtractError {
48 #[snafu(transparent)]
50 RawHeader {
51 source: RawHeaderError,
53 },
54 #[snafu(transparent)]
56 Logo {
57 source: LogoError,
59 },
60 #[snafu(transparent)]
62 RawOverlay {
63 source: RawOverlayError,
65 },
66 #[snafu(transparent)]
68 RawFnt {
69 source: RawFntError,
71 },
72 #[snafu(transparent)]
74 RawFat {
75 source: RawFatError,
77 },
78 #[snafu(transparent)]
80 RawBanner {
81 source: RawBannerError,
83 },
84 #[snafu(transparent)]
86 FileParse {
87 source: FileParseError,
89 },
90 #[snafu(transparent)]
92 RawArm9 {
93 source: RawArm9Error,
95 },
96 #[snafu(transparent)]
98 Arm9Autoload {
99 source: Arm9AutoloadError,
101 },
102 #[snafu(transparent)]
104 RawBuildInfo {
105 source: RawBuildInfoError,
107 },
108 #[snafu(transparent)]
110 Arm9 {
111 source: Arm9Error,
113 },
114 #[snafu(transparent)]
116 RomAlignments {
117 source: RomAlignmentsError,
119 },
120 #[snafu(transparent)]
122 Overlay {
123 source: OverlayError,
125 },
126 #[snafu(transparent)]
128 Arm9HmacSha1Key {
129 source: Arm9HmacSha1KeyError,
131 },
132}
133
134#[derive(Snafu, Debug)]
136pub enum RomBuildError {
137 #[snafu(transparent)]
139 Io {
140 source: io::Error,
142 },
143 #[snafu(transparent)]
145 FileBuild {
146 source: FileBuildError,
148 },
149 #[snafu(transparent)]
151 Banner {
152 source: BannerError,
154 },
155 #[snafu(transparent)]
157 HeaderBuild {
158 source: HeaderBuildError,
160 },
161}
162
163#[derive(Snafu, Debug)]
165pub enum RomSaveError {
166 #[snafu(display("blowfish key is required because ARM9 program is encrypted"))]
168 BlowfishKeyNeeded,
169 #[snafu(transparent)]
171 Io {
172 source: io::Error,
174 },
175 #[snafu(transparent)]
177 File {
178 source: FileError,
180 },
181 #[snafu(transparent)]
183 SerdeJson {
184 source: serde_yml::Error,
186 },
187 #[snafu(transparent)]
189 LogoSave {
190 source: LogoSaveError,
192 },
193 #[snafu(transparent)]
195 LogoLoad {
196 source: LogoLoadError,
198 },
199 #[snafu(transparent)]
201 RawBuildInfo {
202 source: RawBuildInfoError,
204 },
205 #[snafu(transparent)]
207 Arm9 {
208 source: Arm9Error,
210 },
211 #[snafu(transparent)]
213 Arm9Autoload {
214 source: Arm9AutoloadError,
216 },
217 #[snafu(transparent)]
219 BannerImage {
220 source: BannerImageError,
222 },
223 #[snafu(transparent)]
225 Lz77Decompress {
226 source: Lz77DecompressError,
228 },
229 #[snafu(transparent)]
231 Overlay {
232 source: OverlayError,
234 },
235 #[snafu(transparent)]
237 HmacSha1FromBytes {
238 source: HmacSha1FromBytesError,
240 },
241 #[snafu(transparent)]
243 Arm9HmacSha1Key {
244 source: Arm9HmacSha1KeyError,
246 },
247 #[snafu(transparent)]
249 Arm9OverlaySignatures {
250 source: Arm9OverlaySignaturesError,
252 },
253 #[snafu(display("HMAC-SHA1 key was not provided for a signed overlay:\n{backtrace}"))]
255 NoHmacSha1Key {
256 backtrace: Backtrace,
258 },
259 #[snafu(display("autoload index {index} not found in config:\n{backtrace}"))]
261 AutoloadNotFound {
262 index: u32,
264 backtrace: Backtrace,
266 },
267}
268
269#[derive(Serialize, Deserialize)]
271pub struct Arm9BuildConfig {
272 #[serde(flatten)]
274 pub offsets: Arm9Offsets,
275 pub encrypted: bool,
277 pub compressed: bool,
279 #[serde(flatten)]
281 pub build_info: BuildInfo,
282}
283
284#[derive(Serialize, Deserialize)]
286pub struct OverlayConfig {
287 #[serde(flatten)]
289 pub info: OverlayInfo,
290 pub signed: bool,
292 pub file_name: String,
294}
295
296#[derive(Serialize, Deserialize)]
298pub struct OverlayTableConfig {
299 pub table_signed: bool,
301 #[serde(skip_serializing_if = "Option::is_none")]
304 pub table_signature: Option<HmacSha1Signature>,
305 pub overlays: Vec<OverlayConfig>,
307}
308
309impl<'a> Rom<'a> {
310 pub fn load<P: AsRef<Path>>(config_path: P, options: RomLoadOptions) -> Result<Self, RomSaveError> {
316 let config_path = config_path.as_ref();
317 log::info!("Loading ROM from {}", config_path.display());
318
319 let config: RomConfig = serde_yml::from_reader(open_file(config_path)?)?;
320 let path = config_path.parent().unwrap();
321
322 let (header, header_logo) = if options.load_header {
324 let header: Header = serde_yml::from_reader(open_file(path.join(&config.header))?)?;
325 let header_logo = Logo::from_png(path.join(&config.header_logo))?;
326 (header, header_logo)
327 } else {
328 Default::default()
329 };
330
331 let arm9_build_config: Arm9BuildConfig = serde_yml::from_reader(open_file(path.join(&config.arm9_config))?)?;
333 let arm9 = read_file(path.join(&config.arm9_bin))?;
334
335 let mut autoloads = vec![];
337
338 let itcm = read_file(path.join(&config.itcm.bin))?;
339 let itcm_info = serde_yml::from_reader(open_file(path.join(&config.itcm.config))?)?;
340 let itcm = Autoload::new(itcm, itcm_info);
341 autoloads.push(itcm);
342
343 let dtcm = read_file(path.join(&config.dtcm.bin))?;
344 let dtcm_info = serde_yml::from_reader(open_file(path.join(&config.dtcm.config))?)?;
345 let dtcm = Autoload::new(dtcm, dtcm_info);
346 autoloads.push(dtcm);
347
348 for unknown_autoload in &config.unknown_autoloads {
349 let autoload = read_file(path.join(&unknown_autoload.files.bin))?;
350 let autoload_info = serde_yml::from_reader(open_file(path.join(&unknown_autoload.files.config))?)?;
351 let autoload = Autoload::new(autoload, autoload_info);
352 autoloads.push(autoload);
353 }
354
355 autoloads.sort_by_key(|autoload| autoload.kind());
356
357 let arm9_hmac_sha1 = if let Some(hmac_sha1_key_file) = &config.arm9_hmac_sha1_key {
359 let hmac_sha1_key = read_file(path.join(hmac_sha1_key_file))?;
360 Some(HmacSha1::try_from(hmac_sha1_key.as_ref())?)
361 } else {
362 None
363 };
364
365 let arm9_overlays = if let Some(arm9_overlays_config) = &config.arm9_overlays {
367 Self::load_overlays(&path.join(arm9_overlays_config), "arm9", arm9_hmac_sha1, &options)?
368 } else {
369 Default::default()
370 };
371
372 let mut arm9 = Arm9::with_autoloads(
374 arm9,
375 &autoloads,
376 arm9_build_config.offsets,
377 Arm9WithTcmsOptions {
378 originally_compressed: arm9_build_config.compressed,
379 originally_encrypted: arm9_build_config.encrypted,
380 },
381 )?;
382 arm9_build_config.build_info.assign_to_raw(arm9.build_info_mut()?);
383 arm9.update_overlay_signatures(&arm9_overlays)?;
384 if arm9_build_config.compressed && options.compress {
385 log::info!("Compressing ARM9 program");
386 arm9.compress()?;
387 }
388 if arm9_build_config.encrypted && options.encrypt {
389 let Some(key) = options.key else {
390 return BlowfishKeyNeededSnafu {}.fail();
391 };
392 log::info!("Encrypting ARM9 program");
393 arm9.encrypt(key, header.original.gamecode.to_le_u32())?;
394 }
395
396 let arm7_overlays = if let Some(arm7_overlays_config) = &config.arm7_overlays {
398 Self::load_overlays(&path.join(arm7_overlays_config), "arm7", None, &options)?
399 } else {
400 Default::default()
401 };
402
403 let arm7 = read_file(path.join(&config.arm7_bin))?;
405 let arm7_config = serde_yml::from_reader(open_file(path.join(&config.arm7_config))?)?;
406 let arm7 = Arm7::new(arm7, arm7_config);
407
408 let banner = if options.load_banner {
410 let banner_path = path.join(&config.banner);
411 let banner_dir = banner_path.parent().unwrap();
412 let mut banner: Banner = serde_yml::from_reader(open_file(&banner_path)?)?;
413 banner.images.load(banner_dir)?;
414 banner
415 } else {
416 Default::default()
417 };
418
419 let num_overlays = arm9_overlays.overlays().len() + arm7_overlays.overlays().len();
421 let (files, path_order) = if options.load_files {
422 log::info!("Loading ROM assets");
423 let files = FileSystem::load(path.join(&config.files_dir), num_overlays)?;
424 let path_order =
425 read_to_string(path.join(&config.path_order))?.trim().lines().map(|l| l.to_string()).collect::<Vec<_>>();
426 (files, path_order)
427 } else {
428 (FileSystem::new(num_overlays), vec![])
429 };
430
431 Ok(Self {
432 header,
433 header_logo,
434 arm9,
435 arm9_overlay_table: arm9_overlays,
436 arm7,
437 arm7_overlay_table: arm7_overlays,
438 banner,
439 files,
440 path_order,
441 config,
442 })
443 }
444
445 fn load_overlays(
446 config_path: &Path,
447 processor: &str,
448 hmac_sha1: Option<HmacSha1>,
449 options: &RomLoadOptions,
450 ) -> Result<OverlayTable<'a>, RomSaveError> {
451 let path = config_path.parent().unwrap();
452 let mut overlays = vec![];
453 let overlay_table_config: OverlayTableConfig = serde_yml::from_reader(open_file(config_path)?)?;
454 let num_overlays = overlay_table_config.overlays.len();
455 for mut config in overlay_table_config.overlays.into_iter() {
456 let data = read_file(path.join(config.file_name))?;
457 let compressed = config.info.compressed;
458 config.info.compressed = false;
459 let mut overlay = Overlay::new(data, OverlayOptions { info: config.info, originally_compressed: compressed })?;
460
461 if options.compress {
462 if compressed {
463 log::info!("Compressing {processor} overlay {}/{}", overlay.id(), num_overlays - 1);
464 overlay.compress()?;
465 }
466
467 if config.signed {
468 let Some(ref hmac_sha1) = hmac_sha1 else {
469 return NoHmacSha1KeySnafu {}.fail();
470 };
471 overlay.sign(hmac_sha1)?;
472 }
473 }
474
475 overlays.push(overlay);
476 }
477
478 let mut overlay_table = OverlayTable::new(overlays);
479 if overlay_table_config.table_signed {
480 let Some(ref hmac_sha1) = hmac_sha1 else {
481 return NoHmacSha1KeySnafu {}.fail();
482 };
483 if let Some(signature) = overlay_table_config.table_signature {
484 overlay_table.set_signature(signature);
485 } else {
486 overlay_table.sign(hmac_sha1);
487 }
488 }
489
490 Ok(overlay_table)
491 }
492
493 pub fn save<P: AsRef<Path>>(&self, path: P, key: Option<&BlowfishKey>) -> Result<(), RomSaveError> {
499 let path = path.as_ref();
500 create_dir_all(path)?;
501
502 log::info!("Saving ROM to directory {}", path.display());
503
504 serde_yml::to_writer(create_file_and_dirs(path.join("config.yaml"))?, &self.config)?;
506
507 serde_yml::to_writer(create_file_and_dirs(path.join(&self.config.header))?, &self.header)?;
509 self.header_logo.save_png(path.join(&self.config.header_logo))?;
510
511 let arm9_build_config = self.arm9_build_config()?;
513 serde_yml::to_writer(create_file_and_dirs(path.join(&self.config.arm9_config))?, &arm9_build_config)?;
514 let mut plain_arm9 = self.arm9.clone();
515 if plain_arm9.is_encrypted() {
516 let Some(key) = key else {
517 return BlowfishKeyNeededSnafu {}.fail();
518 };
519 log::info!("Decrypting ARM9 program");
520 plain_arm9.decrypt(key, self.header.original.gamecode.to_le_u32())?;
521 }
522 if plain_arm9.is_compressed()? {
523 log::info!("Decompressing ARM9 program");
524 plain_arm9.decompress()?;
525 }
526 create_file_and_dirs(path.join(&self.config.arm9_bin))?.write_all(plain_arm9.code()?)?;
527
528 if let Some(arm9_hmac_sha1_key) = plain_arm9.hmac_sha1_key()? {
530 if let Some(key_file) = &self.config.arm9_hmac_sha1_key {
531 create_file_and_dirs(path.join(key_file))?.write_all(arm9_hmac_sha1_key.as_ref())?;
532 }
533 } else if self.config.arm9_hmac_sha1_key.is_some() {
534 log::warn!("ARM9 HMAC-SHA1 key not found, but config requested it to be saved");
535 }
536
537 for autoload in plain_arm9.autoloads()?.iter() {
539 let (bin_path, config_path) = match autoload.kind() {
540 raw::AutoloadKind::Itcm => (path.join(&self.config.itcm.bin), path.join(&self.config.itcm.config)),
541 raw::AutoloadKind::Dtcm => (path.join(&self.config.dtcm.bin), path.join(&self.config.dtcm.config)),
542 raw::AutoloadKind::Unknown(index) => {
543 let unknown_autoload = self
544 .config
545 .unknown_autoloads
546 .iter()
547 .find(|autoload| autoload.index == index)
548 .ok_or_else(|| AutoloadNotFoundSnafu { index }.build())?;
549 (path.join(&unknown_autoload.files.bin), path.join(&unknown_autoload.files.config))
550 }
551 };
552 create_file_and_dirs(bin_path)?.write_all(autoload.code())?;
553 serde_yml::to_writer(create_file_and_dirs(config_path)?, autoload.info())?;
554 }
555
556 if let Some(arm9_overlays_config) = &self.config.arm9_overlays {
558 Self::save_overlays(&path.join(arm9_overlays_config), &self.arm9_overlay_table, "arm9")?;
559 }
560
561 create_file_and_dirs(path.join(&self.config.arm7_bin))?.write_all(self.arm7.full_data())?;
563 serde_yml::to_writer(create_file_and_dirs(path.join(&self.config.arm7_config))?, self.arm7.offsets())?;
564
565 if let Some(arm7_overlays_config) = &self.config.arm7_overlays {
567 Self::save_overlays(&path.join(arm7_overlays_config), &self.arm7_overlay_table, "arm7")?;
568 }
569
570 {
572 let banner_path = path.join(&self.config.banner);
573 let banner_dir = banner_path.parent().unwrap();
574 serde_yml::to_writer(create_file_and_dirs(&banner_path)?, &self.banner)?;
575 self.banner.images.save_bitmap_file(banner_dir)?;
576 }
577
578 {
580 log::info!("Saving ROM assets");
581 let files_path = path.join(&self.config.files_dir);
582 self.files.traverse_files(["/"], |file, path| {
583 let path = files_path.join(path);
584 create_dir_all(&path).expect("failed to create file directory");
586 create_file(path.join(file.name()))
587 .expect("failed to create file")
588 .write_all(file.contents())
589 .expect("failed to write file");
590 });
591 }
592 let mut path_order_file = create_file_and_dirs(path.join(&self.config.path_order))?;
593 for path in &self.path_order {
594 path_order_file.write_all(path.as_bytes())?;
595 path_order_file.write_all("\n".as_bytes())?;
596 }
597
598 Ok(())
599 }
600
601 pub fn arm9_build_config(&self) -> Result<Arm9BuildConfig, RomSaveError> {
603 Ok(Arm9BuildConfig {
604 offsets: *self.arm9.offsets(),
605 encrypted: self.arm9.is_encrypted(),
606 compressed: self.arm9.is_compressed()?,
607 build_info: (*self.arm9.build_info()?).into(),
608 })
609 }
610
611 fn save_overlays(config_path: &Path, overlay_table: &OverlayTable, processor: &str) -> Result<(), RomSaveError> {
612 let overlays = overlay_table.overlays();
613 if !overlays.is_empty() {
614 let overlays_path = config_path.parent().unwrap();
615 create_dir_all(overlays_path)?;
616
617 let mut configs = vec![];
618 for overlay in overlays {
619 let name = format!("ov{:03}", overlay.id());
620
621 let mut plain_overlay = overlay.clone();
622 configs.push(OverlayConfig {
623 info: plain_overlay.info().clone(),
624 file_name: format!("{name}.bin"),
625 signed: overlay.is_signed(),
626 });
627
628 if plain_overlay.is_compressed() {
629 log::info!("Decompressing {processor} overlay {}/{}", overlay.id(), overlays.len() - 1);
630 plain_overlay.decompress()?;
631 }
632 create_file(overlays_path.join(format!("{name}.bin")))?.write_all(plain_overlay.code())?;
633 }
634
635 let overlay_table_config = OverlayTableConfig {
636 table_signed: overlay_table.is_signed(),
637 table_signature: overlay_table.signature(),
638 overlays: configs,
639 };
640 serde_yml::to_writer(create_file(config_path)?, &overlay_table_config)?;
641 }
642 Ok(())
643 }
644
645 pub fn extract(rom: &'a raw::Rom) -> Result<Self, RomExtractError> {
651 let header = rom.header()?;
652 let fnt = rom.fnt()?;
653 let fat = rom.fat()?;
654 let banner = rom.banner()?;
655 let file_root = FileSystem::parse(&fnt, fat, rom)?;
656 let path_order = file_root.compute_path_order();
657
658 let arm9 = rom.arm9()?;
659 let mut decompressed_arm9 = arm9.clone();
660 decompressed_arm9.decompress()?;
661
662 let arm9_overlays = rom.arm9_overlay_table_with(&decompressed_arm9)?;
663 let arm9_overlays = OverlayTable::parse_arm9(arm9_overlays, rom, &decompressed_arm9)?;
664 let arm7_overlays = rom.arm7_overlay_table()?;
665 let arm7_overlays = OverlayTable::parse_arm7(arm7_overlays, rom)?;
666
667 let autoloads = decompressed_arm9.autoloads()?;
668 let unknown_autoloads = autoloads
669 .iter()
670 .filter_map(|autoload| {
671 let raw::AutoloadKind::Unknown(index) = autoload.kind() else {
672 return None;
673 };
674 Some(RomConfigUnknownAutoload {
675 index,
676 files: RomConfigAutoload {
677 bin: format!("arm9/unk_autoload_{index}.bin").into(),
678 config: format!("arm9/unk_autoload_{index}.yaml").into(),
679 },
680 })
681 })
682 .collect();
683
684 let has_arm9_hmac_sha1 = decompressed_arm9.hmac_sha1_key()?.is_some();
685
686 let alignment = rom.alignments()?;
687
688 let config = RomConfig {
689 padding_value: rom.padding_value()?,
690 header: "header.yaml".into(),
691 header_logo: "header_logo.png".into(),
692 arm9_bin: "arm9/arm9.bin".into(),
693 arm9_config: "arm9/arm9.yaml".into(),
694 arm7_bin: "arm7/arm7.bin".into(),
695 arm7_config: "arm7/arm7.yaml".into(),
696 itcm: RomConfigAutoload { bin: "arm9/itcm.bin".into(), config: "arm9/itcm.yaml".into() },
697 unknown_autoloads,
698 dtcm: RomConfigAutoload { bin: "arm9/dtcm.bin".into(), config: "arm9/dtcm.yaml".into() },
699 arm9_overlays: if arm9_overlays.is_empty() { None } else { Some("arm9_overlays/overlays.yaml".into()) },
700 arm7_overlays: if arm7_overlays.is_empty() { None } else { Some("arm7_overlays/overlays.yaml".into()) },
701 banner: "banner/banner.yaml".into(),
702 files_dir: "files/".into(),
703 path_order: "path_order.txt".into(),
704 arm9_hmac_sha1_key: has_arm9_hmac_sha1.then_some("arm9/hmac_sha1_key.bin".into()),
705 alignment,
706 };
707
708 Ok(Self {
709 header: Header::load_raw(header),
710 header_logo: Logo::decompress(&header.logo)?,
711 arm9,
712 arm9_overlay_table: arm9_overlays,
713 arm7: rom.arm7()?,
714 arm7_overlay_table: arm7_overlays,
715 banner: Banner::load_raw(&banner),
716 files: file_root,
717 path_order,
718 config,
719 })
720 }
721
722 pub fn build(mut self, key: Option<&BlowfishKey>) -> Result<raw::Rom<'a>, RomBuildError> {
728 let mut context = BuildContext { blowfish_key: key, ..Default::default() };
729
730 let mut cursor = Cursor::new(Vec::with_capacity(128 * 1024)); context.header_offset = Some(cursor.position() as u32);
734 cursor.write_all(&[0u8; size_of::<raw::Header>()])?;
735
736 self.align(&mut cursor, self.config.alignment.arm9)?;
738 context.arm9_offset = Some(cursor.position() as u32);
739 context.arm9_autoload_callback = Some(self.arm9.autoload_callback());
740 context.arm9_build_info_offset = Some(self.arm9.build_info_offset());
741 cursor.write_all(self.arm9.full_data())?;
742 let footer = Arm9Footer::new(self.arm9.build_info_offset(), self.arm9.overlay_signatures_offset());
743 cursor.write_all(bytemuck::bytes_of(&footer))?;
744
745 let max_file_id = self.files.max_file_id();
746 let mut file_allocs = vec![FileAlloc::default(); max_file_id as usize + 1];
747
748 if !self.arm9_overlay_table.is_empty() {
749 self.align(&mut cursor, self.config.alignment.arm9_overlay_table)?;
751 context.arm9_ovt_offset = Some(TableOffset {
752 offset: cursor.position() as u32,
753 size: (self.arm9_overlay_table.len() * size_of::<raw::Overlay>()) as u32,
754 });
755 let raw_table = self.arm9_overlay_table.build();
756 cursor.write_all(raw_table.as_bytes())?;
757
758 for overlay in self.arm9_overlay_table.overlays() {
760 self.align(&mut cursor, self.config.alignment.arm9_overlay)?;
761 let start = cursor.position() as u32;
762 let end = start + overlay.full_data().len() as u32;
763 file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
764
765 cursor.write_all(overlay.full_data())?;
766 }
767 }
768
769 self.align(&mut cursor, self.config.alignment.arm7)?;
771 context.arm7_offset = Some(cursor.position() as u32);
772 context.arm7_autoload_callback = Some(self.arm7.autoload_callback());
773 context.arm7_build_info_offset = None;
774 cursor.write_all(self.arm7.full_data())?;
775
776 if !self.arm7_overlay_table.is_empty() {
777 self.align(&mut cursor, self.config.alignment.arm7_overlay_table)?;
779 context.arm7_ovt_offset = Some(TableOffset {
780 offset: cursor.position() as u32,
781 size: (self.arm7_overlay_table.len() * size_of::<raw::Overlay>()) as u32,
782 });
783 let raw_table = self.arm7_overlay_table.build();
784 cursor.write_all(raw_table.as_bytes())?;
785
786 for overlay in self.arm7_overlay_table.overlays() {
788 self.align(&mut cursor, self.config.alignment.arm7_overlay)?;
789 let start = cursor.position() as u32;
790 let end = start + overlay.full_data().len() as u32;
791 file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
792
793 cursor.write_all(overlay.full_data())?;
794 }
795 }
796
797 self.align(&mut cursor, self.config.alignment.file_name_table)?;
799 self.files.sort_for_fnt();
800 let fnt = self.files.build_fnt()?.build()?;
801 context.fnt_offset = Some(TableOffset { offset: cursor.position() as u32, size: fnt.len() as u32 });
802 cursor.write_all(&fnt)?;
803
804 self.align(&mut cursor, self.config.alignment.file_allocation_table)?;
806 context.fat_offset =
807 Some(TableOffset { offset: cursor.position() as u32, size: (file_allocs.len() * size_of::<FileAlloc>()) as u32 });
808 cursor.write_all(bytemuck::cast_slice(&file_allocs))?;
809
810 self.align(&mut cursor, self.config.alignment.banner)?;
812 let banner = self.banner.build()?;
813 context.banner_offset = Some(TableOffset { offset: cursor.position() as u32, size: banner.full_data().len() as u32 });
814 cursor.write_all(banner.full_data())?;
815
816 self.align(&mut cursor, self.config.alignment.file_image_block)?;
818 self.files.sort_for_rom();
819 self.files.traverse_files(self.path_order.iter().map(|s| s.as_str()), |file, _| {
820 self.align(&mut cursor, self.config.alignment.file).expect("failed to align after file");
822
823 let contents = file.contents();
824 let start = cursor.position() as u32;
825 let end = start + contents.len() as u32;
826 file_allocs[file.id() as usize] = FileAlloc { start, end };
827
828 cursor.write_all(contents).expect("failed to write file contents");
829 });
830
831 context.rom_size = Some(cursor.position() as u32);
833 let padded_rom_size = cursor.position().next_power_of_two().max(128 * 1024) as u32;
834 self.align(&mut cursor, padded_rom_size)?;
835
836 cursor.set_position(context.fat_offset.unwrap().offset as u64);
838 cursor.write_all(bytemuck::cast_slice(&file_allocs))?;
839
840 cursor.set_position(context.header_offset.unwrap() as u64);
842 let header = self.header.build(&context, &self)?;
843 cursor.write_all(bytemuck::bytes_of(&header))?;
844
845 Ok(raw::Rom::new(cursor.into_inner()))
846 }
847
848 fn align(&self, cursor: &mut Cursor<Vec<u8>>, alignment: u32) -> Result<(), RomBuildError> {
849 assert!(alignment.is_power_of_two(), "alignment must be a power of two");
850 let mask = alignment - 1;
851 let padding = (!cursor.position() as u32 + 1) & mask;
852 for _ in 0..padding {
853 cursor.write_all(&[self.config.padding_value])?;
854 }
855 Ok(())
856 }
857
858 pub fn header_logo(&self) -> &Logo {
860 &self.header_logo
861 }
862
863 pub fn arm9(&self) -> &Arm9 {
865 &self.arm9
866 }
867
868 pub fn arm9_overlay_table(&self) -> &OverlayTable {
870 &self.arm9_overlay_table
871 }
872
873 pub fn arm9_overlays(&self) -> &[Overlay] {
875 self.arm9_overlay_table.overlays()
876 }
877
878 pub fn arm7(&self) -> &Arm7 {
880 &self.arm7
881 }
882
883 pub fn arm7_overlay_table(&self) -> &OverlayTable {
885 &self.arm7_overlay_table
886 }
887
888 pub fn arm7_overlays(&self) -> &[Overlay] {
890 self.arm7_overlay_table.overlays()
891 }
892
893 pub fn header(&self) -> &Header {
895 &self.header
896 }
897
898 pub fn config(&self) -> &RomConfig {
900 &self.config
901 }
902}
903
904#[derive(Default)]
906pub struct BuildContext<'a> {
907 pub header_offset: Option<u32>,
909 pub arm9_offset: Option<u32>,
911 pub arm7_offset: Option<u32>,
913 pub fnt_offset: Option<TableOffset>,
915 pub fat_offset: Option<TableOffset>,
917 pub arm9_ovt_offset: Option<TableOffset>,
919 pub arm7_ovt_offset: Option<TableOffset>,
921 pub banner_offset: Option<TableOffset>,
923 pub blowfish_key: Option<&'a BlowfishKey>,
925 pub arm9_autoload_callback: Option<u32>,
927 pub arm7_autoload_callback: Option<u32>,
929 pub arm9_build_info_offset: Option<u32>,
931 pub arm7_build_info_offset: Option<u32>,
933 pub rom_size: Option<u32>,
935}
936
937pub struct RomLoadOptions<'a> {
939 pub key: Option<&'a BlowfishKey>,
941 pub compress: bool,
943 pub encrypt: bool,
945 pub load_files: bool,
947 pub load_header: bool,
949 pub load_banner: bool,
951}
952
953impl Default for RomLoadOptions<'_> {
954 fn default() -> Self {
955 Self { key: None, compress: true, encrypt: true, load_files: true, load_header: true, load_banner: true }
956 }
957}