use std::{
backtrace::Backtrace,
io::{self, Cursor, Write},
mem::size_of,
path::Path,
};
use serde::{Deserialize, Serialize};
use snafu::Snafu;
use super::{
raw::{
self, Arm9Footer, HmacSha1Signature, RawArm9Error, RawBannerError, RawBuildInfoError, RawFatError, RawFntError,
RawHeaderError, RawOverlayError, RomAlignmentsError, TableOffset,
},
Arm7, Arm9, Arm9AutoloadError, Arm9Error, Arm9HmacSha1KeyError, Arm9Offsets, Arm9OverlaySignaturesError, Autoload, Banner,
BannerError, BannerImageError, BuildInfo, FileBuildError, FileParseError, FileSystem, Header, HeaderBuildError, Logo,
LogoError, LogoLoadError, LogoSaveError, Overlay, OverlayError, OverlayInfo, OverlayOptions, OverlayTable,
RomConfigAutoload, RomConfigUnknownAutoload,
};
use crate::{
compress::lz77::Lz77DecompressError,
crypto::{
blowfish::BlowfishKey,
hmac_sha1::{HmacSha1, HmacSha1FromBytesError},
},
io::{create_dir_all, create_file, create_file_and_dirs, open_file, read_file, read_to_string, FileError},
rom::{
raw::{FileAlloc, MultibootSignature, RawMultibootSignatureError},
Arm9WithTcmsOptions, RomConfig,
},
};
pub struct Rom<'a> {
header: Header,
header_logo: Logo,
arm9: Arm9<'a>,
arm9_overlay_table: OverlayTable<'a>,
arm7: Arm7<'a>,
arm7_overlay_table: OverlayTable<'a>,
banner: Banner,
files: FileSystem<'a>,
multiboot_signature: Option<MultibootSignature>,
path_order: Vec<String>,
config: RomConfig,
}
#[derive(Debug, Snafu)]
pub enum RomExtractError {
#[snafu(transparent)]
RawHeader {
source: RawHeaderError,
},
#[snafu(transparent)]
Logo {
source: LogoError,
},
#[snafu(transparent)]
RawOverlay {
source: RawOverlayError,
},
#[snafu(transparent)]
RawFnt {
source: RawFntError,
},
#[snafu(transparent)]
RawFat {
source: RawFatError,
},
#[snafu(transparent)]
RawBanner {
source: RawBannerError,
},
#[snafu(transparent)]
FileParse {
source: FileParseError,
},
#[snafu(transparent)]
RawArm9 {
source: RawArm9Error,
},
#[snafu(transparent)]
Arm9Autoload {
source: Arm9AutoloadError,
},
#[snafu(transparent)]
RawBuildInfo {
source: RawBuildInfoError,
},
#[snafu(transparent)]
Arm9 {
source: Arm9Error,
},
#[snafu(transparent)]
RomAlignments {
source: RomAlignmentsError,
},
#[snafu(transparent)]
Overlay {
source: OverlayError,
},
#[snafu(transparent)]
Arm9HmacSha1Key {
source: Arm9HmacSha1KeyError,
},
#[snafu(transparent)]
RawMultibootSignature {
source: RawMultibootSignatureError,
},
}
#[derive(Snafu, Debug)]
pub enum RomBuildError {
#[snafu(transparent)]
Io {
source: io::Error,
},
#[snafu(transparent)]
FileBuild {
source: FileBuildError,
},
#[snafu(transparent)]
Banner {
source: BannerError,
},
#[snafu(transparent)]
HeaderBuild {
source: HeaderBuildError,
},
}
#[derive(Snafu, Debug)]
pub enum RomSaveError {
#[snafu(display("blowfish key is required because ARM9 program is encrypted"))]
BlowfishKeyNeeded,
#[snafu(transparent)]
Io {
source: io::Error,
},
#[snafu(transparent)]
File {
source: FileError,
},
#[snafu(transparent)]
SerdeSaphyrDeserialize {
source: serde_saphyr::Error,
},
#[snafu(transparent)]
SerdeSaphyrSerialize {
source: serde_saphyr::ser_error::Error,
},
#[snafu(transparent)]
LogoSave {
source: LogoSaveError,
},
#[snafu(transparent)]
LogoLoad {
source: LogoLoadError,
},
#[snafu(transparent)]
RawBuildInfo {
source: RawBuildInfoError,
},
#[snafu(transparent)]
Arm9 {
source: Arm9Error,
},
#[snafu(transparent)]
Arm9Autoload {
source: Arm9AutoloadError,
},
#[snafu(transparent)]
BannerImage {
source: BannerImageError,
},
#[snafu(transparent)]
Lz77Decompress {
source: Lz77DecompressError,
},
#[snafu(transparent)]
Overlay {
source: OverlayError,
},
#[snafu(transparent)]
HmacSha1FromBytes {
source: HmacSha1FromBytesError,
},
#[snafu(transparent)]
Arm9HmacSha1Key {
source: Arm9HmacSha1KeyError,
},
#[snafu(transparent)]
Arm9OverlaySignatures {
source: Arm9OverlaySignaturesError,
},
#[snafu(display("HMAC-SHA1 key was not provided for a signed overlay:\n{backtrace}"))]
NoHmacSha1Key {
backtrace: Backtrace,
},
#[snafu(display("autoload index {index} not found in config:\n{backtrace}"))]
AutoloadNotFound {
index: u32,
backtrace: Backtrace,
},
}
#[derive(Serialize, Deserialize)]
pub struct Arm9BuildConfig {
#[serde(flatten)]
pub offsets: Arm9Offsets,
pub encrypted: bool,
pub compressed: bool,
#[serde(flatten)]
pub build_info: BuildInfo,
}
#[derive(Serialize, Deserialize)]
pub struct OverlayConfig {
#[serde(flatten)]
pub info: OverlayInfo,
pub signed: bool,
pub file_name: String,
}
#[derive(Serialize, Deserialize)]
pub struct OverlayTableConfig {
pub table_signed: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub table_signature: Option<HmacSha1Signature>,
pub overlays: Vec<OverlayConfig>,
}
impl<'a> Rom<'a> {
pub fn load<P: AsRef<Path>>(config_path: P, options: RomLoadOptions) -> Result<Self, RomSaveError> {
let config_path = config_path.as_ref();
log::info!("Loading ROM from {}", config_path.display());
let config: RomConfig = serde_saphyr::from_reader(open_file(config_path)?)?;
let path = config_path.parent().unwrap();
let (header, header_logo) = if options.load_header {
let header: Header = serde_saphyr::from_reader(open_file(path.join(&config.header))?)?;
let header_logo = Logo::from_png(path.join(&config.header_logo))?;
(header, header_logo)
} else {
Default::default()
};
let arm9_build_config: Arm9BuildConfig = serde_saphyr::from_reader(open_file(path.join(&config.arm9_config))?)?;
let arm9 = read_file(path.join(&config.arm9_bin))?;
let mut autoloads = vec![];
let itcm = read_file(path.join(&config.itcm.bin))?;
let itcm_info = serde_saphyr::from_reader(open_file(path.join(&config.itcm.config))?)?;
let itcm = Autoload::new(itcm, itcm_info);
autoloads.push(itcm);
let dtcm = read_file(path.join(&config.dtcm.bin))?;
let dtcm_info = serde_saphyr::from_reader(open_file(path.join(&config.dtcm.config))?)?;
let dtcm = Autoload::new(dtcm, dtcm_info);
autoloads.push(dtcm);
for unknown_autoload in &config.unknown_autoloads {
let autoload = read_file(path.join(&unknown_autoload.files.bin))?;
let autoload_info = serde_saphyr::from_reader(open_file(path.join(&unknown_autoload.files.config))?)?;
let autoload = Autoload::new(autoload, autoload_info);
autoloads.push(autoload);
}
autoloads.sort_by_key(|autoload| autoload.kind());
let arm9_hmac_sha1 = if let Some(hmac_sha1_key_file) = &config.arm9_hmac_sha1_key {
let hmac_sha1_key = read_file(path.join(hmac_sha1_key_file))?;
Some(HmacSha1::try_from(hmac_sha1_key.as_ref())?)
} else {
None
};
let arm9_overlays = if let Some(arm9_overlays_config) = &config.arm9_overlays {
Self::load_overlays(&path.join(arm9_overlays_config), "arm9", arm9_hmac_sha1, &options)?
} else {
Default::default()
};
let mut arm9 = Arm9::with_autoloads(arm9, &autoloads, arm9_build_config.offsets, Arm9WithTcmsOptions {
originally_compressed: arm9_build_config.compressed,
originally_encrypted: arm9_build_config.encrypted,
})?;
arm9_build_config.build_info.assign_to_raw(arm9.build_info_mut()?);
arm9.update_overlay_signatures(&arm9_overlays)?;
if arm9_build_config.compressed && options.compress {
log::info!("Compressing ARM9 program");
arm9.compress()?;
}
if arm9_build_config.encrypted && options.encrypt {
let Some(key) = options.key else {
return BlowfishKeyNeededSnafu {}.fail();
};
log::info!("Encrypting ARM9 program");
arm9.encrypt(key, header.original.gamecode.to_le_u32())?;
}
let arm7_overlays = if let Some(arm7_overlays_config) = &config.arm7_overlays {
Self::load_overlays(&path.join(arm7_overlays_config), "arm7", None, &options)?
} else {
Default::default()
};
let arm7 = read_file(path.join(&config.arm7_bin))?;
let arm7_config = serde_saphyr::from_reader(open_file(path.join(&config.arm7_config))?)?;
let arm7 = Arm7::new(arm7, arm7_config);
let banner = if options.load_banner {
let banner_path = path.join(&config.banner);
let banner_dir = banner_path.parent().unwrap();
let mut banner: Banner = serde_saphyr::from_reader(open_file(&banner_path)?)?;
banner.images.load(banner_dir)?;
banner
} else {
Default::default()
};
let num_overlays = arm9_overlays.overlays().len() + arm7_overlays.overlays().len();
let (files, path_order) = if options.load_files {
log::info!("Loading ROM assets");
let files = FileSystem::load(path.join(&config.files_dir), num_overlays)?;
let path_order =
read_to_string(path.join(&config.path_order))?.trim().lines().map(|l| l.to_string()).collect::<Vec<_>>();
(files, path_order)
} else {
(FileSystem::new(num_overlays), vec![])
};
let multiboot_signature = if let Some(multiboot_signature) = config.multiboot_signature.as_ref() {
serde_saphyr::from_reader(open_file(path.join(multiboot_signature))?)?
} else {
None
};
Ok(Self {
header,
header_logo,
arm9,
arm9_overlay_table: arm9_overlays,
arm7,
arm7_overlay_table: arm7_overlays,
banner,
files,
path_order,
multiboot_signature,
config,
})
}
fn load_overlays(
config_path: &Path,
processor: &str,
hmac_sha1: Option<HmacSha1>,
options: &RomLoadOptions,
) -> Result<OverlayTable<'a>, RomSaveError> {
let path = config_path.parent().unwrap();
let mut overlays = vec![];
let overlay_table_config: OverlayTableConfig = serde_saphyr::from_reader(open_file(config_path)?)?;
let num_overlays = overlay_table_config.overlays.len();
for mut config in overlay_table_config.overlays.into_iter() {
let data = read_file(path.join(config.file_name))?;
let compressed = config.info.compressed;
config.info.compressed = false;
let mut overlay = Overlay::new(data, OverlayOptions { info: config.info, originally_compressed: compressed })?;
if options.compress {
if compressed {
log::info!("Compressing {processor} overlay {}/{}", overlay.id(), num_overlays - 1);
overlay.compress()?;
}
if config.signed {
let Some(ref hmac_sha1) = hmac_sha1 else {
return NoHmacSha1KeySnafu {}.fail();
};
overlay.sign(hmac_sha1)?;
}
}
overlays.push(overlay);
}
let mut overlay_table = OverlayTable::new(overlays);
if overlay_table_config.table_signed {
if let Some(signature) = overlay_table_config.table_signature {
overlay_table.set_signature(signature);
} else {
let Some(ref hmac_sha1) = hmac_sha1 else {
return NoHmacSha1KeySnafu {}.fail();
};
overlay_table.sign(hmac_sha1);
}
}
Ok(overlay_table)
}
pub fn save<P: AsRef<Path>>(&self, path: P, key: Option<&BlowfishKey>) -> Result<(), RomSaveError> {
let path = path.as_ref();
create_dir_all(path)?;
log::info!("Saving ROM to directory {}", path.display());
serde_saphyr::to_io_writer(&mut create_file_and_dirs(path.join("config.yaml"))?, &self.config)?;
serde_saphyr::to_io_writer(&mut create_file_and_dirs(path.join(&self.config.header))?, &self.header)?;
self.header_logo.save_png(path.join(&self.config.header_logo))?;
let arm9_build_config = self.arm9_build_config()?;
serde_saphyr::to_io_writer(&mut create_file_and_dirs(path.join(&self.config.arm9_config))?, &arm9_build_config)?;
let mut plain_arm9 = self.arm9.clone();
if plain_arm9.is_encrypted() {
let Some(key) = key else {
return BlowfishKeyNeededSnafu {}.fail();
};
log::info!("Decrypting ARM9 program");
plain_arm9.decrypt(key, self.header.original.gamecode.to_le_u32())?;
}
if plain_arm9.is_compressed()? {
log::info!("Decompressing ARM9 program");
plain_arm9.decompress()?;
}
create_file_and_dirs(path.join(&self.config.arm9_bin))?.write_all(plain_arm9.code()?)?;
if let Some(arm9_hmac_sha1_key) = plain_arm9.hmac_sha1_key()? {
if let Some(key_file) = &self.config.arm9_hmac_sha1_key {
create_file_and_dirs(path.join(key_file))?.write_all(arm9_hmac_sha1_key.as_ref())?;
}
} else if self.config.arm9_hmac_sha1_key.is_some() {
log::warn!("ARM9 HMAC-SHA1 key not found, but config requested it to be saved");
}
for autoload in plain_arm9.autoloads()?.iter() {
let (bin_path, config_path) = match autoload.kind() {
raw::AutoloadKind::Itcm => (path.join(&self.config.itcm.bin), path.join(&self.config.itcm.config)),
raw::AutoloadKind::Dtcm => (path.join(&self.config.dtcm.bin), path.join(&self.config.dtcm.config)),
raw::AutoloadKind::Unknown(index) => {
let unknown_autoload = self
.config
.unknown_autoloads
.iter()
.find(|autoload| autoload.index == index)
.ok_or_else(|| AutoloadNotFoundSnafu { index }.build())?;
(path.join(&unknown_autoload.files.bin), path.join(&unknown_autoload.files.config))
}
};
create_file_and_dirs(bin_path)?.write_all(autoload.code())?;
serde_saphyr::to_io_writer(&mut create_file_and_dirs(config_path)?, autoload.info())?;
}
if let Some(arm9_overlays_config) = &self.config.arm9_overlays {
Self::save_overlays(&path.join(arm9_overlays_config), &self.arm9_overlay_table, "arm9")?;
}
create_file_and_dirs(path.join(&self.config.arm7_bin))?.write_all(self.arm7.full_data())?;
serde_saphyr::to_io_writer(&mut create_file_and_dirs(path.join(&self.config.arm7_config))?, self.arm7.offsets())?;
if let Some(arm7_overlays_config) = &self.config.arm7_overlays {
Self::save_overlays(&path.join(arm7_overlays_config), &self.arm7_overlay_table, "arm7")?;
}
{
let banner_path = path.join(&self.config.banner);
let banner_dir = banner_path.parent().unwrap();
serde_saphyr::to_io_writer(&mut create_file_and_dirs(&banner_path)?, &self.banner)?;
self.banner.images.save_bitmap_file(banner_dir)?;
}
{
log::info!("Saving ROM assets");
let files_path = path.join(&self.config.files_dir);
self.files.traverse_files(["/"], |file, path| {
let path = files_path.join(path);
create_dir_all(&path).expect("failed to create file directory");
create_file(path.join(file.name()))
.expect("failed to create file")
.write_all(file.contents())
.expect("failed to write file");
});
}
let mut path_order_file = create_file_and_dirs(path.join(&self.config.path_order))?;
for path in &self.path_order {
path_order_file.write_all(path.as_bytes())?;
path_order_file.write_all("\n".as_bytes())?;
}
if let Some(multiboot_signature) = &self.multiboot_signature {
if let Some(signature_file) = &self.config.multiboot_signature {
let file_path = path.join(signature_file);
serde_saphyr::to_io_writer(&mut create_file_and_dirs(&file_path)?, multiboot_signature)?;
}
} else if self.config.multiboot_signature.is_some() {
log::warn!("Multiboot signature not found, but config requested it to be saved");
}
Ok(())
}
pub fn arm9_build_config(&self) -> Result<Arm9BuildConfig, RomSaveError> {
Ok(Arm9BuildConfig {
offsets: *self.arm9.offsets(),
encrypted: self.arm9.is_encrypted(),
compressed: self.arm9.is_compressed()?,
build_info: (*self.arm9.build_info()?).into(),
})
}
fn save_overlays(config_path: &Path, overlay_table: &OverlayTable, processor: &str) -> Result<(), RomSaveError> {
let overlays = overlay_table.overlays();
if !overlays.is_empty() {
let overlays_path = config_path.parent().unwrap();
create_dir_all(overlays_path)?;
let mut configs = vec![];
for overlay in overlays {
let name = format!("ov{:03}", overlay.id());
let mut plain_overlay = overlay.clone();
configs.push(OverlayConfig {
info: plain_overlay.info().clone(),
file_name: format!("{name}.bin"),
signed: overlay.is_signed(),
});
if plain_overlay.is_compressed() {
log::info!("Decompressing {processor} overlay {}/{}", overlay.id(), overlays.len() - 1);
plain_overlay.decompress()?;
}
create_file(overlays_path.join(format!("{name}.bin")))?.write_all(plain_overlay.code())?;
}
let overlay_table_config = OverlayTableConfig {
table_signed: overlay_table.is_signed(),
table_signature: overlay_table.signature(),
overlays: configs,
};
serde_saphyr::to_io_writer(&mut create_file_and_dirs(config_path)?, &overlay_table_config)?;
}
Ok(())
}
pub fn extract(rom: &'a raw::Rom) -> Result<Self, RomExtractError> {
let header = rom.header()?;
log::info!("Extracting from {}", header.title);
let fnt = rom.fnt()?;
let fat = rom.fat()?;
let banner = rom.banner()?;
let file_root = FileSystem::parse(&fnt, fat, rom)?;
let path_order = file_root.compute_path_order();
let arm9 = rom.arm9()?;
let mut decompressed_arm9 = arm9.clone();
decompressed_arm9.decompress()?;
let arm9_overlays = rom.arm9_overlay_table_with(&decompressed_arm9)?;
let arm9_overlays = OverlayTable::parse_arm9(arm9_overlays, rom, &decompressed_arm9)?;
let arm7_overlays = rom.arm7_overlay_table()?;
let arm7_overlays = OverlayTable::parse_arm7(arm7_overlays, rom)?;
let autoloads = decompressed_arm9.autoloads()?;
let unknown_autoloads = autoloads
.iter()
.filter_map(|autoload| {
let raw::AutoloadKind::Unknown(index) = autoload.kind() else {
return None;
};
Some(RomConfigUnknownAutoload {
index,
files: RomConfigAutoload {
bin: format!("arm9/unk_autoload_{index}.bin").into(),
config: format!("arm9/unk_autoload_{index}.yaml").into(),
},
})
})
.collect();
let has_arm9_hmac_sha1 = decompressed_arm9.hmac_sha1_key()?.is_some();
let multiboot_signature = rom.multiboot_signature()?.cloned();
let alignment = rom.alignments()?;
let config = RomConfig {
file_image_padding_value: rom.file_image_padding_value()?,
section_padding_value: rom.section_padding_value()?,
header: "header.yaml".into(),
header_logo: "header_logo.png".into(),
arm9_bin: "arm9/arm9.bin".into(),
arm9_config: "arm9/arm9.yaml".into(),
arm7_bin: "arm7/arm7.bin".into(),
arm7_config: "arm7/arm7.yaml".into(),
itcm: RomConfigAutoload { bin: "arm9/itcm.bin".into(), config: "arm9/itcm.yaml".into() },
unknown_autoloads,
dtcm: RomConfigAutoload { bin: "arm9/dtcm.bin".into(), config: "arm9/dtcm.yaml".into() },
arm9_overlays: if arm9_overlays.is_empty() { None } else { Some("arm9_overlays/overlays.yaml".into()) },
arm7_overlays: if arm7_overlays.is_empty() { None } else { Some("arm7_overlays/overlays.yaml".into()) },
banner: "banner/banner.yaml".into(),
files_dir: "files/".into(),
path_order: "path_order.txt".into(),
multiboot_signature: if multiboot_signature.is_none() { None } else { Some("multiboot_signature.yaml".into()) },
arm9_hmac_sha1_key: has_arm9_hmac_sha1.then_some("arm9/hmac_sha1_key.bin".into()),
alignment,
};
Ok(Self {
header: Header::load_raw(header),
header_logo: Logo::decompress(&header.logo)?,
arm9,
arm9_overlay_table: arm9_overlays,
arm7: rom.arm7()?,
arm7_overlay_table: arm7_overlays,
banner: Banner::load_raw(&banner),
files: file_root,
multiboot_signature,
path_order,
config,
})
}
pub fn build(mut self, key: Option<&BlowfishKey>) -> Result<raw::Rom<'a>, RomBuildError> {
let mut context = BuildContext { blowfish_key: key, ..Default::default() };
let mut cursor = Cursor::new(Vec::with_capacity(128 * 1024));
context.header_offset = Some(cursor.position() as u32);
cursor.write_all(&[0u8; size_of::<raw::Header>()])?;
self.align_section(&mut cursor, self.config.alignment.arm9)?;
context.arm9_offset = Some(cursor.position() as u32);
context.arm9_autoload_callback = Some(self.arm9.autoload_callback());
context.arm9_build_info_offset = Some(self.arm9.build_info_offset());
cursor.write_all(self.arm9.full_data())?;
let footer = Arm9Footer::new(self.arm9.build_info_offset(), self.arm9.overlay_signatures_offset());
cursor.write_all(bytemuck::bytes_of(&footer))?;
let max_file_id = self.files.max_file_id();
let mut file_allocs = vec![FileAlloc::default(); max_file_id as usize + 1];
if !self.arm9_overlay_table.is_empty() {
self.align_section(&mut cursor, self.config.alignment.arm9_overlay_table)?;
context.arm9_ovt_offset = Some(TableOffset {
offset: cursor.position() as u32,
size: (self.arm9_overlay_table.len() * size_of::<raw::Overlay>()) as u32,
});
let raw_table = self.arm9_overlay_table.build();
cursor.write_all(raw_table.as_bytes())?;
for overlay in self.arm9_overlay_table.overlays() {
self.align_section(&mut cursor, self.config.alignment.arm9_overlay)?;
let start = cursor.position() as u32;
let end = start + overlay.full_data().len() as u32;
file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
cursor.write_all(overlay.full_data())?;
}
}
self.align_section(&mut cursor, self.config.alignment.arm7)?;
context.arm7_offset = Some(cursor.position() as u32);
context.arm7_autoload_callback = Some(self.arm7.autoload_callback());
context.arm7_build_info_offset = None;
cursor.write_all(self.arm7.full_data())?;
if !self.arm7_overlay_table.is_empty() {
self.align_section(&mut cursor, self.config.alignment.arm7_overlay_table)?;
context.arm7_ovt_offset = Some(TableOffset {
offset: cursor.position() as u32,
size: (self.arm7_overlay_table.len() * size_of::<raw::Overlay>()) as u32,
});
let raw_table = self.arm7_overlay_table.build();
cursor.write_all(raw_table.as_bytes())?;
for overlay in self.arm7_overlay_table.overlays() {
self.align_section(&mut cursor, self.config.alignment.arm7_overlay)?;
let start = cursor.position() as u32;
let end = start + overlay.full_data().len() as u32;
file_allocs[overlay.file_id() as usize] = FileAlloc { start, end };
cursor.write_all(overlay.full_data())?;
}
}
self.align_section(&mut cursor, self.config.alignment.file_name_table)?;
self.files.sort_for_fnt();
let fnt = self.files.build_fnt()?.build()?;
context.fnt_offset = Some(TableOffset { offset: cursor.position() as u32, size: fnt.len() as u32 });
cursor.write_all(&fnt)?;
self.align_section(&mut cursor, self.config.alignment.file_allocation_table)?;
context.fat_offset =
Some(TableOffset { offset: cursor.position() as u32, size: (file_allocs.len() * size_of::<FileAlloc>()) as u32 });
cursor.write_all(bytemuck::cast_slice(&file_allocs))?;
self.align_section(&mut cursor, self.config.alignment.banner)?;
let banner = self.banner.build()?;
context.banner_offset = Some(TableOffset { offset: cursor.position() as u32, size: banner.full_data().len() as u32 });
cursor.write_all(banner.full_data())?;
self.align_file_image(&mut cursor, self.config.alignment.file_image_block)?;
self.files.sort_for_rom();
self.files.traverse_files(self.path_order.iter().map(|s| s.as_str()), |file, _| {
self.align_file_image(&mut cursor, self.config.alignment.file).expect("failed to align after file");
let contents = file.contents();
let start = cursor.position() as u32;
let end = start + contents.len() as u32;
file_allocs[file.id() as usize] = FileAlloc { start, end };
cursor.write_all(contents).expect("failed to write file contents");
});
context.rom_size = Some(cursor.position() as u32);
if let Some(multiboot_signature) = &self.multiboot_signature {
cursor.write_all(bytemuck::bytes_of(multiboot_signature))?;
}
let padded_rom_size = cursor.position().next_power_of_two().max(128 * 1024) as u32;
self.align_file_image(&mut cursor, padded_rom_size)?;
cursor.set_position(context.fat_offset.unwrap().offset as u64);
cursor.write_all(bytemuck::cast_slice(&file_allocs))?;
cursor.set_position(context.header_offset.unwrap() as u64);
let header = self.header.build(&context, &self)?;
cursor.write_all(bytemuck::bytes_of(&header))?;
Ok(raw::Rom::new(cursor.into_inner()))
}
fn align(&self, cursor: &mut Cursor<Vec<u8>>, alignment: u32, padding_value: u8) -> Result<(), RomBuildError> {
assert!(alignment.is_power_of_two(), "alignment must be a power of two");
let mask = alignment - 1;
let padding = (!cursor.position() as u32 + 1) & mask;
for _ in 0..padding {
cursor.write_all(&[padding_value])?;
}
Ok(())
}
fn align_section(&self, cursor: &mut Cursor<Vec<u8>>, alignment: u32) -> Result<(), RomBuildError> {
self.align(cursor, alignment, self.config.section_padding_value)
}
fn align_file_image(&self, cursor: &mut Cursor<Vec<u8>>, alignment: u32) -> Result<(), RomBuildError> {
self.align(cursor, alignment, self.config.file_image_padding_value)
}
pub fn header_logo(&self) -> &Logo {
&self.header_logo
}
pub fn header_logo_mut(&mut self) -> &mut Logo {
&mut self.header_logo
}
pub fn arm9(&self) -> &Arm9<'a> {
&self.arm9
}
pub fn arm9_mut(&mut self) -> &mut Arm9<'a> {
&mut self.arm9
}
pub fn arm9_overlay_table(&self) -> &OverlayTable<'a> {
&self.arm9_overlay_table
}
pub fn arm9_overlay_table_mut(&mut self) -> &mut OverlayTable<'a> {
&mut self.arm9_overlay_table
}
pub fn arm9_overlays(&self) -> &[Overlay<'a>] {
self.arm9_overlay_table.overlays()
}
pub fn arm9_overlays_mut(&mut self) -> &mut [Overlay<'a>] {
self.arm9_overlay_table.overlays_mut()
}
pub fn arm7(&self) -> &Arm7<'a> {
&self.arm7
}
pub fn arm7_mut(&mut self) -> &mut Arm7<'a> {
&mut self.arm7
}
pub fn arm7_overlay_table(&self) -> &OverlayTable<'a> {
&self.arm7_overlay_table
}
pub fn arm7_overlay_table_mut(&mut self) -> &mut OverlayTable<'a> {
&mut self.arm7_overlay_table
}
pub fn arm7_overlays(&self) -> &[Overlay<'a>] {
self.arm7_overlay_table.overlays()
}
pub fn arm7_overlays_mut(&mut self) -> &mut [Overlay<'a>] {
self.arm7_overlay_table.overlays_mut()
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn header_mut(&mut self) -> &mut Header {
&mut self.header
}
pub fn config(&self) -> &RomConfig {
&self.config
}
pub fn multiboot_signature(&self) -> Option<&MultibootSignature> {
self.multiboot_signature.as_ref()
}
}
#[derive(Default)]
pub struct BuildContext<'a> {
pub header_offset: Option<u32>,
pub arm9_offset: Option<u32>,
pub arm7_offset: Option<u32>,
pub fnt_offset: Option<TableOffset>,
pub fat_offset: Option<TableOffset>,
pub arm9_ovt_offset: Option<TableOffset>,
pub arm7_ovt_offset: Option<TableOffset>,
pub banner_offset: Option<TableOffset>,
pub blowfish_key: Option<&'a BlowfishKey>,
pub arm9_autoload_callback: Option<u32>,
pub arm7_autoload_callback: Option<u32>,
pub arm9_build_info_offset: Option<u32>,
pub arm7_build_info_offset: Option<u32>,
pub rom_size: Option<u32>,
}
pub struct RomLoadOptions<'a> {
pub key: Option<&'a BlowfishKey>,
pub compress: bool,
pub encrypt: bool,
pub load_files: bool,
pub load_header: bool,
pub load_banner: bool,
pub load_multiboot_signature: bool,
}
impl Default for RomLoadOptions<'_> {
fn default() -> Self {
Self {
key: None,
compress: true,
encrypt: true,
load_files: true,
load_header: true,
load_banner: true,
load_multiboot_signature: true,
}
}
}