From 4d5ad39e24c67534def875182f58a2466c340978 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Wed, 3 Sep 2025 22:44:21 +0800 Subject: [PATCH] Add support to export all chat message --- src/args.rs | 18 +++++++---- src/main.rs | 8 +++-- src/scripts/kirikiri/scn.rs | 62 ++++++++++++++++++++++--------------- src/types.rs | 14 ++++++--- 4 files changed, 63 insertions(+), 39 deletions(-) diff --git a/src/args.rs b/src/args.rs index da0aa2c..8a38ffe 100644 --- a/src/args.rs +++ b/src/args.rs @@ -69,6 +69,7 @@ fn parse_flac_compression_level(level: &str) -> Result { group = ArgGroup::new("ex_hibit_rld_def_xor_keyg").multiple(false), group = ArgGroup::new("webp_qualityg").multiple(false), group = ArgGroup::new("cat_system_int_encrypt_passwordg").multiple(false), + group = ArgGroup::new("kirikiri_chat_jsong").multiple(false), )] #[command( version, @@ -185,14 +186,19 @@ pub struct Arg { /// Kirikiri language index in script. If not specified, the first language will be used. pub kirikiri_language_index: Option, #[cfg(feature = "kirikiri")] - #[arg(long, global = true, action = ArgAction::SetTrue)] - /// Export COMU message to extra json file. (for Kirikiri SCN script.) - /// Only CIRCUS's game have COMU message. - pub kirikiri_export_comumode: bool, + #[arg(long, global = true)] + /// Export chat message to extra json file. (for Kirikiri SCN script.) + /// For example, CIRCUS's comu message. Yuzusoft's phone chat message. + pub kirikiri_export_chat: bool, #[cfg(feature = "kirikiri")] #[arg(long, global = true)] - /// Kirikiri COMU message translation file. (Map, key is original text, value is translated text.) - pub kirikiri_comumode_json: Option, + /// Kirikiri chat message key. For example, CIRCUS's key is "comumode". Yuzusoft's key is "phonechat". + /// If not specified, "comumode" will be used. + pub kirikiri_chat_key: Option, + #[cfg(feature = "kirikiri")] + #[arg(long, global = true, group = "kirikiri_chat_jsong")] + /// Kirikiri chat message translation file. (Map, key is original text, value is translated text.) + pub kirikiri_chat_json: Option, #[cfg(feature = "kirikiri")] #[arg(long, global = true, action = ArgAction::SetTrue, alias = "kr-no-empty-lines", alias = "kirikiri-no-empty-lines")] /// Remove empty lines in Kirikiri KS script. diff --git a/src/main.rs b/src/main.rs index 71f6055..e3a16e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1698,10 +1698,12 @@ fn main() { #[cfg(feature = "kirikiri")] kirikiri_language_index: arg.kirikiri_language_index.clone(), #[cfg(feature = "kirikiri")] - kirikiri_export_comumode: arg.kirikiri_export_comumode, + kirikiri_export_chat: arg.kirikiri_export_chat, #[cfg(feature = "kirikiri")] - kirikiri_comumode_json: arg - .kirikiri_comumode_json + kirikiri_chat_key: arg.kirikiri_chat_key.clone(), + #[cfg(feature = "kirikiri")] + kirikiri_chat_json: arg + .kirikiri_chat_json .as_ref() .map(|s| scripts::kirikiri::read_kirikiri_comu_json(s).unwrap()), #[cfg(feature = "kirikiri")] diff --git a/src/scripts/kirikiri/scn.rs b/src/scripts/kirikiri/scn.rs index c4ffc57..535d410 100644 --- a/src/scripts/kirikiri/scn.rs +++ b/src/scripts/kirikiri/scn.rs @@ -110,9 +110,10 @@ impl ScriptBuilder for ScnScriptBuilder { pub struct ScnScript { psb: VirtualPsbFixed, language_index: usize, - export_comumode: bool, + export_chat: bool, filename: String, - comumode_json: Option>>, + chat_key: Option, + chat_json: Option>>, custom_yaml: bool, } @@ -144,9 +145,10 @@ impl ScnScript { Ok(Self { psb: psb.to_psb_fixed(), language_index: config.kirikiri_language_index.unwrap_or(0), - export_comumode: config.kirikiri_export_comumode, + export_chat: config.kirikiri_export_chat, filename: filename.to_string(), - comumode_json: config.kirikiri_comumode_json.clone(), + chat_key: config.kirikiri_chat_key.clone(), + chat_json: config.kirikiri_chat_json.clone(), custom_yaml: config.custom_yaml, }) } @@ -179,8 +181,10 @@ impl Script for ScnScript { PsbValueFixed::List(list) => list, _ => return Err(anyhow::anyhow!("scenes is not a list")), }; - let mut comu = if self.export_comumode { - Some(ExportComuMes::new()) + let mut comu = if self.export_chat { + Some(ExportMes::new( + self.chat_key.clone().unwrap_or("comumode".to_string()), + )) } else { None }; @@ -322,19 +326,19 @@ impl Script for ScnScript { let filename = pb .file_stem() .map(|s| s.to_string_lossy()) - .unwrap_or(std::borrow::Cow::from("comumode")); - pb.set_file_name(format!("{}_comumode.json", filename)); + .unwrap_or(std::borrow::Cow::from(comu.key.as_str())); + pb.set_file_name(format!("{}_{}.json", filename, comu.key)); match std::fs::File::create(&pb) { Ok(mut f) => { let messages: Vec = comu.messages.into_iter().collect(); if let Err(e) = serde_json::to_writer_pretty(&mut f, &messages) { - eprintln!("Failed to write COMU messages to {}: {:?}", pb.display(), e); + eprintln!("Failed to write chat messages to {}: {:?}", pb.display(), e); crate::COUNTER.inc_warning(); } } Err(e) => { eprintln!( - "Failed to create COMU messages file {}: {:?}", + "Failed to create chat messages file {}: {:?}", pb.display(), e ); @@ -362,10 +366,13 @@ impl Script for ScnScript { if !scenes.is_list() { return Err(anyhow::anyhow!("scenes is not an array")); } - let comu = self - .comumode_json - .as_ref() - .map(|json| ImportComuMes::new(json, replacement)); + let comu = self.chat_json.as_ref().map(|json| { + ImportMes::new( + json, + replacement, + self.chat_key.clone().unwrap_or("comumode".to_string()), + ) + }); for (i, scene) in scenes.members_mut().enumerate() { if !scene.is_object() { return Err(anyhow::anyhow!("scene at {} is not an object", i)); @@ -653,14 +660,16 @@ impl Script for ScnScript { } #[derive(Debug)] -struct ExportComuMes { +struct ExportMes { pub messages: HashSet, + pub key: String, } -impl ExportComuMes { - pub fn new() -> Self { +impl ExportMes { + pub fn new(key: String) -> Self { Self { messages: HashSet::new(), + key: key, } } @@ -668,7 +677,7 @@ impl ExportComuMes { match value { PsbValueFixed::Object(obj) => { for (k, v) in obj.iter() { - if k == "comumode" { + if k == &self.key { if let PsbValueFixed::List(list) = v { for item in list.iter() { if let PsbValueFixed::Object(obj) = item { @@ -687,7 +696,7 @@ impl ExportComuMes { let list = list.values(); if list.len() > 1 { if let PsbValueFixed::String(s) = &list[0] { - if s.string() == "comumode" { + if s.string() == &self.key { for i in 1..list.len() { if let PsbValueFixed::String(s) = &list[i - 1] { if s.string() == "text" { @@ -712,19 +721,22 @@ impl ExportComuMes { } #[derive(Debug)] -struct ImportComuMes<'a> { +struct ImportMes<'a> { messages: &'a Arc>, replacement: Option<&'a ReplacementTable>, + key: String, } -impl<'a> ImportComuMes<'a> { +impl<'a> ImportMes<'a> { pub fn new( messages: &'a Arc>, replacement: Option<&'a ReplacementTable>, + key: String, ) -> Self { Self { messages, replacement, + key: key, } } @@ -732,7 +744,7 @@ impl<'a> ImportComuMes<'a> { match value { PsbValueFixed::Object(obj) => { for (k, v) in obj.iter_mut() { - if k == "comumode" { + if k == &self.key { for obj in v.members_mut() { if let Some(text) = obj["text"].as_str() { if let Some(replace_text) = self.messages.get(text) { @@ -745,7 +757,7 @@ impl<'a> ImportComuMes<'a> { obj["text"].set_string(text.replace("\n", "\\n")); } else { eprintln!( - "Warning: COMU message '{}' not found in translation table.", + "Warning: chat message '{}' not found in translation table.", text ); crate::COUNTER.inc_warning(); @@ -759,7 +771,7 @@ impl<'a> ImportComuMes<'a> { } PsbValueFixed::List(list) => { if list.len() > 1 { - if list[0] == "comumode" { + if list[0] == self.key { for i in 1..list.len() { if list[i - 1] == "text" { if let Some(text) = list[i].as_str() { @@ -773,7 +785,7 @@ impl<'a> ImportComuMes<'a> { list[i].set_string(text.replace("\n", "\\n")); } else { eprintln!( - "Warning: COMU message '{}' not found in translation table.", + "Warning: chat message '{}' not found in translation table.", text ); crate::COUNTER.inc_warning(); diff --git a/src/types.rs b/src/types.rs index 6811c53..b9443ef 100644 --- a/src/types.rs +++ b/src/types.rs @@ -260,12 +260,16 @@ pub struct ExtraConfig { /// Kirikiri language index in script. If not specified, the first language will be used. pub kirikiri_language_index: Option, #[cfg(feature = "kirikiri")] - /// Export COMU message to extra json file. (for Kirikiri SCN script.) - /// Only CIRCUS's game have COMU message. - pub kirikiri_export_comumode: bool, + /// Export chat message to extra json file. (for Kirikiri SCN script.) + /// For example, CIRCUS's comu message. Yuzusoft's phone chat message. + pub kirikiri_export_chat: bool, #[cfg(feature = "kirikiri")] - /// Kirikiri COMU message translation. key is original text, value is translated text. - pub kirikiri_comumode_json: Option>>, + /// Kirikiri chat message key. For example, CIRCUS's key is "comumode". Yuzusoft's key is "phonechat". + /// If not specified, "comumode" will be used. + pub kirikiri_chat_key: Option, + #[cfg(feature = "kirikiri")] + /// Kirikiri chat message translation. key is original text, value is translated text. + pub kirikiri_chat_json: Option>>, #[cfg(feature = "kirikiri")] /// Remove empty lines in Kirikiri KS script. pub kirikiri_remove_empty_lines: bool,