From dd60128ba0acbbbccfe2beace165e47469b4e7f5 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 26 Nov 2023 00:35:05 -0500 Subject: [PATCH] Extract embedded assets to binary and C header Adds an "extract" list to project configuration: ``` extract: - symbol: SomeData binary: Lib/SomeData.bin header: Lib/SomeData.inc ``` This example extracts the data of symbol `SomeData` to `out_dir/bin/Lib/SomeData.bin`, and a C array representation to `out_dir/include/Lib/SomeData.inc`. Resolves #11 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd/dol.rs | 64 ++++++++++++++++++++++++++++++++++++++++++----- src/util/bin2c.rs | 29 +++++++++++++++++++++ src/util/mod.rs | 1 + 5 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 src/util/bin2c.rs diff --git a/Cargo.lock b/Cargo.lock index 023773c..cd5041f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,7 +295,7 @@ dependencies = [ [[package]] name = "decomp-toolkit" -version = "0.6.1" +version = "0.6.2" dependencies = [ "anyhow", "ar", diff --git a/Cargo.toml b/Cargo.toml index 16c67de..0e9ac07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "decomp-toolkit" description = "Yet another GameCube/Wii decompilation toolkit." authors = ["Luke Street "] license = "MIT OR Apache-2.0" -version = "0.6.1" +version = "0.6.2" edition = "2021" publish = false repository = "https://github.com/encounter/decomp-toolkit" diff --git a/src/cmd/dol.rs b/src/cmd/dol.rs index 2770326..f0d82d9 100644 --- a/src/cmd/dol.rs +++ b/src/cmd/dol.rs @@ -37,6 +37,7 @@ use crate::{ }, util::{ asm::write_asm, + bin2c::bin2c, comment::MWComment, config::{ apply_splits_file, apply_symbols_file, is_auto_symbol, write_splits_file, @@ -252,6 +253,22 @@ pub struct ModuleConfig { /// Overrides links to other modules. #[serde(skip_serializing_if = "is_default")] pub links: Option>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub extract: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct ExtractConfig { + /// The name of the symbol to extract. + pub symbol: String, + /// If specified, the symbol's data will be extracted to the given file. + /// Path is relative to `out_dir/bin`. + #[serde(with = "path_slash_serde_option", default, skip_serializing_if = "Option::is_none")] + pub binary: Option, + /// If specified, the symbol's data will be extracted to the given file as a C array. + /// Path is relative to `out_dir/include`. + #[serde(with = "path_slash_serde_option", default, skip_serializing_if = "Option::is_none")] + pub header: Option, } impl ModuleConfig { @@ -776,6 +793,7 @@ fn load_analyze_dol(config: &ProjectConfig) -> Result { fn split_write_obj( module: &mut ModuleInfo, config: &ProjectConfig, + base_dir: &Path, out_dir: &Path, no_update: bool, ) -> Result { @@ -856,6 +874,37 @@ fn split_write_obj( write_if_changed(&out_path, &out_obj)?; } + // Write extracted files + for extract in &module.config.extract { + let (_, symbol) = module + .obj + .symbols + .by_name(&extract.symbol)? + .with_context(|| format!("Failed to locate symbol '{}'", extract.symbol))?; + let section_index = symbol + .section + .with_context(|| format!("Symbol '{}' has no section", extract.symbol))?; + let section = &module.obj.sections[section_index]; + let data = section.symbol_data(symbol)?; + + if let Some(binary) = &extract.binary { + let out_path = base_dir.join("bin").join(binary); + if let Some(parent) = out_path.parent() { + DirBuilder::new().recursive(true).create(parent)?; + } + write_if_changed(&out_path, data)?; + } + + if let Some(header) = &extract.header { + let header_string = bin2c(symbol, section, data); + let out_path = base_dir.join("include").join(header); + if let Some(parent) = out_path.parent() { + DirBuilder::new().recursive(true).create(parent)?; + } + write_if_changed(&out_path, header_string.as_bytes())?; + } + } + // Generate ldscript.lcf let ldscript_template = if let Some(template) = &module.config.ldscript_template { Some(fs::read_to_string(template).with_context(|| { @@ -894,7 +943,8 @@ fn write_if_changed(path: &Path, contents: &[u8]) -> Result<()> { return Ok(()); } } - fs::write(path, contents)?; + fs::write(path, contents) + .with_context(|| format!("Failed to write file '{}'", path.display()))?; Ok(()) } @@ -1134,15 +1184,14 @@ fn split(args: SplitArgs) -> Result<()> { let _span = info_span!("module", name = %config.base.name(), id = dol.obj.module_id).entered(); dol_result = Some( - split_write_obj(&mut dol, &config, &args.out_dir, args.no_update).with_context( - || { + split_write_obj(&mut dol, &config, &args.out_dir, &args.out_dir, args.no_update) + .with_context(|| { format!( "While processing object '{}' (module ID {})", config.base.file_name(), dol.obj.module_id ) - }, - ), + }), ); }); // Modules @@ -1155,7 +1204,7 @@ fn split(args: SplitArgs) -> Result<()> { info_span!("module", name = %module.config.name(), id = module.obj.module_id) .entered(); let out_dir = args.out_dir.join(module.config.name().as_ref()); - split_write_obj(module, &config, &out_dir, args.no_update).with_context( + split_write_obj(module, &config, &args.out_dir, &out_dir, args.no_update).with_context( || { format!( "While processing object '{}' (module {} ID {})", @@ -1697,6 +1746,7 @@ fn config(args: ConfigArgs) -> Result<()> { force_active: vec![], ldscript_template: None, links: None, + extract: vec![], }, selfile: None, selfile_hash: None, @@ -1733,6 +1783,7 @@ fn config(args: ConfigArgs) -> Result<()> { force_active: vec![], ldscript_template: None, links: None, + extract: vec![], })); } Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("sel")) => { @@ -1750,6 +1801,7 @@ fn config(args: ConfigArgs) -> Result<()> { force_active: vec![], ldscript_template: None, links: None, + extract: vec![], }); } _ => bail!("Unknown file extension: '{}'", path.display()), diff --git a/src/util/bin2c.rs b/src/util/bin2c.rs new file mode 100644 index 0000000..5818cd5 --- /dev/null +++ b/src/util/bin2c.rs @@ -0,0 +1,29 @@ +use crate::obj::{ObjSection, ObjSectionKind, ObjSymbol}; + +/// Converts a binary blob into a C array. +pub fn bin2c(symbol: &ObjSymbol, section: &ObjSection, data: &[u8]) -> String { + let mut output = String::new(); + output.push_str(&format!( + "// {} (size: {:#X}, address: {:#X}, section: {})\n", + symbol.name, symbol.size, symbol.address, section.name + )); + if symbol.flags.is_local() { + output.push_str("static "); + } + if section.kind == ObjSectionKind::ReadOnlyData { + output.push_str("const "); + } + output.push_str("unsigned char "); + output.push_str(symbol.demangled_name.as_deref().unwrap_or(symbol.name.as_str())); + output.push_str("[] = {"); + for (i, byte) in data.iter().enumerate() { + if i % 16 == 0 { + output.push_str("\n "); + } else { + output.push(' '); + } + output.push_str(&format!("0x{:02X},", byte)); + } + output.push_str("\n};\n"); + output +} diff --git a/src/util/mod.rs b/src/util/mod.rs index 1617fad..a599fc4 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,6 +2,7 @@ use std::{borrow::Cow, ops::Deref}; pub mod alf; pub mod asm; +pub mod bin2c; pub mod comment; pub mod config; pub mod dep;