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:
parent
038354a37e
commit
dd60128ba0
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue