msg_tool\scripts\kirikiri/
scn.rs

1//! Kirikiri Scene File (.scn)
2use super::mdf::Mdf;
3use crate::ext::io::*;
4use crate::ext::psb::*;
5use crate::scripts::base::*;
6use crate::types::*;
7use crate::utils::encoding::*;
8use anyhow::Result;
9use emote_psb::{PsbReader, PsbWriter};
10use std::collections::{HashMap, HashSet};
11use std::io::{Read, Seek};
12use std::path::Path;
13use std::sync::Arc;
14
15#[derive(Debug)]
16/// Kirikiri Scene Script Builder
17pub struct ScnScriptBuilder {}
18
19impl ScnScriptBuilder {
20    /// Creates a new instance of `ScnScriptBuilder`
21    pub fn new() -> Self {
22        Self {}
23    }
24}
25
26impl ScriptBuilder for ScnScriptBuilder {
27    fn default_encoding(&self) -> Encoding {
28        Encoding::Utf8
29    }
30
31    fn build_script(
32        &self,
33        buf: Vec<u8>,
34        filename: &str,
35        _encoding: Encoding,
36        _archive_encoding: Encoding,
37        config: &ExtraConfig,
38        _archive: Option<&Box<dyn Script>>,
39    ) -> Result<Box<dyn Script>> {
40        Ok(Box::new(ScnScript::new(
41            MemReader::new(buf),
42            filename,
43            config,
44        )?))
45    }
46
47    fn build_script_from_file(
48        &self,
49        filename: &str,
50        _encoding: Encoding,
51        _archive_encoding: Encoding,
52        config: &ExtraConfig,
53        _archive: Option<&Box<dyn Script>>,
54    ) -> Result<Box<dyn Script>> {
55        if filename == "-" {
56            let data = crate::utils::files::read_file(filename)?;
57            Ok(Box::new(ScnScript::new(
58                MemReader::new(data),
59                filename,
60                config,
61            )?))
62        } else {
63            let f = std::fs::File::open(filename)?;
64            let reader = std::io::BufReader::new(f);
65            Ok(Box::new(ScnScript::new(reader, filename, config)?))
66        }
67    }
68
69    fn build_script_from_reader(
70        &self,
71        reader: Box<dyn ReadSeek>,
72        filename: &str,
73        _encoding: Encoding,
74        _archive_encoding: Encoding,
75        config: &ExtraConfig,
76        _archive: Option<&Box<dyn Script>>,
77    ) -> Result<Box<dyn Script>> {
78        Ok(Box::new(ScnScript::new(reader, filename, config)?))
79    }
80
81    fn extensions(&self) -> &'static [&'static str] {
82        &["ks.scn"]
83    }
84
85    fn script_type(&self) -> &'static ScriptType {
86        &ScriptType::KirikiriScn
87    }
88
89    fn is_this_format(&self, filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
90        if Path::new(filename)
91            .file_name()
92            .map(|name| {
93                name.to_ascii_lowercase()
94                    .to_string_lossy()
95                    .ends_with(".ks.scn")
96            })
97            .unwrap_or(false)
98            && buf_len >= 4
99            && buf.starts_with(b"PSB\0")
100        {
101            return Some(255);
102        }
103        None
104    }
105}
106
107#[derive(Debug)]
108/// Kirikiri Scene Script
109pub struct ScnScript {
110    psb: VirtualPsbFixed,
111    language_index: usize,
112    export_comumode: bool,
113    filename: String,
114    comumode_json: Option<Arc<HashMap<String, String>>>,
115    custom_yaml: bool,
116}
117
118impl ScnScript {
119    /// Creates a new `ScnScript` from the given reader and filename
120    ///
121    /// * `reader` - The reader containing the PSB or MDF data
122    /// * `filename` - The name of the file (used for error reporting and extension detection)
123    /// * `config` - Extra configuration options
124    pub fn new<R: Read + Seek>(
125        mut reader: R,
126        filename: &str,
127        config: &ExtraConfig,
128    ) -> Result<Self> {
129        let mut header = [0u8; 4];
130        reader.read_exact(&mut header)?;
131        if &header == b"mdf\0" {
132            let mut data = Vec::new();
133            reader.read_to_end(&mut data)?;
134            let decoded = Mdf::unpack(MemReaderRef::new(&data))?;
135            return Self::new(MemReader::new(decoded), filename, config);
136        }
137        reader.rewind()?;
138        let mut psb = PsbReader::open_psb(reader)
139            .map_err(|e| anyhow::anyhow!("Failed to open PSB from {}: {:?}", filename, e))?;
140        let psb = psb
141            .load()
142            .map_err(|e| anyhow::anyhow!("Failed to load PSB from {}: {:?}", filename, e))?;
143        Ok(Self {
144            psb: psb.to_psb_fixed(),
145            language_index: config.kirikiri_language_index.unwrap_or(0),
146            export_comumode: config.kirikiri_export_comumode,
147            filename: filename.to_string(),
148            comumode_json: config.kirikiri_comumode_json.clone(),
149            custom_yaml: config.custom_yaml,
150        })
151    }
152}
153
154impl Script for ScnScript {
155    fn default_output_script_type(&self) -> OutputScriptType {
156        OutputScriptType::Json
157    }
158
159    fn default_format_type(&self) -> FormatOptions {
160        FormatOptions::None
161    }
162
163    fn is_output_supported(&self, _: OutputScriptType) -> bool {
164        true
165    }
166
167    fn custom_output_extension<'a>(&'a self) -> &'a str {
168        if self.custom_yaml { "yaml" } else { "json" }
169    }
170
171    fn extract_messages(&self) -> Result<Vec<Message>> {
172        let mut messages = Vec::new();
173        let root = self.psb.root();
174        let scenes = root
175            .get_value("scenes")
176            .ok_or(anyhow::anyhow!("scenes not found"))?;
177        let scenes = match scenes {
178            PsbValueFixed::List(list) => list,
179            _ => return Err(anyhow::anyhow!("scenes is not a list")),
180        };
181        let mut comu = if self.export_comumode {
182            Some(ExportComuMes::new())
183        } else {
184            None
185        };
186        for (i, oscene) in scenes.iter().enumerate() {
187            let scene = match oscene {
188                PsbValueFixed::Object(obj) => obj,
189                _ => return Err(anyhow::anyhow!("scene at index {} is not an object", i)),
190            };
191            if let Some(PsbValueFixed::List(texts)) = scene.get_value("texts") {
192                for (j, text) in texts.iter().enumerate() {
193                    if let PsbValueFixed::List(text) = text {
194                        let values = text.values();
195                        if values.len() <= 1 {
196                            continue; // Skip if there are not enough values
197                        }
198                        let name = &values[0];
199                        let name = match name {
200                            PsbValueFixed::String(s) => Some(s),
201                            PsbValueFixed::Null => None,
202                            _ => return Err(anyhow::anyhow!("name is not a string or null")),
203                        };
204                        let mut display_name;
205                        let mut message;
206                        if matches!(values[1], PsbValueFixed::List(_)) {
207                            display_name = None;
208                            message = &values[1];
209                        } else {
210                            if values.len() <= 2 {
211                                continue; // Skip if there is no message
212                            }
213                            display_name = match &values[1] {
214                                PsbValueFixed::String(s) => Some(s),
215                                PsbValueFixed::Null => None,
216                                _ => {
217                                    return Err(anyhow::anyhow!(
218                                        "display name is not a string or null at {i},{j}"
219                                    ));
220                                }
221                            };
222                            message = &values[2];
223                        }
224                        if matches!(message, PsbValueFixed::List(_)) {
225                            let tmp = message;
226                            if let PsbValueFixed::List(list) = tmp {
227                                if list.len() > self.language_index {
228                                    if let PsbValueFixed::List(data) =
229                                        &list.values()[self.language_index]
230                                    {
231                                        if data.len() >= 2 {
232                                            let data = data.values();
233                                            display_name = match &data[0] {
234                                                PsbValueFixed::String(s) => Some(s),
235                                                PsbValueFixed::Null => None,
236                                                _ => {
237                                                    return Err(anyhow::anyhow!(
238                                                        "display name is not a string or null at {i},{j}"
239                                                    ));
240                                                }
241                                            };
242                                            message = &data[1];
243                                        }
244                                    }
245                                }
246                            }
247                        }
248                        if let PsbValueFixed::String(message) = message {
249                            match name {
250                                Some(name) => {
251                                    let name = match display_name {
252                                        Some(name) => name.string(),
253                                        None => name.string(),
254                                    };
255                                    let message = message.string();
256                                    messages.push(Message {
257                                        name: Some(name.to_string()),
258                                        message: message.replace("\\n", "\n"),
259                                    });
260                                }
261                                None => {
262                                    let message = message.string();
263                                    messages.push(Message {
264                                        name: None,
265                                        message: message.replace("\\n", "\n"),
266                                    });
267                                }
268                            }
269                        }
270                    }
271                }
272            }
273            if let Some(PsbValueFixed::List(selects)) = scene.get_value("selects") {
274                for select in selects.iter() {
275                    if let PsbValueFixed::Object(select) = select {
276                        let mut text = None;
277                        if let Some(PsbValueFixed::List(language)) = select.get_value("language") {
278                            if language.len() > self.language_index {
279                                let v = &language.values()[self.language_index];
280                                if let PsbValueFixed::Object(v) = v {
281                                    text = match v.get_value("text") {
282                                        Some(PsbValueFixed::String(s)) => Some(s),
283                                        Some(PsbValueFixed::Null) => None,
284                                        None => None,
285                                        _ => {
286                                            return Err(anyhow::anyhow!(
287                                                "select text is not a string or null"
288                                            ));
289                                        }
290                                    }
291                                }
292                            }
293                        }
294                        if text.is_none() {
295                            text = match select.get_value("text") {
296                                Some(PsbValueFixed::String(s)) => Some(s),
297                                Some(PsbValueFixed::Null) => None,
298                                None => None,
299                                _ => {
300                                    return Err(anyhow::anyhow!(
301                                        "select text is not a string or null"
302                                    ));
303                                }
304                            };
305                        }
306                        if let Some(text) = text {
307                            let text = text.string();
308                            messages.push(Message {
309                                name: None,
310                                message: text.replace("\\n", "\n"),
311                            });
312                        }
313                    }
314                }
315            }
316            comu.as_mut().map(|c| c.export(&oscene));
317        }
318        if let Some(comu) = comu {
319            if !comu.messages.is_empty() {
320                let mut pb = std::path::PathBuf::from(&self.filename);
321                let filename = pb
322                    .file_stem()
323                    .map(|s| s.to_string_lossy())
324                    .unwrap_or(std::borrow::Cow::from("comumode"));
325                pb.set_file_name(format!("{}_comumode.json", filename));
326                match std::fs::File::create(&pb) {
327                    Ok(mut f) => {
328                        let messages: Vec<String> = comu.messages.into_iter().collect();
329                        if let Err(e) = serde_json::to_writer_pretty(&mut f, &messages) {
330                            eprintln!("Failed to write COMU messages to {}: {:?}", pb.display(), e);
331                            crate::COUNTER.inc_warning();
332                        }
333                    }
334                    Err(e) => {
335                        eprintln!(
336                            "Failed to create COMU messages file {}: {:?}",
337                            pb.display(),
338                            e
339                        );
340                        crate::COUNTER.inc_warning();
341                    }
342                }
343            }
344        }
345        Ok(messages)
346    }
347
348    fn import_messages<'a>(
349        &'a self,
350        messages: Vec<Message>,
351        file: Box<dyn WriteSeek + 'a>,
352        _filename: &str,
353        _encoding: Encoding,
354        replacement: Option<&'a ReplacementTable>,
355    ) -> Result<()> {
356        let mut mes = messages.iter();
357        let mut cur_mes = mes.next();
358        let mut psb = self.psb.clone();
359        let root = psb.root_mut();
360        let scenes = &mut root["scenes"];
361        if !scenes.is_list() {
362            return Err(anyhow::anyhow!("scenes is not an array"));
363        }
364        let comu = self
365            .comumode_json
366            .as_ref()
367            .map(|json| ImportComuMes::new(json, replacement));
368        for (i, scene) in scenes.members_mut().enumerate() {
369            if !scene.is_object() {
370                return Err(anyhow::anyhow!("scene at {} is not an object", i));
371            }
372            for (j, text) in scene["texts"].members_mut().enumerate() {
373                if text.is_list() {
374                    if text.len() <= 1 {
375                        continue; // Skip if there are not enough values
376                    }
377                    if cur_mes.is_none() {
378                        cur_mes = mes.next();
379                    }
380                    if !text[0].is_string_or_null() {
381                        return Err(anyhow::anyhow!("name is not a string or null"));
382                    }
383                    let has_name = text[0].is_string();
384                    let mut has_display_name;
385                    if text[1].is_list() {
386                        if text[1].is_string() {
387                            let m = match cur_mes.take() {
388                                Some(m) => m,
389                                None => {
390                                    return Err(anyhow::anyhow!(
391                                        "No enough messages. (text {j} at scene {i})"
392                                    ));
393                                }
394                            };
395                            if has_name {
396                                if let Some(name) = &m.name {
397                                    let mut name = name.clone();
398                                    if let Some(replacement) = replacement {
399                                        for (key, value) in replacement.map.iter() {
400                                            name = name.replace(key, value);
401                                        }
402                                    }
403                                    text[0].set_string(name);
404                                } else {
405                                    return Err(anyhow::anyhow!(
406                                        "Name is missing for message. (text {j} at scene {i})"
407                                    ));
408                                }
409                            }
410                            let mut message = m.message.clone();
411                            if let Some(replacement) = replacement {
412                                for (key, value) in replacement.map.iter() {
413                                    message = message.replace(key, value);
414                                }
415                            }
416                            text[1].set_string(message.replace("\n", "\\n"));
417                        } else if text[1].is_list() {
418                            if text[1].len() > self.language_index
419                                && text[1][self.language_index].is_list()
420                                && text[1][self.language_index].len() >= 2
421                            {
422                                if !text[1][self.language_index][0].is_string_or_null() {
423                                    return Err(anyhow::anyhow!(
424                                        "display name is not a string or null"
425                                    ));
426                                }
427                                has_display_name = text[1][self.language_index][0].is_string();
428                                if text[1][self.language_index][1].is_string() {
429                                    let m = match cur_mes.take() {
430                                        Some(m) => m,
431                                        None => {
432                                            return Err(anyhow::anyhow!(
433                                                "No enough messages. (text {j} at scene {i})"
434                                            ));
435                                        }
436                                    };
437                                    if has_name {
438                                        if let Some(name) = &m.name {
439                                            let mut name = name.clone();
440                                            if let Some(replacement) = replacement {
441                                                for (key, value) in replacement.map.iter() {
442                                                    name = name.replace(key, value);
443                                                }
444                                            }
445                                            if has_display_name {
446                                                text[1][self.language_index][0].set_string(name);
447                                            } else {
448                                                text[0].set_string(name);
449                                            }
450                                        } else {
451                                            return Err(anyhow::anyhow!(
452                                                "Name is missing for message. (text {j} at scene {i})"
453                                            ));
454                                        }
455                                    }
456                                    let mut message = m.message.clone();
457                                    if let Some(replacement) = replacement {
458                                        for (key, value) in replacement.map.iter() {
459                                            message = message.replace(key, value);
460                                        }
461                                    }
462                                    text[1][self.language_index][1]
463                                        .set_string(message.replace("\n", "\\n"));
464                                }
465                            }
466                        }
467                    } else {
468                        if text.len() <= 2 {
469                            continue; // Skip if there is no message
470                        }
471                        if !text[1].is_string_or_null() {
472                            return Err(anyhow::anyhow!("display name is not a string or null"));
473                        }
474                        has_display_name = text[1].is_string();
475                        if text[2].is_string() {
476                            let m = match cur_mes.take() {
477                                Some(m) => m,
478                                None => {
479                                    return Err(anyhow::anyhow!(
480                                        "No enough messages.(text {j} at scene {i})"
481                                    ));
482                                }
483                            };
484                            if has_name {
485                                if let Some(name) = &m.name {
486                                    let mut name = name.clone();
487                                    if let Some(replacement) = replacement {
488                                        for (key, value) in replacement.map.iter() {
489                                            name = name.replace(key, value);
490                                        }
491                                    }
492                                    if has_display_name {
493                                        text[1].set_string(name);
494                                    } else {
495                                        text[0].set_string(name);
496                                    }
497                                } else {
498                                    return Err(anyhow::anyhow!(
499                                        "Name is missing for message.(text {j} at scene {i})"
500                                    ));
501                                }
502                            }
503                            let mut message = m.message.clone();
504                            if let Some(replacement) = replacement {
505                                for (key, value) in replacement.map.iter() {
506                                    message = message.replace(key, value);
507                                }
508                            }
509                            text[2].set_string(message.replace("\n", "\\n"));
510                        } else if text[2].is_list() {
511                            if text[2].len() > self.language_index
512                                && text[2][self.language_index].is_list()
513                                && text[2][self.language_index].len() >= 2
514                            {
515                                if !text[2][self.language_index][0].is_string_or_null() {
516                                    return Err(anyhow::anyhow!(
517                                        "display name is not a string or null"
518                                    ));
519                                }
520                                has_display_name = text[2][self.language_index][0].is_string();
521                                if text[2][self.language_index][1].is_string() {
522                                    let m = match cur_mes.take() {
523                                        Some(m) => m,
524                                        None => {
525                                            return Err(anyhow::anyhow!(
526                                                "No enough messages.(text {j} at scene {i})"
527                                            ));
528                                        }
529                                    };
530                                    if has_name {
531                                        if let Some(name) = &m.name {
532                                            let mut name = name.clone();
533                                            if let Some(replacement) = replacement {
534                                                for (key, value) in replacement.map.iter() {
535                                                    name = name.replace(key, value);
536                                                }
537                                            }
538                                            if has_display_name {
539                                                text[2][self.language_index][0].set_string(name);
540                                            } else {
541                                                text[0].set_string(name);
542                                            }
543                                        } else {
544                                            return Err(anyhow::anyhow!(
545                                                "Name is missing for message.(text {j} at scene {i})"
546                                            ));
547                                        }
548                                    }
549                                    let mut message = m.message.clone();
550                                    if let Some(replacement) = replacement {
551                                        for (key, value) in replacement.map.iter() {
552                                            message = message.replace(key, value);
553                                        }
554                                    }
555                                    text[2][self.language_index][1]
556                                        .set_string(message.replace("\n", "\\n"));
557                                }
558                            }
559                        }
560                    }
561                }
562            }
563            for select in scene["selects"].members_mut() {
564                if select.is_object() {
565                    if cur_mes.is_none() {
566                        cur_mes = mes.next();
567                    }
568                    if select["language"].is_list()
569                        && select["language"].len() > self.language_index
570                        && select["language"][self.language_index].is_object()
571                    {
572                        let lang_obj = &mut select["language"][self.language_index];
573                        if lang_obj["text"].is_string() {
574                            let m = match cur_mes.take() {
575                                Some(m) => m,
576                                None => {
577                                    return Err(anyhow::anyhow!("No enough messages."));
578                                }
579                            };
580                            let mut text = m.message.clone();
581                            if let Some(replacement) = replacement {
582                                for (key, value) in replacement.map.iter() {
583                                    text = text.replace(key, value);
584                                }
585                            }
586                            lang_obj["text"].set_string(text.replace("\n", "\\n"));
587                            continue;
588                        }
589                    }
590                    if select["text"].is_string() {
591                        let m = match cur_mes.take() {
592                            Some(m) => m,
593                            None => {
594                                return Err(anyhow::anyhow!("No enough messages."));
595                            }
596                        };
597                        let mut text = m.message.clone();
598                        if let Some(replacement) = replacement {
599                            for (key, value) in replacement.map.iter() {
600                                text = text.replace(key, value);
601                            }
602                        }
603                        select["text"].set_string(text.replace("\n", "\\n"));
604                    }
605                }
606            }
607            comu.as_ref().map(|c| c.import(scene));
608        }
609        if cur_mes.is_some() || mes.next().is_some() {
610            return Err(anyhow::anyhow!("Some messages were not processed."));
611        }
612        let psb = psb.to_psb();
613        let writer = PsbWriter::new(psb, file);
614        writer.finish().map_err(|e| {
615            anyhow::anyhow!("Failed to write PSB to file {}: {:?}", self.filename, e)
616        })?;
617        Ok(())
618    }
619
620    fn custom_export(&self, filename: &Path, encoding: Encoding) -> Result<()> {
621        let s = if self.custom_yaml {
622            serde_yaml_ng::to_string(&self.psb)
623                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
624        } else {
625            json::stringify_pretty(self.psb.to_json(), 2)
626        };
627        let mut f = crate::utils::files::write_file(filename)?;
628        let b = encode_string(encoding, &s, false)?;
629        f.write_all(&b)?;
630        Ok(())
631    }
632
633    fn custom_import<'a>(
634        &'a self,
635        custom_filename: &'a str,
636        file: Box<dyn WriteSeek + 'a>,
637        _encoding: Encoding,
638        output_encoding: Encoding,
639    ) -> Result<()> {
640        let data = crate::utils::files::read_file(custom_filename)?;
641        let s = decode_to_string(output_encoding, &data, true)?;
642        let psb = if self.custom_yaml {
643            let data: VirtualPsbFixedData = serde_yaml_ng::from_str(&s)
644                .map_err(|e| anyhow::anyhow!("Failed to deserialize YAML: {}", e))?;
645            let mut psb = self.psb.clone();
646            psb.set_data(data);
647            psb.to_psb()
648        } else {
649            let json = json::parse(&s)?;
650            let mut psb = self.psb.clone();
651            psb.from_json(&json)?;
652            psb.to_psb()
653        };
654        let writer = PsbWriter::new(psb, file);
655        writer.finish().map_err(|e| {
656            anyhow::anyhow!("Failed to write PSB to file {}: {:?}", self.filename, e)
657        })?;
658        Ok(())
659    }
660}
661
662#[derive(Debug)]
663struct ExportComuMes {
664    pub messages: HashSet<String>,
665}
666
667impl ExportComuMes {
668    pub fn new() -> Self {
669        Self {
670            messages: HashSet::new(),
671        }
672    }
673
674    pub fn export(&mut self, value: &PsbValueFixed) {
675        match value {
676            PsbValueFixed::Object(obj) => {
677                for (k, v) in obj.iter() {
678                    if k == "comumode" {
679                        if let PsbValueFixed::List(list) = v {
680                            for item in list.iter() {
681                                if let PsbValueFixed::Object(obj) = item {
682                                    if let Some(PsbValueFixed::String(s)) = obj.get_value("text") {
683                                        self.messages.insert(s.string().replace("\\n", "\n"));
684                                    }
685                                }
686                            }
687                        }
688                    } else {
689                        self.export(v);
690                    }
691                }
692            }
693            PsbValueFixed::List(list) => {
694                let list = list.values();
695                if list.len() > 1 {
696                    if let PsbValueFixed::String(s) = &list[0] {
697                        if s.string() == "comumode" {
698                            for i in 1..list.len() {
699                                if let PsbValueFixed::String(s) = &list[i - 1] {
700                                    if s.string() == "text" {
701                                        if let PsbValueFixed::String(text) = &list[i] {
702                                            self.messages
703                                                .insert(text.string().replace("\\n", "\n"));
704                                        }
705                                    }
706                                }
707                            }
708                            return;
709                        }
710                    }
711                }
712                for item in list {
713                    self.export(item);
714                }
715            }
716            _ => {}
717        }
718    }
719}
720
721#[derive(Debug)]
722struct ImportComuMes<'a> {
723    messages: &'a Arc<HashMap<String, String>>,
724    replacement: Option<&'a ReplacementTable>,
725}
726
727impl<'a> ImportComuMes<'a> {
728    pub fn new(
729        messages: &'a Arc<HashMap<String, String>>,
730        replacement: Option<&'a ReplacementTable>,
731    ) -> Self {
732        Self {
733            messages,
734            replacement,
735        }
736    }
737
738    pub fn import(&self, value: &mut PsbValueFixed) {
739        match value {
740            PsbValueFixed::Object(obj) => {
741                for (k, v) in obj.iter_mut() {
742                    if k == "comumode" {
743                        for obj in v.members_mut() {
744                            if let Some(text) = obj["text"].as_str() {
745                                if let Some(replace_text) = self.messages.get(text) {
746                                    let mut text = replace_text.clone();
747                                    if let Some(replacement) = self.replacement {
748                                        for (key, value) in replacement.map.iter() {
749                                            text = text.replace(key, value);
750                                        }
751                                    }
752                                    obj["text"].set_string(text.replace("\n", "\\n"));
753                                } else {
754                                    eprintln!(
755                                        "Warning: COMU message '{}' not found in translation table.",
756                                        text
757                                    );
758                                    crate::COUNTER.inc_warning();
759                                }
760                            }
761                        }
762                    } else {
763                        self.import(v);
764                    }
765                }
766            }
767            PsbValueFixed::List(list) => {
768                if list.len() > 1 {
769                    if list[0] == "comumode" {
770                        for i in 1..list.len() {
771                            if list[i - 1] == "text" {
772                                if let Some(text) = list[i].as_str() {
773                                    if let Some(replace_text) = self.messages.get(text) {
774                                        let mut text = replace_text.clone();
775                                        if let Some(replacement) = self.replacement {
776                                            for (key, value) in replacement.map.iter() {
777                                                text = text.replace(key, value);
778                                            }
779                                        }
780                                        list[i].set_string(text.replace("\n", "\\n"));
781                                    } else {
782                                        eprintln!(
783                                            "Warning: COMU message '{}' not found in translation table.",
784                                            text
785                                        );
786                                        crate::COUNTER.inc_warning();
787                                    }
788                                }
789                            }
790                        }
791                        return;
792                    }
793                }
794                for item in list.iter_mut() {
795                    self.import(item);
796                }
797            }
798            _ => {}
799        }
800    }
801}