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
26pub 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#[derive(Debug, Snafu)]
42pub enum RomExtractError {
43 #[snafu(transparent)]
45 RawHeader {
46 source: RawHeaderError,
48 },
49 #[snafu(transparent)]
51 Logo {
52 source: LogoError,
54 },
55 #[snafu(transparent)]
57 RawOverlay {
58 source: RawOverlayError,
60 },
61 #[snafu(transparent)]
63 RawFnt {
64 source: RawFntError,
66 },
67 #[snafu(transparent)]
69 RawFat {
70 source: RawFatError,
72 },
73 #[snafu(transparent)]
75 RawBanner {
76 source: RawBannerError,
78 },
79 #[snafu(transparent)]
81 FileParse {
82 source: FileParseError,
84 },
85 #[snafu(transparent)]
87 RawArm9 {
88 source: RawArm9Error,
90 },
91 #[snafu(transparent)]
93 Arm9Autoload {
94 source: Arm9AutoloadError,
96 },
97 #[snafu(transparent)]
99 RawBuildInfo {
100 source: RawBuildInfoError,
102 },
103 #[snafu(transparent)]
105 Arm9 {
106 source: Arm9Error,
108 },
109}
110
111#[derive(Snafu, Debug)]
113pub enum RomBuildError {
114 #[snafu(transparent)]
116 Io {
117 source: io::Error,
119 },
120 #[snafu(transparent)]
122 FileBuild {
123 source: FileBuildError,
125 },
126 #[snafu(transparent)]
128 Banner {
129 source: BannerError,
131 },
132 #[snafu(transparent)]
134 HeaderBuild {
135 source: HeaderBuildError,
137 },
138}
139
140#[derive(Snafu, Debug)]
142pub enum RomSaveError {
143 #[snafu(display("blowfish key is required because ARM9 program is encrypted"))]
145 BlowfishKeyNeeded,
146 #[snafu(transparent)]
148 Io {
149 source: io::Error,
151 },
152 #[snafu(transparent)]
154 File {
155 source: FileError,
157 },
158 #[snafu(transparent)]
160 SerdeJson {
161 source: serde_yml::Error,
163 },
164 #[snafu(transparent)]
166 LogoSave {
167 source: LogoSaveError,
169 },
170 #[snafu(transparent)]
172 LogoLoad {
173 source: LogoLoadError,
175 },
176 #[snafu(transparent)]
178 RawBuildInfo {
179 source: RawBuildInfoError,
181 },
182 #[snafu(transparent)]
184 Arm9 {
185 source: Arm9Error,
187 },
188 #[snafu(transparent)]
190 Arm9Autoload {
191 source: Arm9AutoloadError,
193 },
194 #[snafu(transparent)]
196 BannerImage {
197 source: BannerImageError,
199 },
200 #[snafu(transparent)]
202 Lz77Decompress {
203 source: Lz77DecompressError,
205 },
206}
207
208#[derive(Serialize, Deserialize)]
210pub struct Arm9BuildConfig {
211 #[serde(flatten)]
213 pub offsets: Arm9Offsets,
214 pub encrypted: bool,
216 pub compressed: bool,
218 #[serde(flatten)]
220 pub build_info: BuildInfo,
221}
222
223#[derive(Serialize, Deserialize)]
225pub struct OverlayConfig {
226 #[serde(flatten)]
228 pub info: OverlayInfo,
229 pub file_name: String,
231}
232
233impl<'a> Rom<'a> {
234 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 let (header, header_logo) = if options.load_header {
248 let header: Header = serde_yml::from_reader(open_file(path.join(&config.header))?)?;
249 let header_logo = Logo::from_png(path.join(&config.header_logo))?;
250 (header, header_logo)
251 } else {
252 Default::default()
253 };
254
255 let arm9_build_config: Arm9BuildConfig = serde_yml::from_reader(open_file(path.join(&config.arm9_config))?)?;
257 let arm9 = read_file(path.join(&config.arm9_bin))?;
258
259 let mut autoloads = vec![];
261
262 let itcm = read_file(path.join(&config.itcm.bin))?;
263 let itcm_info = serde_yml::from_reader(open_file(path.join(&config.itcm.config))?)?;
264 let itcm = Autoload::new(itcm, itcm_info);
265 autoloads.push(itcm);
266
267 let dtcm = read_file(path.join(&config.dtcm.bin))?;
268 let dtcm_info = serde_yml::from_reader(open_file(path.join(&config.dtcm.config))?)?;
269 let dtcm = Autoload::new(dtcm, dtcm_info);
270 autoloads.push(dtcm);
271
272 for unknown_autoload in &config.unknown_autoloads {
273 let autoload = read_file(path.join(&unknown_autoload.bin))?;
274 let autoload_info = serde_yml::from_reader(open_file(path.join(&unknown_autoload.config))?)?;
275 let autoload = Autoload::new(autoload, autoload_info);
276 autoloads.push(autoload);
277 }
278
279 let mut arm9 = Arm9::with_autoloads(
281 arm9,
282 &autoloads,
283 arm9_build_config.offsets,
284 Arm9WithTcmsOptions {
285 originally_compressed: arm9_build_config.compressed,
286 originally_encrypted: arm9_build_config.encrypted,
287 },
288 )?;
289 arm9_build_config.build_info.assign_to_raw(arm9.build_info_mut()?);
290 if arm9_build_config.compressed && options.compress {
291 log::info!("Compressing ARM9 program");
292 arm9.compress()?;
293 }
294 if arm9_build_config.encrypted && options.encrypt {
295 let Some(key) = options.key else {
296 return BlowfishKeyNeededSnafu {}.fail();
297 };
298 log::info!("Encrypting ARM9 program");
299 arm9.encrypt(key, header.original.gamecode.to_le_u32())?;
300 }
301
302 let arm9_overlays = if let Some(arm9_overlays_config) = &config.arm9_overlays {
304 Self::load_overlays(&path.join(arm9_overlays_config), "arm9", &options)?
305 } else {
306 vec![]
307 };
308
309 let arm7 = read_file(path.join(&config.arm7_bin))?;
311 let arm7_config = serde_yml::from_reader(open_file(path.join(&config.arm7_config))?)?;
312 let arm7 = Arm7::new(arm7, arm7_config);
313
314 let arm7_overlays = if let Some(arm7_overlays_config) = &config.arm7_overlays {
316 Self::load_overlays(&path.join(arm7_overlays_config), "arm7", &options)?
317 } else {
318 vec![]
319 };
320
321 let banner = if options.load_banner {
323 let banner_path = path.join(&config.banner);
324 let banner_dir = banner_path.parent().unwrap();
325 let mut banner: Banner = serde_yml::from_reader(open_file(&banner_path)?)?;
326 banner.images.load(banner_dir)?;
327 banner
328 } else {
329 Default::default()
330 };
331
332 let num_overlays = arm9_overlays.len() + arm7_overlays.len();
334 let (files, path_order) = if options.load_files {
335 log::info!("Loading ROM assets");
336 let files = FileSystem::load(path.join(&config.files_dir), num_overlays)?;
337 let path_order =
338 read_to_string(path.join(&config.path_order))?.trim().lines().map(|l| l.to_string()).collect::<Vec<_>>();
339 (files, path_order)
340 } else {
341 (FileSystem::new(num_overlays), vec![])
342 };
343
344 Ok(Self { header, header_logo, arm9, arm9_overlays, arm7, arm7_overlays, banner, files, path_order, config })
345 }
346
347 fn load_overlays(config_path: &Path, processor: &str, options: &RomLoadOptions) -> Result<Vec<Overlay<'a>>, RomSaveError> {
348 let path = config_path.parent().unwrap();
349 let mut overlays = vec![];
350 let overlay_configs: Vec<OverlayConfig> = serde_yml::from_reader(open_file(config_path)?)?;
351 let num_overlays = overlay_configs.len();
352 for mut config in overlay_configs.into_iter() {
353 let data = read_file(path.join(config.file_name))?;
354 let compressed = config.info.compressed;
355 config.info.compressed = false;
356 let mut overlay = Overlay::new(data, config.info, compressed);
357 if compressed && options.compress {
358 log::info!("Compressing {processor} overlay {}/{}", overlay.id(), num_overlays - 1);
359 overlay.compress()?;
360 }
361 overlays.push(overlay);
362 }
363 Ok(overlays)
364 }
365
366 pub fn save<P: AsRef<Path>>(&self, path: P, key: Option<&BlowfishKey>) -> Result<(), RomSaveError> {
372 let path = path.as_ref();
373 create_dir_all(path)?;
374
375 log::info!("Saving ROM to directory {}", path.display());
376
377 serde_yml::to_writer(create_file_and_dirs(path.join("config.yaml"))?, &self.config)?;
379
380 serde_yml::to_writer(create_file_and_dirs(path.join(&self.config.header))?, &self.header)?;
382 self.header_logo.save_png(path.join(&self.config.header_logo))?;
383
384 let arm9_build_config = self.arm9_build_config()?;
386 serde_yml::to_writer(create_file_and_dirs(path.join(&self.config.arm9_config))?, &arm9_build_config)?;
387 let mut plain_arm9 = self.arm9.clone();
388 if plain_arm9.is_encrypted() {
389 let Some(key) = key else {
390 return BlowfishKeyNeededSnafu {}.fail();
391 };
392 log::info!("Decrypting ARM9 program");
393 plain_arm9.decrypt(key, self.header.original.gamecode.to_le_u32())?;
394 }
395 if plain_arm9.is_compressed()? {
396 log::info!("Decompressing ARM9 program");
397 plain_arm9.decompress()?;
398 }
399 create_file_and_dirs(path.join(&self.config.arm9_bin))?.write_all(plain_arm9.code()?)?;
400
401 let mut unknown_autoloads = self.config.unknown_autoloads.iter();
403 for autoload in plain_arm9.autoloads()?.iter() {
404 let (bin_path, config_path) = match autoload.kind() {
405 raw::AutoloadKind::Itcm => (path.join(&self.config.itcm.bin), path.join(&self.config.itcm.config)),
406 raw::AutoloadKind::Dtcm => (path.join(&self.config.dtcm.bin), path.join(&self.config.dtcm.config)),
407 raw::AutoloadKind::Unknown(_) => {
408 let unknown_autoload = unknown_autoloads.next().expect("no more autoloads in config, was it removed?");
409 (path.join(&unknown_autoload.bin), path.join(&unknown_autoload.config))
410 }
411 };
412 create_file_and_dirs(bin_path)?.write_all(autoload.code())?;
413 serde_yml::to_writer(create_file_and_dirs(config_path)?, autoload.info())?;
414 }
415
416 if let Some(arm9_overlays_config) = &self.config.arm9_overlays {
418 Self::save_overlays(&path.join(arm9_overlays_config), &self.arm9_overlays, "arm9")?;
419 }
420
421 create_file_and_dirs(path.join(&self.config.arm7_bin))?.write_all(self.arm7.full_data())?;
423 serde_yml::to_writer(create_file_and_dirs(path.join(&self.config.arm7_config))?, self.arm7.offsets())?;
424
425 if let Some(arm7_overlays_config) = &self.config.arm7_overlays {
427 Self::save_overlays(&path.join(arm7_overlays_config), &self.arm7_overlays, "arm7")?;
428 }
429
430 {
432 let banner_path = path.join(&self.config.banner);
433 let banner_dir = banner_path.parent().unwrap();
434 serde_yml::to_writer(create_file_and_dirs(&banner_path)?, &self.banner)?;
435 self.banner.images.save_bitmap_file(banner_dir)?;
436 }
437
438 {
440 log::info!("Saving ROM assets");
441 let files_path = path.join(&self.config.files_dir);
442 self.files.traverse_files(["/"], |file, path| {
443 let path = files_path.join(path);
444 create_dir_all(&path).expect("failed to create file directory");
446 create_file(path.join(file.name()))
447 .expect("failed to create file")
448 .write_all(file.contents())
449 .expect("failed to write file");
450 });
451 }
452 let mut path_order_file = create_file_and_dirs(path.join(&self.config.path_order))?;
453 for path in &self.path_order {
454 path_order_file.write_all(path.as_bytes())?;
455 path_order_file.write_all("\n".as_bytes())?;
456 }
457
458 Ok(())
459 }
460
461 pub fn arm9_build_config(&self) -> Result<Arm9BuildConfig, RomSaveError> {
463 Ok(Arm9BuildConfig {
464 offsets: *self.arm9.offsets(),
465 encrypted: self.arm9.is_encrypted(),
466 compressed: self.arm9.is_compressed()?,
467 build_info: (*self.arm9.build_info()?).into(),
468 })
469 }
470
471 fn save_overlays(config_path: &Path, overlays: &[Overlay], processor: &str) -> Result<(), RomSaveError> {
472 if !overlays.is_empty() {
473 let overlays_path = config_path.parent().unwrap();
474 create_dir_all(overlays_path)?;
475
476 let mut configs = vec![];
477 for overlay in overlays {
478 let name = format!("ov{:03}", overlay.id());
479
480 let mut plain_overlay = overlay.clone();
481 configs.push(OverlayConfig { info: plain_overlay.info().clone(), file_name: format!("{name}.bin") });
482
483 if plain_overlay.is_compressed() {
484 log::info!("Decompressing {processor} overlay {}/{}", overlay.id(), overlays.len() - 1);
485 plain_overlay.decompress()?;
486 }
487 create_file(overlays_path.join(format!("{name}.bin")))?.write_all(plain_overlay.code())?;
488 }
489 serde_yml::to_writer(create_file(config_path)?, &configs)?;
490 }
491 Ok(())
492 }
493
494 pub fn extract(rom: &'a raw::Rom) -> Result<Self, RomExtractError> {
500 let header = rom.header()?;
501 let fnt = rom.fnt()?;
502 let fat = rom.fat()?;
503 let banner = rom.banner()?;
504 let file_root = FileSystem::parse(&fnt, fat, rom)?;
505 let path_order = file_root.compute_path_order();
506
507 let arm9_overlays =
508 rom.arm9_overlay_table()?.iter().map(|ov| Overlay::parse(ov, fat, rom)).collect::<Result<Vec<_>, _>>()?;
509 let arm7_overlays =
510 rom.arm7_overlay_table()?.iter().map(|ov| Overlay::parse(ov, fat, rom)).collect::<Result<Vec<_>, _>>()?;
511
512 let arm9 = rom.arm9()?;
513
514 let num_unknown_autoloads = if arm9.is_compressed()? {
515 let mut decompressed_arm9 = arm9.clone();
516 decompressed_arm9.decompress()?;
517 decompressed_arm9.num_unknown_autoloads()?
518 } else {
519 arm9.num_unknown_autoloads()?
520 };
521 let unknown_autoloads = (0..num_unknown_autoloads)
522 .map(|index| RomConfigAutoload {
523 bin: format!("arm9/unk_autoload_{index}.bin").into(),
524 config: format!("arm9/unk_autoload_{index}.yaml").into(),
525 })
526 .collect();
527
528 let config = RomConfig {
529 padding_value: rom.padding_value()?,
530 header: "header.yaml".into(),
531 header_logo: "header_logo.png".into(),
532 arm9_bin: "arm9/arm9.bin".into(),
533 arm9_config: "arm9/arm9.yaml".into(),
534 arm7_bin: "arm7/arm7.bin".into(),
535 arm7_config: "arm7/arm7.yaml".into(),
536 itcm: RomConfigAutoload { bin: "arm9/itcm.bin".into(), config: "arm9/itcm.yaml".into() },
537 unknown_autoloads,
538 dtcm: RomConfigAutoload { bin: "arm9/dtcm.bin".into(), config: "arm9/dtcm.yaml".into() },
539 arm9_overlays: if arm9_overlays.is_empty() { None } else { Some("arm9_overlays/overlays.yaml".into()) },
540 arm7_overlays: if arm7_overlays.is_empty() { None } else { Some("arm7_overlays/overlays.yaml".into()) },
541 banner: "banner/banner.yaml".into(),
542 files_dir: "files/".into(),
543 path_order: "path_order.txt".into(),
544 };
545
546 Ok(Self {
547 header: Header::load_raw(header),
548 header_logo: Logo::decompress(&header.logo)?,
549 arm9,
550 arm9_overlays,
551 arm7: rom.arm7()?,
552 arm7_overlays,
553 banner: Banner::load_raw(&banner),
554 files: file_root,
555 path_order,
556 config,
557 })
558 }
559
560 pub fn build(mut self, key: Option<&BlowfishKey>) -> Result<raw::Rom<'a>, RomBuildError> {
566 let mut context = BuildContext { blowfish_key: key, ..Default::default() };
567
568 let mut cursor = Cursor::new(Vec::with_capacity(128 * 1024)); context.header_offset = Some(cursor.position() as u32);
572 cursor.write_all(&[0u8; size_of::<raw::Header>()])?;
573 self.align(&mut cursor)?;
574
575 context.arm9_offset = Some(cursor.position() as u32);
577 context.arm9_autoload_callback = Some(self.arm9.autoload_callback());
578 context.arm9_build_info_offset = Some(self.arm9.build_info_offset());
579 cursor.write_all(self.arm9.full_data())?;
580 let footer = Arm9Footer::new(self.arm9.build_info_offset());
581 cursor.write_all(bytemuck::bytes_of(&footer))?;
582 self.align(&mut cursor)?;
583
584 let max_file_id = self.files.max_file_id();
585 let mut file_allocs = vec![FileAlloc::default(); max_file_id as usize + 1];
586
587 if !self.arm9_overlays.is_empty() {
588 context.arm9_ovt_offset = Some(TableOffset {
590 offset: cursor.position() as u32,
591 size: (self.arm9_overlays.len() * size_of::<raw::Overlay>()) as u32,
592 });
593 for overlay in &self.arm9_overlays {
594 let raw = overlay.build();
595 cursor.write_all(bytemuck::bytes_of(&raw))?;
596 }
597 self.align(&mut cursor)?;
598
599 for overlay in &self.arm9_overlays {
601 let start = cursor.position() as u32;
602 let end = start + overlay.full_data().len() as u32;
603 file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
604
605 cursor.write_all(overlay.full_data())?;
606 self.align(&mut cursor)?;
607 }
608 }
609
610 context.arm7_offset = Some(cursor.position() as u32);
612 context.arm7_autoload_callback = Some(self.arm7.autoload_callback());
613 context.arm7_build_info_offset = None;
614 cursor.write_all(self.arm7.full_data())?;
615 self.align(&mut cursor)?;
616
617 if !self.arm7_overlays.is_empty() {
618 context.arm7_ovt_offset = Some(TableOffset {
620 offset: cursor.position() as u32,
621 size: (self.arm7_overlays.len() * size_of::<raw::Overlay>()) as u32,
622 });
623 for overlay in &self.arm7_overlays {
624 let raw = overlay.build();
625 cursor.write_all(bytemuck::bytes_of(&raw))?;
626 }
627 self.align(&mut cursor)?;
628
629 for overlay in &self.arm7_overlays {
631 let start = cursor.position() as u32;
632 let end = start + overlay.full_data().len() as u32;
633 file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
634
635 cursor.write_all(overlay.full_data())?;
636 self.align(&mut cursor)?;
637 }
638 }
639
640 self.files.sort_for_fnt();
642 let fnt = self.files.build_fnt()?.build()?;
643 context.fnt_offset = Some(TableOffset { offset: cursor.position() as u32, size: fnt.len() as u32 });
644 cursor.write_all(&fnt)?;
645 self.align(&mut cursor)?;
646
647 context.fat_offset =
649 Some(TableOffset { offset: cursor.position() as u32, size: (file_allocs.len() * size_of::<FileAlloc>()) as u32 });
650 cursor.write_all(bytemuck::cast_slice(&file_allocs))?;
651 self.align(&mut cursor)?;
652
653 let banner = self.banner.build()?;
655 context.banner_offset = Some(TableOffset { offset: cursor.position() as u32, size: banner.full_data().len() as u32 });
656 cursor.write_all(banner.full_data())?;
657 self.align(&mut cursor)?;
658
659 self.files.sort_for_rom();
661 self.files.traverse_files(self.path_order.iter().map(|s| s.as_str()), |file, _| {
662 self.align(&mut cursor).expect("failed to align before file");
664
665 let contents = file.contents();
666 let start = cursor.position() as u32;
667 let end = start + contents.len() as u32;
668 file_allocs[file.id() as usize] = FileAlloc { start, end };
669
670 cursor.write_all(contents).expect("failed to write file contents");
671 });
672
673 context.rom_size = Some(cursor.position() as u32);
675 while !cursor.position().is_power_of_two() && cursor.position() >= 128 * 1024 {
676 cursor.write_all(&[self.config.padding_value])?;
677 }
678
679 cursor.set_position(context.fat_offset.unwrap().offset as u64);
681 cursor.write_all(bytemuck::cast_slice(&file_allocs))?;
682
683 cursor.set_position(context.header_offset.unwrap() as u64);
685 let header = self.header.build(&context, &self)?;
686 cursor.write_all(bytemuck::bytes_of(&header))?;
687
688 Ok(raw::Rom::new(cursor.into_inner()))
689 }
690
691 fn align(&self, cursor: &mut Cursor<Vec<u8>>) -> Result<(), RomBuildError> {
692 let padding = (!cursor.position() + 1) & 0x1ff;
693 for _ in 0..padding {
694 cursor.write_all(&[self.config.padding_value])?;
695 }
696 Ok(())
697 }
698
699 pub fn header_logo(&self) -> &Logo {
701 &self.header_logo
702 }
703
704 pub fn arm9(&self) -> &Arm9 {
706 &self.arm9
707 }
708
709 pub fn arm9_overlays(&self) -> &[Overlay] {
711 &self.arm9_overlays
712 }
713
714 pub fn arm7(&self) -> &Arm7 {
716 &self.arm7
717 }
718
719 pub fn arm7_overlays(&self) -> &[Overlay] {
721 &self.arm7_overlays
722 }
723
724 pub fn header(&self) -> &Header {
726 &self.header
727 }
728
729 pub fn config(&self) -> &RomConfig {
731 &self.config
732 }
733}
734
735#[derive(Default)]
737pub struct BuildContext<'a> {
738 pub header_offset: Option<u32>,
740 pub arm9_offset: Option<u32>,
742 pub arm7_offset: Option<u32>,
744 pub fnt_offset: Option<TableOffset>,
746 pub fat_offset: Option<TableOffset>,
748 pub arm9_ovt_offset: Option<TableOffset>,
750 pub arm7_ovt_offset: Option<TableOffset>,
752 pub banner_offset: Option<TableOffset>,
754 pub blowfish_key: Option<&'a BlowfishKey>,
756 pub arm9_autoload_callback: Option<u32>,
758 pub arm7_autoload_callback: Option<u32>,
760 pub arm9_build_info_offset: Option<u32>,
762 pub arm7_build_info_offset: Option<u32>,
764 pub rom_size: Option<u32>,
766}
767
768pub struct RomLoadOptions<'a> {
770 pub key: Option<&'a BlowfishKey>,
772 pub compress: bool,
774 pub encrypt: bool,
776 pub load_files: bool,
778 pub load_header: bool,
780 pub load_banner: bool,
782}
783
784impl Default for RomLoadOptions<'_> {
785 fn default() -> Self {
786 Self { key: None, compress: true, encrypt: true, load_files: true, load_header: true, load_banner: true }
787 }
788}