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.
This commit is contained in:
Luke Street 2024-10-28 17:44:07 -06:00
parent f984bc3fb2
commit 146c4d2f8c
4 changed files with 119 additions and 10 deletions

2
Cargo.lock generated
View File

@ -348,7 +348,7 @@ dependencies = [
[[package]]
name = "decomp-toolkit"
version = "1.1.4"
version = "1.2.0"
dependencies = [
"anyhow",
"ar",

View File

@ -3,7 +3,7 @@ name = "decomp-toolkit"
description = "Yet another GameCube/Wii decompilation toolkit."
authors = ["Luke Street <luke@street.dev>"]
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"

View File

@ -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<Utf8UnixPathBuf>,
/// 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<String>,
/// 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<String>,
/// 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<serde_json::Value>,
}
/// A relocation that should be blocked.
@ -364,6 +379,19 @@ pub struct OutputModule {
pub ldscript: Utf8UnixPathBuf,
pub entry: Option<String>,
pub units: Vec<OutputUnit>,
pub extract: Vec<OutputExtract>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct OutputExtract {
pub symbol: String,
#[serde(with = "unix_path_serde_option")]
pub binary: Option<Utf8UnixPathBuf>,
#[serde(with = "unix_path_serde_option")]
pub header: Option<Utf8UnixPathBuf>,
pub header_type: String,
pub custom_type: Option<String>,
pub custom_data: Option<serde_json::Value>,
}
#[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

View File

@ -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<Self, Self::Err> {
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
}