From 146c4d2f8cc84b7822ef9c7b0761fdafcea9c1a3 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 28 Oct 2024 17:44:07 -0600 Subject: [PATCH] Add `header_type` and `custom_type` to extract config Extract configuration is now emitted in the output config, so tooling can load and perform their own tasks on extracted assets without having to parse YAML. `header_type`: - `symbol` (default): Emit a full symbol declaration. - `raw`: Emit raw array data (for wrapping in your own declaration) - `none`: Don't emit a header at all. (For custom processing) `custom_type`/`custom_data`: Passed through to the output config as-is for custom tasks. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd/dol.rs | 63 +++++++++++++++++++++++++++++++++++++++++------ src/util/bin2c.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 119 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ab32de..b7af474 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,7 +348,7 @@ dependencies = [ [[package]] name = "decomp-toolkit" -version = "1.1.4" +version = "1.2.0" dependencies = [ "anyhow", "ar", diff --git a/Cargo.toml b/Cargo.toml index 9ad7ad9..3e05e3a 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 = "1.1.4" +version = "1.2.0" edition = "2021" publish = false repository = "https://github.com/encounter/decomp-toolkit" diff --git a/src/cmd/dol.rs b/src/cmd/dol.rs index 410fc88..d70e45a 100644 --- a/src/cmd/dol.rs +++ b/src/cmd/dol.rs @@ -5,6 +5,7 @@ use std::{ fs::DirBuilder, io::{Cursor, Seek, Write}, mem::take, + str::FromStr, time::Instant, }; @@ -36,7 +37,7 @@ use crate::{ }, util::{ asm::write_asm, - bin2c::bin2c, + bin2c::{bin2c, HeaderKind}, comment::MWComment, config::{ apply_splits_file, apply_symbols_file, is_auto_symbol, signed_hex_serde, @@ -303,6 +304,20 @@ pub struct ExtractConfig { /// Path is relative to `out_dir/include`. #[serde(with = "unix_path_serde_option", default, skip_serializing_if = "Option::is_none")] pub header: Option, + /// The type for the extracted symbol in the header file. By default, the header will emit + /// a full symbol declaration (a.k.a. `symbol`), but this can be set to `raw` to emit the raw + /// data as a byte array. `none` avoids emitting a header entirely, in which case the `header` + /// field can be used by external asset processing. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub header_type: Option, + /// A user-defined type for use with external asset processing. This value is simply passed + /// through to the `custom_type` field in the output config. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_type: Option, + /// User-defined data for use with external asset processing. This value is simply passed + /// through to the `custom_data` field in the output config. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_data: Option, } /// A relocation that should be blocked. @@ -364,6 +379,19 @@ pub struct OutputModule { pub ldscript: Utf8UnixPathBuf, pub entry: Option, pub units: Vec, + pub extract: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct OutputExtract { + pub symbol: String, + #[serde(with = "unix_path_serde_option")] + pub binary: Option, + #[serde(with = "unix_path_serde_option")] + pub header: Option, + pub header_type: String, + pub custom_type: Option, + pub custom_data: Option, } #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, Hash)] @@ -938,6 +966,7 @@ fn split_write_obj( ldscript: out_dir.join("ldscript.lcf").with_unix_encoding(), units: Vec::with_capacity(split_objs.len()), entry, + extract: Vec::with_capacity(module.config.extract.len()), }; for (unit, split_obj) in module.obj.link_order.iter().zip(&split_objs) { let out_obj = write_elf(split_obj, config.export_all)?; @@ -975,14 +1004,34 @@ fn split_write_obj( 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.with_encoding()); - if let Some(parent) = out_path.parent() { - DirBuilder::new().recursive(true).create(parent)?; + let header_kind = match extract.header_type.as_deref() { + Some(value) => match HeaderKind::from_str(value) { + Ok(kind) => kind, + Err(()) => bail!("Invalid header type '{}'", value), + }, + _ => HeaderKind::Symbol, + }; + + if header_kind != HeaderKind::None { + if let Some(header) = &extract.header { + let header_string = bin2c(symbol, section, data, header_kind); + let out_path = base_dir.join("include").join(header.with_encoding()); + if let Some(parent) = out_path.parent() { + DirBuilder::new().recursive(true).create(parent)?; + } + write_if_changed(&out_path, header_string.as_bytes())?; } - write_if_changed(&out_path, header_string.as_bytes())?; } + + // Copy to output config + out_config.extract.push(OutputExtract { + symbol: symbol.name.clone(), + binary: extract.binary.clone(), + header: extract.header.clone(), + header_type: header_kind.to_string(), + custom_type: extract.custom_type.clone(), + custom_data: extract.custom_data.clone(), + }); } // Generate ldscript.lcf diff --git a/src/util/bin2c.rs b/src/util/bin2c.rs index 64f9030..601b441 100644 --- a/src/util/bin2c.rs +++ b/src/util/bin2c.rs @@ -1,3 +1,5 @@ +use std::{fmt, str::FromStr}; + use crate::obj::{ObjSection, ObjSectionKind, ObjSymbol}; const PROLOGUE: &str = r#" @@ -13,8 +15,50 @@ const PROLOGUE: &str = r#" "#; +/// The output header type. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum HeaderKind { + /// Do not generate a header. (Used for custom processing) + None, + /// A full symbol definition. + Symbol, + /// Raw array data. + Raw, +} + +impl FromStr for HeaderKind { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "none" => Ok(Self::None), + "symbol" => Ok(Self::Symbol), + "raw" => Ok(Self::Raw), + _ => Err(()), + } + } +} + +impl fmt::Display for HeaderKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => write!(f, "none"), + Self::Symbol => write!(f, "symbol"), + Self::Raw => write!(f, "raw"), + } + } +} + /// Converts a binary blob into a C array. -pub fn bin2c(symbol: &ObjSymbol, section: &ObjSection, data: &[u8]) -> String { +pub fn bin2c(symbol: &ObjSymbol, section: &ObjSection, data: &[u8], kind: HeaderKind) -> String { + match kind { + HeaderKind::None => String::new(), + HeaderKind::Symbol => bin2c_symbol(symbol, section, data), + HeaderKind::Raw => bin2c_raw(data), + } +} + +fn bin2c_symbol(symbol: &ObjSymbol, section: &ObjSection, data: &[u8]) -> String { let mut output = String::new(); output.push_str(PROLOGUE); output.push_str(&format!( @@ -41,3 +85,19 @@ pub fn bin2c(symbol: &ObjSymbol, section: &ObjSection, data: &[u8]) -> String { output.push_str("\n};\n"); output } + +fn bin2c_raw(data: &[u8]) -> String { + let mut output = String::new(); + for (i, byte) in data.iter().enumerate() { + if i > 0 { + if i % 16 == 0 { + output.push('\n'); + } else { + output.push(' '); + } + } + output.push_str(&format!("0x{:02X},", byte)); + } + output.push('\n'); + output +}