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
This commit is contained in:
Luke Street 2023-11-26 00:35:05 -05:00
parent 038354a37e
commit dd60128ba0
5 changed files with 90 additions and 8 deletions

2
Cargo.lock generated
View File

@ -295,7 +295,7 @@ dependencies = [
[[package]] [[package]]
name = "decomp-toolkit" name = "decomp-toolkit"
version = "0.6.1" version = "0.6.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ar", "ar",

View File

@ -3,7 +3,7 @@ name = "decomp-toolkit"
description = "Yet another GameCube/Wii decompilation toolkit." description = "Yet another GameCube/Wii decompilation toolkit."
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
version = "0.6.1" version = "0.6.2"
edition = "2021" edition = "2021"
publish = false publish = false
repository = "https://github.com/encounter/decomp-toolkit" repository = "https://github.com/encounter/decomp-toolkit"

View File

@ -37,6 +37,7 @@ use crate::{
}, },
util::{ util::{
asm::write_asm, asm::write_asm,
bin2c::bin2c,
comment::MWComment, comment::MWComment,
config::{ config::{
apply_splits_file, apply_symbols_file, is_auto_symbol, write_splits_file, apply_splits_file, apply_symbols_file, is_auto_symbol, write_splits_file,
@ -252,6 +253,22 @@ pub struct ModuleConfig {
/// Overrides links to other modules. /// Overrides links to other modules.
#[serde(skip_serializing_if = "is_default")] #[serde(skip_serializing_if = "is_default")]
pub links: Option<Vec<String>>, pub links: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub extract: Vec<ExtractConfig>,
}
#[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<PathBuf>,
/// 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<PathBuf>,
} }
impl ModuleConfig { impl ModuleConfig {
@ -776,6 +793,7 @@ fn load_analyze_dol(config: &ProjectConfig) -> Result<AnalyzeResult> {
fn split_write_obj( fn split_write_obj(
module: &mut ModuleInfo, module: &mut ModuleInfo,
config: &ProjectConfig, config: &ProjectConfig,
base_dir: &Path,
out_dir: &Path, out_dir: &Path,
no_update: bool, no_update: bool,
) -> Result<OutputModule> { ) -> Result<OutputModule> {
@ -856,6 +874,37 @@ fn split_write_obj(
write_if_changed(&out_path, &out_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 // Generate ldscript.lcf
let ldscript_template = if let Some(template) = &module.config.ldscript_template { let ldscript_template = if let Some(template) = &module.config.ldscript_template {
Some(fs::read_to_string(template).with_context(|| { Some(fs::read_to_string(template).with_context(|| {
@ -894,7 +943,8 @@ fn write_if_changed(path: &Path, contents: &[u8]) -> Result<()> {
return Ok(()); return Ok(());
} }
} }
fs::write(path, contents)?; fs::write(path, contents)
.with_context(|| format!("Failed to write file '{}'", path.display()))?;
Ok(()) Ok(())
} }
@ -1134,15 +1184,14 @@ fn split(args: SplitArgs) -> Result<()> {
let _span = let _span =
info_span!("module", name = %config.base.name(), id = dol.obj.module_id).entered(); info_span!("module", name = %config.base.name(), id = dol.obj.module_id).entered();
dol_result = Some( 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!( format!(
"While processing object '{}' (module ID {})", "While processing object '{}' (module ID {})",
config.base.file_name(), config.base.file_name(),
dol.obj.module_id dol.obj.module_id
) )
}, }),
),
); );
}); });
// Modules // Modules
@ -1155,7 +1204,7 @@ fn split(args: SplitArgs) -> Result<()> {
info_span!("module", name = %module.config.name(), id = module.obj.module_id) info_span!("module", name = %module.config.name(), id = module.obj.module_id)
.entered(); .entered();
let out_dir = args.out_dir.join(module.config.name().as_ref()); 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!( format!(
"While processing object '{}' (module {} ID {})", "While processing object '{}' (module {} ID {})",
@ -1697,6 +1746,7 @@ fn config(args: ConfigArgs) -> Result<()> {
force_active: vec![], force_active: vec![],
ldscript_template: None, ldscript_template: None,
links: None, links: None,
extract: vec![],
}, },
selfile: None, selfile: None,
selfile_hash: None, selfile_hash: None,
@ -1733,6 +1783,7 @@ fn config(args: ConfigArgs) -> Result<()> {
force_active: vec![], force_active: vec![],
ldscript_template: None, ldscript_template: None,
links: None, links: None,
extract: vec![],
})); }));
} }
Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("sel")) => { Some(ext) if ext.eq_ignore_ascii_case(OsStr::new("sel")) => {
@ -1750,6 +1801,7 @@ fn config(args: ConfigArgs) -> Result<()> {
force_active: vec![], force_active: vec![],
ldscript_template: None, ldscript_template: None,
links: None, links: None,
extract: vec![],
}); });
} }
_ => bail!("Unknown file extension: '{}'", path.display()), _ => bail!("Unknown file extension: '{}'", path.display()),

29
src/util/bin2c.rs Normal file
View File

@ -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
}

View File

@ -2,6 +2,7 @@ use std::{borrow::Cow, ops::Deref};
pub mod alf; pub mod alf;
pub mod asm; pub mod asm;
pub mod bin2c;
pub mod comment; pub mod comment;
pub mod config; pub mod config;
pub mod dep; pub mod dep;