From a26498e3817acea586c329cf6e82d2421377ddee Mon Sep 17 00:00:00 2001 From: lifegpc Date: Mon, 11 Aug 2025 11:25:10 +0800 Subject: [PATCH] Add new argument --custom-yaml to support custom export as yaml format --- Cargo.lock | 1 + Cargo.toml | 3 ++- src/args.rs | 4 +++ src/ext/psb.rs | 36 ++++++++++++++++++++++++--- src/main.rs | 5 ++++ src/scripts/artemis/asb.rs | 42 +++++++++++++++++++++++++------ src/scripts/bgi/bsi.rs | 45 ++++++++++++++++++++++++++-------- src/scripts/cat_system/cstl.rs | 38 +++++++++++++++++++++++----- src/scripts/escude/list.rs | 45 ++++++++++++++++++++++++++-------- src/scripts/ex_hibit/rld.rs | 36 +++++++++++++++++++++++---- src/scripts/kirikiri/scn.rs | 27 +++++++++++++++----- src/types.rs | 2 ++ 12 files changed, 235 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 867e556..02f1ff6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,6 +320,7 @@ dependencies = [ "encoding", "flate2", "itertools", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3daa84a..cc281f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" repository = "https://github.com/lifegpc/msg-tool" description = "A command-line tool for exporting, importing, packing, and unpacking script files." license = "MIT" +exclude = [".github", "*.py"] [dependencies] anyhow = "1" @@ -13,7 +14,7 @@ clap = { version = "4.5", features = ["derive"] } clap-num = "1.2" csv = "1.3" ctrlc = "3.4" -emote-psb = { version = "0.5", optional = true } +emote-psb = { version = "0.5", optional = true , features = ["serde"] } encoding_rs = "0.8" fancy-regex = { version = "0.16", optional = true } flate2 = { version = "1.1", optional = true } diff --git a/src/args.rs b/src/args.rs index 89d45a2..2b74d58 100644 --- a/src/args.rs +++ b/src/args.rs @@ -329,6 +329,10 @@ pub struct Arg { #[arg(short = 'W', long, global = true, value_name = "QUALITY", group = "webp_qualityg", value_parser = parse_webp_quality, default_value_t = 80)] /// WebP quality for output images, 0-100. 100 means best quality. pub webp_quality: u8, + #[arg(long, global = true)] + /// Try use YAML format instead of JSON when custom exporting. + /// By default, this is based on output type. But can be overridden by this option. + pub custom_yaml: Option, #[command(subcommand)] /// Command pub command: Command, diff --git a/src/ext/psb.rs b/src/ext/psb.rs index 578e57b..a0581a5 100644 --- a/src/ext/psb.rs +++ b/src/ext/psb.rs @@ -8,13 +8,15 @@ use emote_psb::types::string::*; use emote_psb::types::*; #[cfg(feature = "json")] use json::JsonValue; +use serde::ser::SerializeStruct; +use serde::{Deserialize, Serialize}; use std::cmp::PartialEq; use std::collections::HashMap; use std::ops::{Index, IndexMut}; const NONE: PsbValueFixed = PsbValueFixed::None; -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] /// Represents of a PSB value. pub enum PsbValueFixed { /// No value. @@ -448,7 +450,8 @@ impl PsbValueExt for PsbValue { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] /// Represents a PSB list of PSB values. pub struct PsbListFixed { /// The values in the list. @@ -602,7 +605,8 @@ impl PsbListExt for PsbList { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] /// Represents a PSB object with key-value pairs. pub struct PsbObjectFixed { /// The key-value pairs in the object. @@ -785,6 +789,26 @@ pub struct VirtualPsbFixed { root: PsbObjectFixed, } +impl Serialize for VirtualPsbFixed { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("VirtualPsbFixed", 3)?; + state.serialize_field("version", &self.header.version)?; + state.serialize_field("encryption", &self.header.encryption)?; + state.serialize_field("data", &self.root)?; + state.end() + } +} + +#[derive(Deserialize)] +pub struct VirtualPsbFixedData { + version: u16, + encryption: u16, + data: PsbObjectFixed, +} + impl VirtualPsbFixed { /// Creates a new fixed virtual PSB. pub fn new( @@ -867,6 +891,12 @@ impl VirtualPsbFixed { Ok(()) } + pub fn set_data(&mut self, data: VirtualPsbFixedData) { + self.header.version = data.version; + self.header.encryption = data.encryption; + self.root = data.data; + } + /// Converts this fixed PSB to a JSON object. #[cfg(feature = "json")] pub fn to_json(&self) -> JsonValue { diff --git a/src/main.rs b/src/main.rs index 6c8c41a..5097f55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1746,6 +1746,11 @@ fn main() { webp_quality: arg.webp_quality, #[cfg(feature = "circus-img")] circus_crx_canvas: arg.circus_crx_canvas, + custom_yaml: arg.custom_yaml.unwrap_or_else(|| { + arg.output_type + .map(|s| s == types::OutputScriptType::Yaml) + .unwrap_or(false) + }), }; match &arg.command { args::Command::Export { input, output } => { diff --git a/src/scripts/artemis/asb.rs b/src/scripts/artemis/asb.rs index e222f0d..c5c2ca6 100644 --- a/src/scripts/artemis/asb.rs +++ b/src/scripts/artemis/asb.rs @@ -64,9 +64,15 @@ impl ScriptBuilder for ArtemisAsbBuilder { writer: Box, encoding: Encoding, file_encoding: Encoding, - _config: &ExtraConfig, + config: &ExtraConfig, ) -> Result<()> { - create_file(filename, writer, encoding, file_encoding) + create_file( + filename, + writer, + encoding, + file_encoding, + config.custom_yaml, + ) } } @@ -414,6 +420,7 @@ impl<'a> TextParser<'a> { /// The Artemis ASB script. pub struct Asb { items: Vec, + custom_yaml: bool, } impl Asb { @@ -422,7 +429,7 @@ impl Asb { /// * `buf` - The buffer containing the ASB data. /// * `encoding` - The encoding used for the ASB data. /// * `config` - Extra configuration options. - pub fn new(buf: Vec, encoding: Encoding, _config: &ExtraConfig) -> Result { + pub fn new(buf: Vec, encoding: Encoding, config: &ExtraConfig) -> Result { let mut data = MemReader::new(buf); let mut magic = [0; 5]; data.read_exact(&mut magic)?; @@ -434,7 +441,10 @@ impl Asb { for _ in 0..nums { items.push(data.read_item(encoding)?); } - Ok(Asb { items }) + Ok(Asb { + items, + custom_yaml: config.custom_yaml, + }) } } @@ -452,7 +462,7 @@ impl Script for Asb { } fn custom_output_extension<'a>(&'a self) -> &'a str { - "json" + if self.custom_yaml { "yaml" } else { "json" } } fn extract_messages(&self) -> Result> { @@ -645,7 +655,11 @@ impl Script for Asb { } fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> { - let s = serde_json::to_string_pretty(&self.items)?; + let s = if self.custom_yaml { + serde_yaml_ng::to_string(&self.items)? + } else { + serde_json::to_string_pretty(&self.items)? + }; let s = encode_string(encoding, &s, false)?; let mut file = std::fs::File::create(filename)?; file.write_all(&s)?; @@ -659,7 +673,13 @@ impl Script for Asb { encoding: Encoding, output_encoding: Encoding, ) -> Result<()> { - create_file(custom_filename, file, encoding, output_encoding) + create_file( + custom_filename, + file, + encoding, + output_encoding, + self.custom_yaml, + ) } } @@ -669,15 +689,21 @@ impl Script for Asb { /// * `writer` - The writer to write the ASB script. /// * `encoding` - The encoding used for the ASB script. /// * `output_encoding` - The encoding used for the input file. +/// * `yaml` - Whether to use YAML format instead of JSON for the input file. pub fn create_file<'a>( custom_filename: &'a str, mut writer: Box, encoding: Encoding, output_encoding: Encoding, + yaml: bool, ) -> Result<()> { let f = crate::utils::files::read_file(custom_filename)?; let s = decode_to_string(output_encoding, &f, true)?; - let items: Vec = serde_json::from_str(&s)?; + let items: Vec = if yaml { + serde_yaml_ng::from_str(&s)? + } else { + serde_json::from_str(&s)? + }; writer.write_all(b"ASB\0\0")?; writer.write_u32(items.len() as u32)?; for item in items { diff --git a/src/scripts/bgi/bsi.rs b/src/scripts/bgi/bsi.rs index 1dad1d6..7756cfe 100644 --- a/src/scripts/bgi/bsi.rs +++ b/src/scripts/bgi/bsi.rs @@ -53,9 +53,15 @@ impl ScriptBuilder for BGIBsiScriptBuilder { writer: Box, encoding: Encoding, file_encoding: Encoding, - _config: &ExtraConfig, + config: &ExtraConfig, ) -> Result<()> { - create_file(filename, writer, encoding, file_encoding) + create_file( + filename, + writer, + encoding, + file_encoding, + config.custom_yaml, + ) } } @@ -64,6 +70,7 @@ impl ScriptBuilder for BGIBsiScriptBuilder { pub struct BGIBsiScript { /// Section name and its data map. pub data: BTreeMap>, + custom_yaml: bool, } impl BGIBsiScript { @@ -72,7 +79,7 @@ impl BGIBsiScript { /// * `buf` - The buffer containing the script data. /// * `encoding` - The encoding of the script. /// * `config` - Extra configuration options. - pub fn new(buf: Vec, encoding: Encoding, _config: &ExtraConfig) -> Result { + pub fn new(buf: Vec, encoding: Encoding, config: &ExtraConfig) -> Result { let mut data = BTreeMap::new(); let mut reader = MemReader::new(buf); let section_count = reader.read_u32()?; @@ -97,7 +104,10 @@ impl BGIBsiScript { ); crate::COUNTER.inc_warning(); } - Ok(BGIBsiScript { data }) + Ok(BGIBsiScript { + data, + custom_yaml: config.custom_yaml, + }) } } @@ -115,12 +125,17 @@ impl Script for BGIBsiScript { } fn custom_output_extension(&self) -> &'static str { - "json" + if self.custom_yaml { "yaml" } else { "json" } } fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> { - let s = serde_json::to_string_pretty(&self.data) - .map_err(|e| anyhow::anyhow!("Failed to write BSI Map data to JSON: {}", e))?; + let s = if self.custom_yaml { + serde_yaml_ng::to_string(&self.data) + .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))? + } else { + serde_json::to_string(&self.data) + .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))? + }; let mut writer = crate::utils::files::write_file(filename)?; let s = encode_string(encoding, &s, false)?; writer.write_all(&s)?; @@ -135,7 +150,13 @@ impl Script for BGIBsiScript { encoding: Encoding, output_encoding: Encoding, ) -> Result<()> { - create_file(custom_filename, file, encoding, output_encoding) + create_file( + custom_filename, + file, + encoding, + output_encoding, + self.custom_yaml, + ) } } @@ -144,11 +165,15 @@ fn create_file<'a>( mut writer: Box, encoding: Encoding, output_encoding: Encoding, + yaml: bool, ) -> Result<()> { let input = crate::utils::files::read_file(custom_filename)?; let s = decode_to_string(output_encoding, &input, true)?; - let data: BTreeMap> = serde_json::from_str(&s) - .map_err(|e| anyhow::anyhow!("Failed to read BSI Map data from JSON: {}", e))?; + let data: BTreeMap> = if yaml { + serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))? + } else { + serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))? + }; writer.write_u32(data.len() as u32)?; for (section_name, section_data) in data { let section_name_bytes = encode_string(encoding, §ion_name, false)?; diff --git a/src/scripts/cat_system/cstl.rs b/src/scripts/cat_system/cstl.rs index a5a9f8b..3dc2636 100644 --- a/src/scripts/cat_system/cstl.rs +++ b/src/scripts/cat_system/cstl.rs @@ -60,9 +60,15 @@ impl ScriptBuilder for CstlScriptBuilder { writer: Box, encoding: Encoding, file_encoding: Encoding, - _config: &ExtraConfig, + config: &ExtraConfig, ) -> Result<()> { - create_file(filename, writer, encoding, file_encoding) + create_file( + filename, + writer, + encoding, + file_encoding, + config.custom_yaml, + ) } } @@ -72,15 +78,21 @@ impl ScriptBuilder for CstlScriptBuilder { /// * `file` - The writer to write the CSTL file to. /// * `encoding` - The encoding of the CSTL file. /// * `output_encoding` - The encoding to use for the input file. +/// * `yaml` - Whether to use YAML format. pub fn create_file( custom_filename: &str, mut file: T, encoding: Encoding, output_encoding: Encoding, + yaml: bool, ) -> Result<()> { let input = crate::utils::files::read_file(custom_filename)?; let s = decode_to_string(output_encoding, &input, true)?; - let data: BTreeMap> = serde_json::from_str(&s)?; + let data: BTreeMap> = if yaml { + serde_yaml_ng::from_str(&s)? + } else { + serde_json::from_str(&s)? + }; let count = data .first_key_value() .ok_or(anyhow::anyhow!("No data found in JSON"))? @@ -165,6 +177,7 @@ pub struct CstlScript { langs: Vec, data: Vec>, lang_index: Option, + custom_yaml: bool, } impl CstlScript { @@ -242,6 +255,7 @@ impl CstlScript { langs, data, lang_index, + custom_yaml: config.custom_yaml, }) } } @@ -260,7 +274,7 @@ impl Script for CstlScript { } fn custom_output_extension<'a>(&'a self) -> &'a str { - "json" + if self.custom_yaml { "yaml" } else { "json" } } fn extract_messages(&self) -> Result> { @@ -348,7 +362,13 @@ impl Script for CstlScript { for (lang, data) in self.langs.iter().zip(&self.data) { d.insert(lang, data); } - let s = serde_json::to_string_pretty(&d)?; + let s = if self.custom_yaml { + serde_yaml_ng::to_string(&d) + .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))? + } else { + serde_json::to_string(&d) + .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))? + }; let s = encode_string(encoding, &s, false)?; let mut file = std::fs::File::create(filename)?; file.write_all(&s)?; @@ -362,6 +382,12 @@ impl Script for CstlScript { encoding: Encoding, output_encoding: Encoding, ) -> Result<()> { - create_file(custom_filename, file, encoding, output_encoding) + create_file( + custom_filename, + file, + encoding, + output_encoding, + self.custom_yaml, + ) } } diff --git a/src/scripts/escude/list.rs b/src/scripts/escude/list.rs index 32de8a0..98b9dd2 100644 --- a/src/scripts/escude/list.rs +++ b/src/scripts/escude/list.rs @@ -64,9 +64,15 @@ impl ScriptBuilder for EscudeBinListBuilder { writer: Box, encoding: Encoding, file_encoding: Encoding, - _config: &ExtraConfig, + config: &ExtraConfig, ) -> Result<()> { - create_file(filename, writer, encoding, file_encoding) + create_file( + filename, + writer, + encoding, + file_encoding, + config.custom_yaml, + ) } } @@ -75,6 +81,7 @@ impl ScriptBuilder for EscudeBinListBuilder { pub struct EscudeBinList { /// List of entries in the Escu:de list pub entries: Vec, + custom_yaml: bool, } impl EscudeBinList { @@ -88,7 +95,7 @@ impl EscudeBinList { data: Vec, filename: &str, encoding: Encoding, - _config: &ExtraConfig, + config: &ExtraConfig, ) -> Result { let mut reader = MemReader::new(data); let mut magic = [0; 4]; @@ -111,7 +118,10 @@ impl EscudeBinList { data: ListData::Unknown(data), }); } - let mut s = EscudeBinList { entries }; + let mut s = EscudeBinList { + entries, + custom_yaml: config.custom_yaml, + }; match s.try_decode(filename, encoding) { Ok(_) => {} Err(e) => { @@ -293,11 +303,15 @@ fn create_file<'a>( mut writer: Box, encoding: Encoding, output_encoding: Encoding, + yaml: bool, ) -> Result<()> { let input = crate::utils::files::read_file(custom_filename)?; let s = decode_to_string(output_encoding, &input, true)?; - let entries: Vec = serde_json::from_str(&s) - .map_err(|e| anyhow::anyhow!("Failed to read Escude list from JSON: {}", e))?; + let entries: Vec = if yaml { + serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))? + } else { + serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))? + }; writer.write_all(b"LIST")?; writer.write_u32(0)?; // Placeholder for size let mut total_size = 0; @@ -333,12 +347,17 @@ impl Script for EscudeBinList { } fn custom_output_extension(&self) -> &'static str { - "json" + if self.custom_yaml { "yaml" } else { "json" } } fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> { - let s = serde_json::to_string_pretty(&self.entries) - .map_err(|e| anyhow::anyhow!("Failed to write Escude list to JSON: {}", e))?; + let s = if self.custom_yaml { + serde_yaml_ng::to_string(&self.entries) + .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))? + } else { + serde_json::to_string(&self.entries) + .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))? + }; let mut writer = crate::utils::files::write_file(filename)?; let s = encode_string(encoding, &s, false)?; writer.write_all(&s)?; @@ -353,7 +372,13 @@ impl Script for EscudeBinList { encoding: Encoding, output_encoding: Encoding, ) -> Result<()> { - create_file(custom_filename, writer, encoding, output_encoding) + create_file( + custom_filename, + writer, + encoding, + output_encoding, + self.custom_yaml, + ) } } diff --git a/src/scripts/ex_hibit/rld.rs b/src/scripts/ex_hibit/rld.rs index 696ba9d..36b7c7d 100644 --- a/src/scripts/ex_hibit/rld.rs +++ b/src/scripts/ex_hibit/rld.rs @@ -184,6 +184,7 @@ pub struct RldScript { ops: Vec, is_def_chara: bool, name_table: Option>, + custom_yaml: bool, } impl RldScript { @@ -283,6 +284,7 @@ impl RldScript { ops, is_def_chara, name_table, + custom_yaml: config.custom_yaml, }) } @@ -375,7 +377,7 @@ impl Script for RldScript { } fn custom_output_extension<'a>(&'a self) -> &'a str { - "json" + if self.custom_yaml { "yaml" } else { "json" } } fn extract_messages(&self) -> Result> { @@ -472,9 +474,21 @@ impl Script for RldScript { fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> { let s = if self.is_def_chara { let names = self.name_table()?; - serde_json::to_string_pretty(&names)? + if self.custom_yaml { + serde_yaml_ng::to_string(&names) + .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))? + } else { + serde_json::to_string(&names) + .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))? + } } else { - serde_json::to_string_pretty(&self.ops)? + if self.custom_yaml { + serde_yaml_ng::to_string(&self.ops) + .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))? + } else { + serde_json::to_string(&self.ops) + .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))? + } }; let s = encode_string(encoding, &s, false)?; let mut file = std::fs::File::create(filename)?; @@ -493,7 +507,13 @@ impl Script for RldScript { let s = decode_to_string(output_encoding, &f, true)?; let ops: Vec = if self.is_def_chara { let mut ops = self.ops.clone(); - let names: BTreeMap = serde_json::from_str(&s)?; + let names: BTreeMap = if self.custom_yaml { + serde_yaml_ng::from_str(&s) + .map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))? + } else { + serde_json::from_str(&s) + .map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))? + }; for op in ops.iter_mut() { if op.op == 48 { if op.strs.is_empty() { @@ -516,7 +536,13 @@ impl Script for RldScript { } ops } else { - serde_json::from_str(&s)? + if self.custom_yaml { + serde_yaml_ng::from_str(&s) + .map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))? + } else { + serde_json::from_str(&s) + .map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))? + } }; if self.decrypted { let mut writer = MemWriter::new(); diff --git a/src/scripts/kirikiri/scn.rs b/src/scripts/kirikiri/scn.rs index 805e3f8..af1bebe 100644 --- a/src/scripts/kirikiri/scn.rs +++ b/src/scripts/kirikiri/scn.rs @@ -112,6 +112,7 @@ pub struct ScnScript { export_comumode: bool, filename: String, comumode_json: Option>>, + custom_yaml: bool, } impl ScnScript { @@ -145,6 +146,7 @@ impl ScnScript { export_comumode: config.kirikiri_export_comumode, filename: filename.to_string(), comumode_json: config.kirikiri_comumode_json.clone(), + custom_yaml: config.custom_yaml, }) } } @@ -163,7 +165,7 @@ impl Script for ScnScript { } fn custom_output_extension<'a>(&'a self) -> &'a str { - "json" + if self.custom_yaml { "yaml" } else { "json" } } fn extract_messages(&self) -> Result> { @@ -615,7 +617,12 @@ impl Script for ScnScript { } fn custom_export(&self, filename: &Path, encoding: Encoding) -> Result<()> { - let s = json::stringify_pretty(self.psb.to_json(), 2); + let s = if self.custom_yaml { + serde_yaml_ng::to_string(&self.psb) + .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))? + } else { + json::stringify_pretty(self.psb.to_json(), 2) + }; let mut f = crate::utils::files::write_file(filename)?; let b = encode_string(encoding, &s, false)?; f.write_all(&b)?; @@ -631,10 +638,18 @@ impl Script for ScnScript { ) -> Result<()> { let data = crate::utils::files::read_file(custom_filename)?; let s = decode_to_string(output_encoding, &data, true)?; - let json = json::parse(&s)?; - let mut psb = self.psb.clone(); - psb.from_json(&json)?; - let psb = psb.to_psb(); + let psb = if self.custom_yaml { + let data: VirtualPsbFixedData = serde_yaml_ng::from_str(&s) + .map_err(|e| anyhow::anyhow!("Failed to deserialize YAML: {}", e))?; + let mut psb = self.psb.clone(); + psb.set_data(data); + psb.to_psb() + } else { + let json = json::parse(&s)?; + let mut psb = self.psb.clone(); + psb.from_json(&json)?; + psb.to_psb() + }; let writer = PsbWriter::new(psb, file); writer.finish().map_err(|e| { anyhow::anyhow!("Failed to write PSB to file {}: {:?}", self.filename, e) diff --git a/src/types.rs b/src/types.rs index f97dc0f..cdaef59 100644 --- a/src/types.rs +++ b/src/types.rs @@ -323,6 +323,8 @@ pub struct ExtraConfig { #[cfg(feature = "circus-img")] /// Draw Circus CRX images on canvas (if canvas width and height are specified in file) pub circus_crx_canvas: bool, + /// Try use YAML format instead of JSON when custom exporting. + pub custom_yaml: bool, } #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]