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:
parent
f984bc3fb2
commit
146c4d2f8c
|
@ -348,7 +348,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "decomp-toolkit"
|
||||
version = "1.1.4"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ar",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue