msg_tool/
main.rs

1#![cfg_attr(any(docsrs, feature = "unstable"), feature(doc_cfg))]
2pub mod args;
3pub mod ext;
4pub mod format;
5pub mod output_scripts;
6pub mod scripts;
7pub mod types;
8pub mod utils;
9
10use ext::path::PathBufExt;
11use scripts::base::ArchiveContent;
12
13fn escape_dep_string(s: &str) -> String {
14    s.replace("\\", "\\\\").replace(" ", "\\ ")
15}
16
17fn get_encoding(
18    arg: &args::Arg,
19    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
20) -> types::Encoding {
21    match &arg.encoding {
22        Some(enc) => {
23            return match enc {
24                &types::TextEncoding::Default => builder.default_encoding(),
25                &types::TextEncoding::Auto => types::Encoding::Auto,
26                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
27                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
28                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
29            };
30        }
31        None => {}
32    }
33    #[cfg(windows)]
34    match &arg.code_page {
35        Some(code_page) => {
36            return types::Encoding::CodePage(*code_page);
37        }
38        None => {}
39    }
40    builder.default_encoding()
41}
42
43fn get_archived_encoding(
44    arg: &args::Arg,
45    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
46    encoding: types::Encoding,
47) -> types::Encoding {
48    match &arg.archive_encoding {
49        Some(enc) => {
50            return match enc {
51                &types::TextEncoding::Default => builder
52                    .default_archive_encoding()
53                    .unwrap_or_else(|| builder.default_encoding()),
54                &types::TextEncoding::Auto => types::Encoding::Auto,
55                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
56                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
57                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
58            };
59        }
60        None => {}
61    }
62    #[cfg(windows)]
63    match &arg.archive_code_page {
64        Some(code_page) => {
65            return types::Encoding::CodePage(*code_page);
66        }
67        None => {}
68    }
69    builder.default_archive_encoding().unwrap_or(encoding)
70}
71
72fn get_input_output_script_encoding(arg: &args::Arg) -> types::Encoding {
73    match &arg.encoding {
74        Some(enc) => {
75            return match enc {
76                &types::TextEncoding::Default => types::Encoding::Utf8,
77                &types::TextEncoding::Auto => types::Encoding::Utf8,
78                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
79                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
80                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
81            };
82        }
83        None => {}
84    }
85    #[cfg(windows)]
86    match &arg.code_page {
87        Some(code_page) => {
88            return types::Encoding::CodePage(*code_page);
89        }
90        None => {}
91    }
92    types::Encoding::Utf8
93}
94
95fn get_output_encoding(arg: &args::Arg) -> types::Encoding {
96    match &arg.output_encoding {
97        Some(enc) => {
98            return match enc {
99                &types::TextEncoding::Default => types::Encoding::Utf8,
100                &types::TextEncoding::Auto => types::Encoding::Utf8,
101                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
102                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
103                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
104            };
105        }
106        None => {}
107    }
108    #[cfg(windows)]
109    match &arg.output_code_page {
110        Some(code_page) => {
111            return types::Encoding::CodePage(*code_page);
112        }
113        None => {}
114    }
115    types::Encoding::Utf8
116}
117
118fn get_patched_encoding(
119    arg: &args::ImportArgs,
120    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
121) -> types::Encoding {
122    match &arg.patched_encoding {
123        Some(enc) => {
124            return match enc {
125                &types::TextEncoding::Default => builder.default_patched_encoding(),
126                &types::TextEncoding::Auto => types::Encoding::Utf8,
127                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
128                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
129                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
130            };
131        }
132        None => {}
133    }
134    #[cfg(windows)]
135    match &arg.patched_code_page {
136        Some(code_page) => {
137            return types::Encoding::CodePage(*code_page);
138        }
139        None => {}
140    }
141    builder.default_patched_encoding()
142}
143
144fn get_patched_archive_encoding(
145    arg: &args::ImportArgs,
146    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
147    encoding: types::Encoding,
148) -> types::Encoding {
149    match &arg.patched_archive_encoding {
150        Some(enc) => {
151            return match enc {
152                &types::TextEncoding::Default => {
153                    builder.default_archive_encoding().unwrap_or(encoding)
154                }
155                &types::TextEncoding::Auto => types::Encoding::Utf8,
156                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
157                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
158                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
159            };
160        }
161        None => {}
162    }
163    #[cfg(windows)]
164    match &arg.patched_archive_code_page {
165        Some(code_page) => {
166            return types::Encoding::CodePage(*code_page);
167        }
168        None => {}
169    }
170    builder.default_archive_encoding().unwrap_or(encoding)
171}
172
173pub fn parse_script(
174    filename: &str,
175    arg: &args::Arg,
176    config: std::sync::Arc<types::ExtraConfig>,
177) -> anyhow::Result<(
178    Box<dyn scripts::Script>,
179    &'static Box<dyn scripts::ScriptBuilder + Send + Sync>,
180)> {
181    match &arg.script_type {
182        Some(typ) => {
183            for builder in scripts::BUILDER.iter() {
184                if typ == builder.script_type() {
185                    let encoding = get_encoding(arg, builder);
186                    let archive_encoding = get_archived_encoding(arg, builder, encoding);
187                    return Ok((
188                        builder.build_script_from_file(
189                            filename,
190                            encoding,
191                            archive_encoding,
192                            &config,
193                            None,
194                        )?,
195                        builder,
196                    ));
197                }
198            }
199        }
200        _ => {}
201    }
202    let mut exts_builder = Vec::new();
203    for builder in scripts::BUILDER.iter() {
204        let exts = builder.extensions();
205        for ext in exts {
206            if filename.to_lowercase().ends_with(ext) {
207                exts_builder.push(builder);
208                break;
209            }
210        }
211    }
212    let exts_builder = if exts_builder.is_empty() {
213        scripts::BUILDER.iter().collect::<Vec<_>>()
214    } else {
215        exts_builder
216    };
217    if exts_builder.len() == 1 {
218        let builder = exts_builder.first().unwrap();
219        let encoding = get_encoding(arg, builder);
220        let archive_encoding = get_archived_encoding(arg, builder, encoding);
221        return Ok((
222            builder.build_script_from_file(filename, encoding, archive_encoding, &config, None)?,
223            builder,
224        ));
225    }
226    let mut buf = [0u8; 1024];
227    let mut size = 0;
228    if filename != "-" {
229        let mut f = std::fs::File::open(filename)?;
230        size = std::io::Read::read(&mut f, &mut buf)?;
231    }
232    let mut scores = Vec::new();
233    for builder in exts_builder.iter() {
234        if let Some(score) = builder.is_this_format(filename, &buf, size) {
235            scores.push((score, builder));
236        }
237    }
238    if scores.is_empty() {
239        return Err(anyhow::anyhow!("Unsupported script type"));
240    }
241    let max_score = scores.iter().map(|s| s.0).max().unwrap();
242    let mut best_builders = Vec::new();
243    for (score, builder) in scores.iter() {
244        if *score == max_score {
245            best_builders.push(builder);
246        }
247    }
248    if best_builders.len() == 1 {
249        let builder = best_builders.first().unwrap();
250        let encoding = get_encoding(arg, builder);
251        let archive_encoding = get_archived_encoding(arg, builder, encoding);
252        return Ok((
253            builder.build_script_from_file(filename, encoding, archive_encoding, &config, None)?,
254            builder,
255        ));
256    }
257    if best_builders.len() > 1 {
258        eprintln!(
259            "Multiple script types found for {}: {:?}",
260            filename, best_builders
261        );
262        return Err(anyhow::anyhow!("Multiple script types found"));
263    }
264    Err(anyhow::anyhow!("Unsupported script type"))
265}
266
267pub fn parse_script_from_archive<'a>(
268    file: &mut Box<dyn ArchiveContent + 'a>,
269    arg: &args::Arg,
270    config: std::sync::Arc<types::ExtraConfig>,
271    archive: &Box<dyn scripts::Script>,
272) -> anyhow::Result<(
273    Box<dyn scripts::Script>,
274    &'static Box<dyn scripts::ScriptBuilder + Send + Sync>,
275)> {
276    match file.script_type() {
277        Some(typ) => {
278            for builder in scripts::BUILDER.iter() {
279                if typ == builder.script_type() {
280                    let encoding = get_encoding(arg, builder);
281                    let archive_encoding = get_archived_encoding(arg, builder, encoding);
282                    return Ok((
283                        builder.build_script(
284                            file.data()?,
285                            file.name(),
286                            encoding,
287                            archive_encoding,
288                            &config,
289                            Some(archive),
290                        )?,
291                        builder,
292                    ));
293                }
294            }
295        }
296        _ => {}
297    }
298    let mut exts_builder = Vec::new();
299    for builder in scripts::BUILDER.iter() {
300        let exts = builder.extensions();
301        for ext in exts {
302            if file.name().to_lowercase().ends_with(ext) {
303                exts_builder.push(builder);
304                break;
305            }
306        }
307    }
308    let exts_builder = if exts_builder.is_empty() {
309        scripts::BUILDER.iter().collect::<Vec<_>>()
310    } else {
311        exts_builder
312    };
313    if exts_builder.len() == 1 {
314        let builder = exts_builder.first().unwrap();
315        let encoding = get_encoding(arg, builder);
316        let archive_encoding = get_archived_encoding(arg, builder, encoding);
317        return Ok((
318            builder.build_script(
319                file.data()?,
320                file.name(),
321                encoding,
322                archive_encoding,
323                &config,
324                Some(archive),
325            )?,
326            builder,
327        ));
328    }
329    let buf = file.data()?;
330    let mut scores = Vec::new();
331    for builder in exts_builder.iter() {
332        if let Some(score) = builder.is_this_format(file.name(), buf.as_slice(), buf.len()) {
333            scores.push((score, builder));
334        }
335    }
336    if scores.is_empty() {
337        return Err(anyhow::anyhow!("Unsupported script type"));
338    }
339    let max_score = scores.iter().map(|s| s.0).max().unwrap();
340    let mut best_builders = Vec::new();
341    for (score, builder) in scores.iter() {
342        if *score == max_score {
343            best_builders.push(builder);
344        }
345    }
346    if best_builders.len() == 1 {
347        let builder = best_builders.first().unwrap();
348        let encoding = get_encoding(arg, builder);
349        let archive_encoding = get_archived_encoding(arg, builder, encoding);
350        return Ok((
351            builder.build_script(
352                buf,
353                file.name(),
354                encoding,
355                archive_encoding,
356                &config,
357                Some(archive),
358            )?,
359            builder,
360        ));
361    }
362    if best_builders.len() > 1 {
363        eprintln!(
364            "Multiple script types found for {}: {:?}",
365            file.name(),
366            best_builders
367        );
368        return Err(anyhow::anyhow!("Multiple script types found"));
369    }
370    Err(anyhow::anyhow!("Unsupported script type"))
371}
372
373pub fn export_script(
374    filename: &str,
375    arg: &args::Arg,
376    config: std::sync::Arc<types::ExtraConfig>,
377    output: &Option<String>,
378    root_dir: Option<&std::path::Path>,
379    #[cfg(feature = "image")] img_threadpool: Option<
380        &utils::threadpool::ThreadPool<Result<(), anyhow::Error>>,
381    >,
382) -> anyhow::Result<types::ScriptResult> {
383    eprintln!("Exporting {}", filename);
384    let script = parse_script(filename, arg, config.clone())?.0;
385    if script.is_archive() {
386        let odir = match output.as_ref() {
387            Some(output) => {
388                let mut pb = std::path::PathBuf::from(output);
389                let filename = std::path::PathBuf::from(filename);
390                if let Some(root_dir) = root_dir {
391                    let rpath = utils::files::relative_path(root_dir, &filename);
392                    if let Some(parent) = rpath.parent() {
393                        pb.push(parent);
394                    }
395                    if let Some(fname) = filename.file_name() {
396                        pb.push(fname);
397                    }
398                }
399                pb.set_extension("");
400                if let Some(ext) = script.archive_output_ext() {
401                    pb.set_extension(ext);
402                }
403                pb.to_string_lossy().into_owned()
404            }
405            None => {
406                let mut pb = std::path::PathBuf::from(filename);
407                pb.set_extension("");
408                if let Some(ext) = script.archive_output_ext() {
409                    pb.set_extension(ext);
410                }
411                pb.to_string_lossy().into_owned()
412            }
413        };
414        if !std::fs::exists(&odir)? {
415            std::fs::create_dir_all(&odir)?;
416        }
417        for (i, filename) in script.iter_archive_filename()?.enumerate() {
418            let filename = match filename {
419                Ok(f) => f,
420                Err(e) => {
421                    eprintln!("Error reading archive filename: {}", e);
422                    COUNTER.inc_error();
423                    if arg.backtrace {
424                        eprintln!("Backtrace: {}", e.backtrace());
425                    }
426                    continue;
427                }
428            };
429            let mut f = match script.open_file(i) {
430                Ok(f) => f,
431                Err(e) => {
432                    eprintln!("Error opening file {}: {}", filename, e);
433                    COUNTER.inc_error();
434                    if arg.backtrace {
435                        eprintln!("Backtrace: {}", e.backtrace());
436                    }
437                    continue;
438                }
439            };
440            if arg.force_script || f.is_script() {
441                let (script_file, _) =
442                    match parse_script_from_archive(&mut f, arg, config.clone(), &script) {
443                        Ok(s) => s,
444                        Err(e) => {
445                            eprintln!("Error parsing script '{}' from archive: {}", filename, e);
446                            COUNTER.inc_error();
447                            if arg.backtrace {
448                                eprintln!("Backtrace: {}", e.backtrace());
449                            }
450                            continue;
451                        }
452                    };
453                #[cfg(feature = "image")]
454                if script_file.is_image() {
455                    if script_file.is_multi_image() {
456                        for i in script_file.export_multi_image()? {
457                            let img_data = match i {
458                                Ok(data) => data,
459                                Err(e) => {
460                                    eprintln!("Error exporting image: {}", e);
461                                    COUNTER.inc_error();
462                                    if arg.backtrace {
463                                        eprintln!("Backtrace: {}", e.backtrace());
464                                    }
465                                    continue;
466                                }
467                            };
468                            let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
469                            let mut out_path = std::path::PathBuf::from(&odir);
470                            if !arg.image_output_flat {
471                                out_path.push(f.name());
472                                out_path.set_extension("");
473                                out_path.push(img_data.name);
474                            } else {
475                                let name = std::path::Path::new(f.name());
476                                out_path.push(format!(
477                                    "{}_{}",
478                                    name.file_stem().unwrap_or_default().to_string_lossy(),
479                                    img_data.name
480                                ));
481                            }
482                            out_path.set_extension(out_type.as_ref());
483                            match utils::files::make_sure_dir_exists(&out_path) {
484                                Ok(_) => {}
485                                Err(e) => {
486                                    eprintln!(
487                                        "Error creating parent directory for {}: {}",
488                                        out_path.display(),
489                                        e
490                                    );
491                                    COUNTER.inc_error();
492                                    continue;
493                                }
494                            }
495                            if let Some(threadpool) = img_threadpool {
496                                let outpath = out_path.to_string_lossy().into_owned();
497                                let config = config.clone();
498                                threadpool.execute(
499                                    move |_| {
500                                        utils::img::encode_img(
501                                            img_data.data,
502                                            out_type,
503                                            &outpath,
504                                            &config,
505                                        )
506                                        .map_err(|e| {
507                                            anyhow::anyhow!(
508                                                "Failed to encode image {}: {}",
509                                                outpath,
510                                                e
511                                            )
512                                        })
513                                    },
514                                    true,
515                                )?;
516                                continue;
517                            } else {
518                                match utils::img::encode_img(
519                                    img_data.data,
520                                    out_type,
521                                    &out_path.to_string_lossy(),
522                                    &config,
523                                ) {
524                                    Ok(_) => {}
525                                    Err(e) => {
526                                        eprintln!("Error encoding image: {}", e);
527                                        COUNTER.inc_error();
528                                        continue;
529                                    }
530                                }
531                                COUNTER.inc(types::ScriptResult::Ok);
532                            }
533                        }
534                        COUNTER.inc(types::ScriptResult::Ok);
535                        continue;
536                    }
537                    let img_data = match script_file.export_image() {
538                        Ok(data) => data,
539                        Err(e) => {
540                            eprintln!("Error exporting image: {}", e);
541                            COUNTER.inc_error();
542                            if arg.backtrace {
543                                eprintln!("Backtrace: {}", e.backtrace());
544                            }
545                            continue;
546                        }
547                    };
548                    let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
549                    let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
550                    out_path.set_extension(out_type.as_ref());
551                    match utils::files::make_sure_dir_exists(&out_path) {
552                        Ok(_) => {}
553                        Err(e) => {
554                            eprintln!(
555                                "Error creating parent directory for {}: {}",
556                                out_path.display(),
557                                e
558                            );
559                            COUNTER.inc_error();
560                            continue;
561                        }
562                    }
563                    if let Some(threadpool) = img_threadpool {
564                        let outpath = out_path.to_string_lossy().into_owned();
565                        let config = config.clone();
566                        threadpool.execute(
567                            move |_| {
568                                utils::img::encode_img(img_data, out_type, &outpath, &config)
569                                    .map_err(|e| {
570                                        anyhow::anyhow!("Failed to encode image {}: {}", outpath, e)
571                                    })
572                            },
573                            true,
574                        )?;
575                        continue;
576                    } else {
577                        match utils::img::encode_img(
578                            img_data,
579                            out_type,
580                            &out_path.to_string_lossy(),
581                            &config,
582                        ) {
583                            Ok(_) => {}
584                            Err(e) => {
585                                eprintln!("Error encoding image: {}", e);
586                                COUNTER.inc_error();
587                                continue;
588                            }
589                        }
590                        COUNTER.inc(types::ScriptResult::Ok);
591                    }
592                    continue;
593                }
594                let mut of = match &arg.output_type {
595                    Some(t) => t.clone(),
596                    None => script_file.default_output_script_type(),
597                };
598                if !script_file.is_output_supported(of) {
599                    of = script_file.default_output_script_type();
600                }
601                if !arg.no_multi_message && !of.is_custom() && script_file.multiple_message_files()
602                {
603                    let mmes = script_file.extract_multiple_messages()?;
604                    if mmes.is_empty() {
605                        eprintln!("No messages found in {}", f.name());
606                        COUNTER.inc(types::ScriptResult::Ignored);
607                        continue;
608                    }
609                    let ext = of.as_ref();
610                    let mut out_dir = std::path::PathBuf::from(&odir).join(f.name());
611                    if arg.output_no_extra_ext {
612                        out_dir.remove_all_extensions();
613                    } else {
614                        out_dir.set_extension("");
615                    }
616                    std::fs::create_dir_all(&out_dir)?;
617                    for (name, data) in mmes {
618                        let ofp = out_dir.join(name).with_extension(ext);
619                        match of {
620                            types::OutputScriptType::Json => {
621                                let enc = get_output_encoding(arg);
622                                let s = match serde_json::to_string_pretty(&data) {
623                                    Ok(s) => s,
624                                    Err(e) => {
625                                        eprintln!("Error serializing messages to JSON: {}", e);
626                                        COUNTER.inc_error();
627                                        continue;
628                                    }
629                                };
630                                let b = match utils::encoding::encode_string(enc, &s, false) {
631                                    Ok(b) => b,
632                                    Err(e) => {
633                                        eprintln!("Error encoding string: {}", e);
634                                        COUNTER.inc_error();
635                                        continue;
636                                    }
637                                };
638                                let mut f = match utils::files::write_file(&ofp) {
639                                    Ok(f) => f,
640                                    Err(e) => {
641                                        eprintln!("Error writing file {}: {}", ofp.display(), e);
642                                        COUNTER.inc_error();
643                                        continue;
644                                    }
645                                };
646                                match f.write_all(&b) {
647                                    Ok(_) => {}
648                                    Err(e) => {
649                                        eprintln!("Error writing to file {}: {}", ofp.display(), e);
650                                        COUNTER.inc_error();
651                                        continue;
652                                    }
653                                }
654                            }
655                            types::OutputScriptType::M3t
656                            | types::OutputScriptType::M3ta
657                            | types::OutputScriptType::M3tTxt => {
658                                let enc = get_output_encoding(arg);
659                                let s =
660                                    output_scripts::m3t::M3tDumper::dump(&data, arg.m3t_no_quote);
661                                let b = match utils::encoding::encode_string(enc, &s, false) {
662                                    Ok(b) => b,
663                                    Err(e) => {
664                                        eprintln!("Error encoding string: {}", e);
665                                        COUNTER.inc_error();
666                                        continue;
667                                    }
668                                };
669                                let mut f = match utils::files::write_file(&ofp) {
670                                    Ok(f) => f,
671                                    Err(e) => {
672                                        eprintln!("Error writing file {}: {}", ofp.display(), e);
673                                        COUNTER.inc_error();
674                                        continue;
675                                    }
676                                };
677                                match f.write_all(&b) {
678                                    Ok(_) => {}
679                                    Err(e) => {
680                                        eprintln!("Error writing to file {}: {}", ofp.display(), e);
681                                        COUNTER.inc_error();
682                                        continue;
683                                    }
684                                }
685                            }
686                            types::OutputScriptType::Yaml => {
687                                let enc = get_output_encoding(arg);
688                                let s = match serde_yaml_ng::to_string(&data) {
689                                    Ok(s) => s,
690                                    Err(e) => {
691                                        eprintln!("Error serializing messages to YAML: {}", e);
692                                        COUNTER.inc_error();
693                                        continue;
694                                    }
695                                };
696                                let b = match utils::encoding::encode_string(enc, &s, false) {
697                                    Ok(b) => b,
698                                    Err(e) => {
699                                        eprintln!("Error encoding string: {}", e);
700                                        COUNTER.inc_error();
701                                        continue;
702                                    }
703                                };
704                                let mut f = match utils::files::write_file(&ofp) {
705                                    Ok(f) => f,
706                                    Err(e) => {
707                                        eprintln!("Error writing file {}: {}", ofp.display(), e);
708                                        COUNTER.inc_error();
709                                        continue;
710                                    }
711                                };
712                                match f.write_all(&b) {
713                                    Ok(_) => {}
714                                    Err(e) => {
715                                        eprintln!("Error writing to file {}: {}", ofp.display(), e);
716                                        COUNTER.inc_error();
717                                        continue;
718                                    }
719                                }
720                            }
721                            types::OutputScriptType::Pot | types::OutputScriptType::Po => {
722                                let enc = get_output_encoding(arg);
723                                let s = match output_scripts::po::PoDumper::new().dump(&data, enc) {
724                                    Ok(s) => s,
725                                    Err(e) => {
726                                        eprintln!("Error dumping messages to PO format: {}", e);
727                                        COUNTER.inc_error();
728                                        continue;
729                                    }
730                                };
731                                let b = match utils::encoding::encode_string(enc, &s, false) {
732                                    Ok(b) => b,
733                                    Err(e) => {
734                                        eprintln!("Error encoding string: {}", e);
735                                        COUNTER.inc_error();
736                                        continue;
737                                    }
738                                };
739                                let mut f = match utils::files::write_file(&ofp) {
740                                    Ok(f) => f,
741                                    Err(e) => {
742                                        eprintln!("Error writing file {}: {}", ofp.display(), e);
743                                        COUNTER.inc_error();
744                                        continue;
745                                    }
746                                };
747                                match f.write_all(&b) {
748                                    Ok(_) => {}
749                                    Err(e) => {
750                                        eprintln!("Error writing to file {}: {}", ofp.display(), e);
751                                        COUNTER.inc_error();
752                                        continue;
753                                    }
754                                }
755                            }
756                            types::OutputScriptType::Custom => {}
757                        }
758                    }
759                    COUNTER.inc(types::ScriptResult::Ok);
760                    continue;
761                }
762                let mes = if of.is_custom() {
763                    Vec::new()
764                } else {
765                    match script_file.extract_messages() {
766                        Ok(mes) => mes,
767                        Err(e) => {
768                            eprintln!("Error extracting messages from {}: {}", f.name(), e);
769                            COUNTER.inc_error();
770                            if arg.backtrace {
771                                eprintln!("Backtrace: {}", e.backtrace());
772                            }
773                            continue;
774                        }
775                    }
776                };
777                if !of.is_custom() && mes.is_empty() {
778                    eprintln!("No messages found in {}", f.name());
779                    COUNTER.inc(types::ScriptResult::Ignored);
780                    continue;
781                }
782                let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
783                if arg.output_no_extra_ext {
784                    out_path.remove_all_extensions();
785                }
786                out_path.set_extension(if of.is_custom() {
787                    script_file.custom_output_extension()
788                } else {
789                    of.as_ref()
790                });
791                match utils::files::make_sure_dir_exists(&out_path) {
792                    Ok(_) => {}
793                    Err(e) => {
794                        eprintln!(
795                            "Error creating parent directory for {}: {}",
796                            out_path.display(),
797                            e
798                        );
799                        COUNTER.inc_error();
800                        continue;
801                    }
802                }
803                match of {
804                    types::OutputScriptType::Json => {
805                        let enc = get_output_encoding(arg);
806                        let s = match serde_json::to_string_pretty(&mes) {
807                            Ok(s) => s,
808                            Err(e) => {
809                                eprintln!("Error serializing messages to JSON: {}", e);
810                                COUNTER.inc_error();
811                                continue;
812                            }
813                        };
814                        let b = match utils::encoding::encode_string(enc, &s, false) {
815                            Ok(b) => b,
816                            Err(e) => {
817                                eprintln!("Error encoding string: {}", e);
818                                COUNTER.inc_error();
819                                continue;
820                            }
821                        };
822                        let mut f = match utils::files::write_file(&out_path) {
823                            Ok(f) => f,
824                            Err(e) => {
825                                eprintln!("Error writing file {}: {}", out_path.display(), e);
826                                COUNTER.inc_error();
827                                continue;
828                            }
829                        };
830                        match f.write_all(&b) {
831                            Ok(_) => {}
832                            Err(e) => {
833                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
834                                COUNTER.inc_error();
835                                continue;
836                            }
837                        }
838                    }
839                    types::OutputScriptType::M3t
840                    | types::OutputScriptType::M3ta
841                    | types::OutputScriptType::M3tTxt => {
842                        let enc = get_output_encoding(arg);
843                        let s = output_scripts::m3t::M3tDumper::dump(&mes, arg.m3t_no_quote);
844                        let b = match utils::encoding::encode_string(enc, &s, false) {
845                            Ok(b) => b,
846                            Err(e) => {
847                                eprintln!("Error encoding string: {}", e);
848                                COUNTER.inc_error();
849                                continue;
850                            }
851                        };
852                        let mut f = match utils::files::write_file(&out_path) {
853                            Ok(f) => f,
854                            Err(e) => {
855                                eprintln!("Error writing file {}: {}", out_path.display(), e);
856                                COUNTER.inc_error();
857                                continue;
858                            }
859                        };
860                        match f.write_all(&b) {
861                            Ok(_) => {}
862                            Err(e) => {
863                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
864                                COUNTER.inc_error();
865                                continue;
866                            }
867                        }
868                    }
869                    types::OutputScriptType::Yaml => {
870                        let enc = get_output_encoding(arg);
871                        let s = match serde_yaml_ng::to_string(&mes) {
872                            Ok(s) => s,
873                            Err(e) => {
874                                eprintln!("Error serializing messages to YAML: {}", e);
875                                COUNTER.inc_error();
876                                continue;
877                            }
878                        };
879                        let b = match utils::encoding::encode_string(enc, &s, false) {
880                            Ok(b) => b,
881                            Err(e) => {
882                                eprintln!("Error encoding string: {}", e);
883                                COUNTER.inc_error();
884                                continue;
885                            }
886                        };
887                        let mut f = match utils::files::write_file(&out_path) {
888                            Ok(f) => f,
889                            Err(e) => {
890                                eprintln!("Error writing file {}: {}", out_path.display(), e);
891                                COUNTER.inc_error();
892                                continue;
893                            }
894                        };
895                        match f.write_all(&b) {
896                            Ok(_) => {}
897                            Err(e) => {
898                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
899                                COUNTER.inc_error();
900                                continue;
901                            }
902                        }
903                    }
904                    types::OutputScriptType::Pot | types::OutputScriptType::Po => {
905                        let enc = get_output_encoding(arg);
906                        let s = match output_scripts::po::PoDumper::new().dump(&mes, enc) {
907                            Ok(s) => s,
908                            Err(e) => {
909                                eprintln!("Error dumping messages to PO format: {}", e);
910                                COUNTER.inc_error();
911                                continue;
912                            }
913                        };
914                        let b = match utils::encoding::encode_string(enc, &s, false) {
915                            Ok(b) => b,
916                            Err(e) => {
917                                eprintln!("Error encoding string: {}", e);
918                                COUNTER.inc_error();
919                                continue;
920                            }
921                        };
922                        let mut f = match utils::files::write_file(&out_path) {
923                            Ok(f) => f,
924                            Err(e) => {
925                                eprintln!("Error writing file {}: {}", out_path.display(), e);
926                                COUNTER.inc_error();
927                                continue;
928                            }
929                        };
930                        match f.write_all(&b) {
931                            Ok(_) => {}
932                            Err(e) => {
933                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
934                                COUNTER.inc_error();
935                                continue;
936                            }
937                        }
938                    }
939                    types::OutputScriptType::Custom => {
940                        let enc = get_output_encoding(arg);
941                        if let Err(e) = script_file.custom_export(&out_path, enc) {
942                            eprintln!("Error exporting custom script: {}", e);
943                            COUNTER.inc_error();
944                            continue;
945                        }
946                    }
947                }
948            } else {
949                let out_path = std::path::PathBuf::from(&odir).join(f.name());
950                match utils::files::make_sure_dir_exists(&out_path) {
951                    Ok(_) => {}
952                    Err(e) => {
953                        eprintln!(
954                            "Error creating parent directory for {}: {}",
955                            out_path.display(),
956                            e
957                        );
958                        COUNTER.inc_error();
959                        continue;
960                    }
961                }
962                match utils::files::write_file(&out_path) {
963                    Ok(mut fi) => match std::io::copy(&mut f, &mut fi) {
964                        Ok(_) => {}
965                        Err(e) => {
966                            eprintln!("Error writing to file {}: {}", out_path.display(), e);
967                            COUNTER.inc_error();
968                            continue;
969                        }
970                    },
971                    Err(e) => {
972                        eprintln!("Error writing file {}: {}", out_path.display(), e);
973                        COUNTER.inc_error();
974                        continue;
975                    }
976                }
977            }
978            COUNTER.inc(types::ScriptResult::Ok);
979        }
980        return Ok(types::ScriptResult::Ok);
981    }
982    #[cfg(feature = "image")]
983    if script.is_image() {
984        if script.is_multi_image() {
985            for i in script.export_multi_image()? {
986                let img_data = match i {
987                    Ok(data) => data,
988                    Err(e) => {
989                        eprintln!("Error exporting image: {}", e);
990                        COUNTER.inc_error();
991                        if arg.backtrace {
992                            eprintln!("Backtrace: {}", e.backtrace());
993                        }
994                        continue;
995                    }
996                };
997                let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
998                let f = match output.as_ref() {
999                    Some(output) => {
1000                        if let Some(root_dir) = root_dir {
1001                            let f = std::path::PathBuf::from(filename);
1002                            let mut pb = std::path::PathBuf::from(output);
1003                            let rpath = utils::files::relative_path(root_dir, &f);
1004                            if let Some(parent) = rpath.parent() {
1005                                pb.push(parent);
1006                            }
1007                            if !arg.image_output_flat {
1008                                if let Some(fname) = f.file_name() {
1009                                    pb.push(fname);
1010                                    if arg.output_no_extra_ext {
1011                                        pb.remove_all_extensions();
1012                                    } else {
1013                                        pb.set_extension("");
1014                                    }
1015                                }
1016                                pb.push(img_data.name);
1017                            } else {
1018                                pb.push(format!(
1019                                    "{}_{}",
1020                                    f.file_stem().unwrap_or_default().to_string_lossy(),
1021                                    img_data.name
1022                                ));
1023                            }
1024                            pb.set_extension(out_type.as_ref());
1025                            pb.to_string_lossy().into_owned()
1026                        } else {
1027                            let mut pb = std::path::PathBuf::from(output);
1028                            if arg.image_output_flat {
1029                                let f = std::path::PathBuf::from(filename);
1030                                pb.push(format!(
1031                                    "{}_{}",
1032                                    f.file_stem().unwrap_or_default().to_string_lossy(),
1033                                    img_data.name
1034                                ));
1035                            } else {
1036                                pb.push(img_data.name);
1037                                if arg.output_no_extra_ext {
1038                                    pb.remove_all_extensions();
1039                                } else {
1040                                    pb.set_extension("");
1041                                }
1042                            }
1043                            pb.set_extension(out_type.as_ref());
1044                            pb.to_string_lossy().into_owned()
1045                        }
1046                    }
1047                    None => {
1048                        let mut pb = std::path::PathBuf::from(filename);
1049                        if arg.image_output_flat {
1050                            let f = std::path::PathBuf::from(filename);
1051                            pb.set_file_name(format!(
1052                                "{}_{}",
1053                                f.file_stem().unwrap_or_default().to_string_lossy(),
1054                                img_data.name
1055                            ));
1056                        } else {
1057                            if arg.output_no_extra_ext {
1058                                pb.remove_all_extensions();
1059                            } else {
1060                                pb.set_extension("");
1061                            }
1062                            pb.push(img_data.name);
1063                        }
1064                        pb.set_extension(out_type.as_ref());
1065                        pb.to_string_lossy().into_owned()
1066                    }
1067                };
1068                match utils::files::make_sure_dir_exists(&f) {
1069                    Ok(_) => {}
1070                    Err(e) => {
1071                        eprintln!("Error creating parent directory for {}: {}", f, e);
1072                        COUNTER.inc_error();
1073                        continue;
1074                    }
1075                }
1076                if let Some(threadpool) = img_threadpool {
1077                    let outpath = f.clone();
1078                    let config = config.clone();
1079                    threadpool.execute(
1080                        move |_| {
1081                            utils::img::encode_img(img_data.data, out_type, &outpath, &config)
1082                                .map_err(|e| {
1083                                    anyhow::anyhow!("Failed to encode image {}: {}", outpath, e)
1084                                })
1085                        },
1086                        true,
1087                    )?;
1088                    continue;
1089                } else {
1090                    match utils::img::encode_img(img_data.data, out_type, &f, &config) {
1091                        Ok(_) => {}
1092                        Err(e) => {
1093                            eprintln!("Error encoding image: {}", e);
1094                            COUNTER.inc_error();
1095                            continue;
1096                        }
1097                    }
1098                    COUNTER.inc(types::ScriptResult::Ok);
1099                }
1100            }
1101            return Ok(types::ScriptResult::Ok);
1102        }
1103        let img_data = script.export_image()?;
1104        let out_type = arg.image_type.unwrap_or_else(|| {
1105            if root_dir.is_some() {
1106                types::ImageOutputType::Png
1107            } else {
1108                output
1109                    .as_ref()
1110                    .and_then(|s| types::ImageOutputType::try_from(std::path::Path::new(s)).ok())
1111                    .unwrap_or(types::ImageOutputType::Png)
1112            }
1113        });
1114        let f = if filename == "-" {
1115            String::from("-")
1116        } else {
1117            match output.as_ref() {
1118                Some(output) => {
1119                    if let Some(root_dir) = root_dir {
1120                        let f = std::path::PathBuf::from(filename);
1121                        let mut pb = std::path::PathBuf::from(output);
1122                        let rpath = utils::files::relative_path(root_dir, &f);
1123                        if let Some(parent) = rpath.parent() {
1124                            pb.push(parent);
1125                        }
1126                        if let Some(fname) = f.file_name() {
1127                            pb.push(fname);
1128                        }
1129                        if arg.output_no_extra_ext {
1130                            pb.remove_all_extensions();
1131                        }
1132                        pb.set_extension(out_type.as_ref());
1133                        pb.to_string_lossy().into_owned()
1134                    } else {
1135                        output.clone()
1136                    }
1137                }
1138                None => {
1139                    let mut pb = std::path::PathBuf::from(filename);
1140                    if arg.output_no_extra_ext {
1141                        pb.remove_all_extensions();
1142                    }
1143                    pb.set_extension(out_type.as_ref());
1144                    pb.to_string_lossy().into_owned()
1145                }
1146            }
1147        };
1148        utils::files::make_sure_dir_exists(&f)?;
1149        if let Some(threadpool) = img_threadpool {
1150            let outpath = f.clone();
1151            let config = config.clone();
1152            threadpool.execute(
1153                move |_| {
1154                    utils::img::encode_img(img_data, out_type, &outpath, &config)
1155                        .map_err(|e| anyhow::anyhow!("Failed to encode image {}: {}", outpath, e))
1156                },
1157                true,
1158            )?;
1159            return Ok(types::ScriptResult::Uncount);
1160        } else {
1161            utils::img::encode_img(img_data, out_type, &f, &config)?;
1162        }
1163        return Ok(types::ScriptResult::Ok);
1164    }
1165    let mut of = match &arg.output_type {
1166        Some(t) => t.clone(),
1167        None => script.default_output_script_type(),
1168    };
1169    if !script.is_output_supported(of) {
1170        of = script.default_output_script_type();
1171    }
1172    if !arg.no_multi_message && !of.is_custom() && script.multiple_message_files() {
1173        let mmes = script.extract_multiple_messages()?;
1174        if mmes.is_empty() {
1175            eprintln!("No messages found");
1176            return Ok(types::ScriptResult::Ignored);
1177        }
1178        let ext = of.as_ref();
1179        let out_dir = if let Some(output) = output.as_ref() {
1180            if let Some(root_dir) = root_dir {
1181                let f = std::path::PathBuf::from(filename);
1182                let mut pb = std::path::PathBuf::from(output);
1183                let rpath = utils::files::relative_path(root_dir, &f);
1184                if let Some(parent) = rpath.parent() {
1185                    pb.push(parent);
1186                }
1187                if let Some(fname) = f.file_name() {
1188                    pb.push(fname);
1189                }
1190                if arg.output_no_extra_ext {
1191                    pb.remove_all_extensions();
1192                } else {
1193                    pb.set_extension("");
1194                }
1195                pb.to_string_lossy().into_owned()
1196            } else {
1197                output.clone()
1198            }
1199        } else {
1200            let mut pb = std::path::PathBuf::from(filename);
1201            if arg.output_no_extra_ext {
1202                pb.remove_all_extensions();
1203            } else {
1204                pb.set_extension("");
1205            }
1206            pb.to_string_lossy().into_owned()
1207        };
1208        std::fs::create_dir_all(&out_dir)?;
1209        let outdir = std::path::PathBuf::from(&out_dir);
1210        for (name, data) in mmes {
1211            let ofp = outdir.join(name).with_extension(ext);
1212            match of {
1213                types::OutputScriptType::Json => {
1214                    let enc = get_output_encoding(arg);
1215                    let s = match serde_json::to_string_pretty(&data) {
1216                        Ok(s) => s,
1217                        Err(e) => {
1218                            eprintln!("Error serializing messages to JSON: {}", e);
1219                            COUNTER.inc_error();
1220                            continue;
1221                        }
1222                    };
1223                    let b = match utils::encoding::encode_string(enc, &s, false) {
1224                        Ok(b) => b,
1225                        Err(e) => {
1226                            eprintln!("Error encoding string: {}", e);
1227                            COUNTER.inc_error();
1228                            continue;
1229                        }
1230                    };
1231                    let mut f = match utils::files::write_file(&ofp) {
1232                        Ok(f) => f,
1233                        Err(e) => {
1234                            eprintln!("Error writing file {}: {}", ofp.display(), e);
1235                            COUNTER.inc_error();
1236                            continue;
1237                        }
1238                    };
1239                    match f.write_all(&b) {
1240                        Ok(_) => {}
1241                        Err(e) => {
1242                            eprintln!("Error writing to file {}: {}", ofp.display(), e);
1243                            COUNTER.inc_error();
1244                            continue;
1245                        }
1246                    }
1247                }
1248                types::OutputScriptType::M3t
1249                | types::OutputScriptType::M3ta
1250                | types::OutputScriptType::M3tTxt => {
1251                    let enc = get_output_encoding(arg);
1252                    let s = output_scripts::m3t::M3tDumper::dump(&data, arg.m3t_no_quote);
1253                    let b = match utils::encoding::encode_string(enc, &s, false) {
1254                        Ok(b) => b,
1255                        Err(e) => {
1256                            eprintln!("Error encoding string: {}", e);
1257                            COUNTER.inc_error();
1258                            continue;
1259                        }
1260                    };
1261                    let mut f = match utils::files::write_file(&ofp) {
1262                        Ok(f) => f,
1263                        Err(e) => {
1264                            eprintln!("Error writing file {}: {}", ofp.display(), e);
1265                            COUNTER.inc_error();
1266                            continue;
1267                        }
1268                    };
1269                    match f.write_all(&b) {
1270                        Ok(_) => {}
1271                        Err(e) => {
1272                            eprintln!("Error writing to file {}: {}", ofp.display(), e);
1273                            COUNTER.inc_error();
1274                            continue;
1275                        }
1276                    }
1277                }
1278                types::OutputScriptType::Yaml => {
1279                    let enc = get_output_encoding(arg);
1280                    let s = match serde_yaml_ng::to_string(&data) {
1281                        Ok(s) => s,
1282                        Err(e) => {
1283                            eprintln!("Error serializing messages to YAML: {}", e);
1284                            COUNTER.inc_error();
1285                            continue;
1286                        }
1287                    };
1288                    let b = match utils::encoding::encode_string(enc, &s, false) {
1289                        Ok(b) => b,
1290                        Err(e) => {
1291                            eprintln!("Error encoding string: {}", e);
1292                            COUNTER.inc_error();
1293                            continue;
1294                        }
1295                    };
1296                    let mut f = match utils::files::write_file(&ofp) {
1297                        Ok(f) => f,
1298                        Err(e) => {
1299                            eprintln!("Error writing file {}: {}", ofp.display(), e);
1300                            COUNTER.inc_error();
1301                            continue;
1302                        }
1303                    };
1304                    match f.write_all(&b) {
1305                        Ok(_) => {}
1306                        Err(e) => {
1307                            eprintln!("Error writing to file {}: {}", ofp.display(), e);
1308                            COUNTER.inc_error();
1309                            continue;
1310                        }
1311                    }
1312                }
1313                types::OutputScriptType::Pot | types::OutputScriptType::Po => {
1314                    let enc = get_output_encoding(arg);
1315                    let s = match output_scripts::po::PoDumper::new().dump(&data, enc) {
1316                        Ok(s) => s,
1317                        Err(e) => {
1318                            eprintln!("Error dumping messages to PO format: {}", e);
1319                            COUNTER.inc_error();
1320                            continue;
1321                        }
1322                    };
1323                    let b = match utils::encoding::encode_string(enc, &s, false) {
1324                        Ok(b) => b,
1325                        Err(e) => {
1326                            eprintln!("Error encoding string: {}", e);
1327                            COUNTER.inc_error();
1328                            continue;
1329                        }
1330                    };
1331                    let mut f = match utils::files::write_file(&ofp) {
1332                        Ok(f) => f,
1333                        Err(e) => {
1334                            eprintln!("Error writing file {}: {}", ofp.display(), e);
1335                            COUNTER.inc_error();
1336                            continue;
1337                        }
1338                    };
1339                    match f.write_all(&b) {
1340                        Ok(_) => {}
1341                        Err(e) => {
1342                            eprintln!("Error writing to file {}: {}", ofp.display(), e);
1343                            COUNTER.inc_error();
1344                            continue;
1345                        }
1346                    }
1347                }
1348                types::OutputScriptType::Custom => {}
1349            }
1350            COUNTER.inc(types::ScriptResult::Ok);
1351        }
1352        return Ok(types::ScriptResult::Ok);
1353    }
1354    let mes = if of.is_custom() {
1355        Vec::new()
1356    } else {
1357        script.extract_messages()?
1358    };
1359    if !of.is_custom() && mes.is_empty() {
1360        eprintln!("No messages found");
1361        return Ok(types::ScriptResult::Ignored);
1362    }
1363    let ext = if of.is_custom() {
1364        script.custom_output_extension()
1365    } else {
1366        of.as_ref()
1367    };
1368    let f = if filename == "-" {
1369        String::from("-")
1370    } else {
1371        match output.as_ref() {
1372            Some(output) => {
1373                if let Some(root_dir) = root_dir {
1374                    let f = std::path::PathBuf::from(filename);
1375                    let mut pb = std::path::PathBuf::from(output);
1376                    let rpath = utils::files::relative_path(root_dir, &f);
1377                    if let Some(parent) = rpath.parent() {
1378                        pb.push(parent);
1379                    }
1380                    if let Some(fname) = f.file_name() {
1381                        pb.push(fname);
1382                    }
1383                    if arg.output_no_extra_ext {
1384                        pb.remove_all_extensions();
1385                    }
1386                    pb.set_extension(ext);
1387                    pb.to_string_lossy().into_owned()
1388                } else {
1389                    output.clone()
1390                }
1391            }
1392            None => {
1393                let mut pb = std::path::PathBuf::from(filename);
1394                if arg.output_no_extra_ext {
1395                    pb.remove_all_extensions();
1396                }
1397                pb.set_extension(ext);
1398                pb.to_string_lossy().into_owned()
1399            }
1400        }
1401    };
1402    utils::files::make_sure_dir_exists(&f)?;
1403    match of {
1404        types::OutputScriptType::Json => {
1405            let enc = get_output_encoding(arg);
1406            let s = serde_json::to_string_pretty(&mes)?;
1407            let b = utils::encoding::encode_string(enc, &s, false)?;
1408            let mut f = utils::files::write_file(&f)?;
1409            f.write_all(&b)?;
1410        }
1411        types::OutputScriptType::M3t
1412        | types::OutputScriptType::M3ta
1413        | types::OutputScriptType::M3tTxt => {
1414            let enc = get_output_encoding(arg);
1415            let s = output_scripts::m3t::M3tDumper::dump(&mes, arg.m3t_no_quote);
1416            let b = utils::encoding::encode_string(enc, &s, false)?;
1417            let mut f = utils::files::write_file(&f)?;
1418            f.write_all(&b)?;
1419        }
1420        types::OutputScriptType::Yaml => {
1421            let enc = get_output_encoding(arg);
1422            let s = serde_yaml_ng::to_string(&mes)?;
1423            let b = utils::encoding::encode_string(enc, &s, false)?;
1424            let mut f = utils::files::write_file(&f)?;
1425            f.write_all(&b)?;
1426        }
1427        types::OutputScriptType::Pot | types::OutputScriptType::Po => {
1428            let enc = get_output_encoding(arg);
1429            let s = output_scripts::po::PoDumper::new().dump(&mes, enc)?;
1430            let b = utils::encoding::encode_string(enc, &s, false)?;
1431            let mut f = utils::files::write_file(&f)?;
1432            f.write_all(&b)?;
1433        }
1434        types::OutputScriptType::Custom => {
1435            let enc = get_output_encoding(arg);
1436            script.custom_export(f.as_ref(), enc)?;
1437        }
1438    }
1439    Ok(types::ScriptResult::Ok)
1440}
1441
1442pub fn import_script(
1443    filename: &str,
1444    arg: &args::Arg,
1445    config: std::sync::Arc<types::ExtraConfig>,
1446    imp_cfg: &args::ImportArgs,
1447    root_dir: Option<&std::path::Path>,
1448    name_csv: Option<&std::collections::HashMap<String, String>>,
1449    repl: Option<&types::ReplacementTable>,
1450    mut dep_graph: Option<&mut (String, Vec<String>)>,
1451) -> anyhow::Result<types::ScriptResult> {
1452    eprintln!("Importing {}", filename);
1453    if let Some(dep_graph) = dep_graph.as_mut() {
1454        dep_graph.1.push(filename.to_string());
1455    }
1456    let (script, builder) = parse_script(filename, arg, config.clone())?;
1457    if script.is_archive() {
1458        let odir = {
1459            let mut pb = std::path::PathBuf::from(&imp_cfg.output);
1460            let filename = std::path::PathBuf::from(filename);
1461            if let Some(root_dir) = root_dir {
1462                let rpath = utils::files::relative_path(root_dir, &filename);
1463                if let Some(parent) = rpath.parent() {
1464                    pb.push(parent);
1465                }
1466                if let Some(fname) = filename.file_name() {
1467                    pb.push(fname);
1468                }
1469            }
1470            pb.set_extension("");
1471            if let Some(ext) = script.archive_output_ext() {
1472                pb.set_extension(ext);
1473            }
1474            pb.to_string_lossy().into_owned()
1475        };
1476        let files: Vec<_> = script.iter_archive_filename()?.collect();
1477        let files = files.into_iter().filter_map(|f| f.ok()).collect::<Vec<_>>();
1478        let patched_f = if let Some(root_dir) = root_dir {
1479            let f = std::path::PathBuf::from(filename);
1480            let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
1481            let rpath = utils::files::relative_path(root_dir, &f);
1482            if let Some(parent) = rpath.parent() {
1483                pb.push(parent);
1484            }
1485            if let Some(fname) = f.file_name() {
1486                pb.push(fname);
1487            }
1488            pb.set_extension(builder.extensions().first().unwrap_or(&""));
1489            pb.to_string_lossy().into_owned()
1490        } else {
1491            imp_cfg.patched.clone()
1492        };
1493        if let Some(dep_graph) = dep_graph.as_mut() {
1494            dep_graph.0 = patched_f.clone();
1495        }
1496        let files: Vec<_> = files.iter().map(|s| s.as_str()).collect();
1497        let pencoding = get_patched_encoding(imp_cfg, builder);
1498        let enc = get_patched_archive_encoding(imp_cfg, builder, pencoding);
1499        utils::files::make_sure_dir_exists(&patched_f)?;
1500        let mut arch = builder.create_archive(&patched_f, &files, enc, &config)?;
1501        for (index, filename) in script.iter_archive_filename()?.enumerate() {
1502            let filename = match filename {
1503                Ok(f) => f,
1504                Err(e) => {
1505                    eprintln!("Error reading archive filename: {}", e);
1506                    COUNTER.inc_error();
1507                    if arg.backtrace {
1508                        eprintln!("Backtrace: {}", e.backtrace());
1509                    }
1510                    continue;
1511                }
1512            };
1513            let mut f = match script.open_file(index) {
1514                Ok(f) => f,
1515                Err(e) => {
1516                    eprintln!("Error opening file {}: {}", filename, e);
1517                    COUNTER.inc_error();
1518                    if arg.backtrace {
1519                        eprintln!("Backtrace: {}", e.backtrace());
1520                    }
1521                    continue;
1522                }
1523            };
1524            if arg.force_script || f.is_script() {
1525                let mut writer = arch.new_file(f.name(), None)?;
1526                let (script_file, _) =
1527                    match parse_script_from_archive(&mut f, arg, config.clone(), &script) {
1528                        Ok(s) => s,
1529                        Err(e) => {
1530                            eprintln!("Error parsing script '{}' from archive: {}", filename, e);
1531                            COUNTER.inc_error();
1532                            if arg.backtrace {
1533                                eprintln!("Backtrace: {}", e.backtrace());
1534                            }
1535                            continue;
1536                        }
1537                    };
1538                let mut of = match &arg.output_type {
1539                    Some(t) => t.clone(),
1540                    None => script_file.default_output_script_type(),
1541                };
1542                if !script_file.is_output_supported(of) {
1543                    of = script_file.default_output_script_type();
1544                }
1545                if !arg.no_multi_message && !of.is_custom() && script_file.multiple_message_files()
1546                {
1547                    let out_dir = std::path::PathBuf::from(&odir)
1548                        .join(f.name())
1549                        .with_extension("");
1550                    let outfiles = utils::files::find_ext_files(
1551                        &out_dir.to_string_lossy(),
1552                        false,
1553                        &[of.as_ref()],
1554                    )?;
1555                    if outfiles.is_empty() {
1556                        if imp_cfg.warn_when_output_file_not_found {
1557                            eprintln!(
1558                                "Warning: No output files found in {}, using file from original archive.",
1559                                out_dir.display()
1560                            );
1561                            COUNTER.inc_warning();
1562                        } else {
1563                            COUNTER.inc(types::ScriptResult::Ignored);
1564                        }
1565                        continue;
1566                    }
1567                    if let Some(dep_graph) = dep_graph.as_mut() {
1568                        dep_graph.1.extend_from_slice(&outfiles);
1569                    }
1570                    let fmt = match imp_cfg.patched_format {
1571                        Some(fmt) => match fmt {
1572                            types::FormatType::Fixed => types::FormatOptions::Fixed {
1573                                length: imp_cfg.patched_fixed_length.unwrap_or(32),
1574                                keep_original: imp_cfg.patched_keep_original,
1575                                break_words: imp_cfg.patched_break_words,
1576                                insert_fullwidth_space_at_line_start: imp_cfg
1577                                    .patched_insert_fullwidth_space_at_line_start,
1578                                break_with_sentence: imp_cfg.patched_break_with_sentence,
1579                                #[cfg(feature = "jieba")]
1580                                break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
1581                                #[cfg(feature = "jieba")]
1582                                jieba_dict: arg.jieba_dict.clone(),
1583                                no_remove_space_at_line_start: imp_cfg
1584                                    .patched_no_remove_space_at_line_start,
1585                            },
1586                            types::FormatType::None => types::FormatOptions::None,
1587                        },
1588                        None => script.default_format_type(),
1589                    };
1590                    let mut mmes = std::collections::HashMap::new();
1591                    for out_f in outfiles {
1592                        let name = utils::files::relative_path(&out_dir, &out_f)
1593                            .with_extension("")
1594                            .to_string_lossy()
1595                            .into_owned();
1596                        let mut mes = match of {
1597                            types::OutputScriptType::Json => {
1598                                let enc = get_output_encoding(arg);
1599                                let b = match utils::files::read_file(&out_f) {
1600                                    Ok(b) => b,
1601                                    Err(e) => {
1602                                        eprintln!("Error reading file {}: {}", out_f, e);
1603                                        COUNTER.inc_error();
1604                                        continue;
1605                                    }
1606                                };
1607                                let s = match utils::encoding::decode_to_string(enc, &b, true) {
1608                                    Ok(s) => s,
1609                                    Err(e) => {
1610                                        eprintln!("Error decoding string: {}", e);
1611                                        COUNTER.inc_error();
1612                                        continue;
1613                                    }
1614                                };
1615                                match serde_json::from_str::<Vec<types::Message>>(&s) {
1616                                    Ok(mes) => mes,
1617                                    Err(e) => {
1618                                        eprintln!("Error parsing JSON: {}", e);
1619                                        COUNTER.inc_error();
1620                                        continue;
1621                                    }
1622                                }
1623                            }
1624                            types::OutputScriptType::M3t
1625                            | types::OutputScriptType::M3ta
1626                            | types::OutputScriptType::M3tTxt => {
1627                                let enc = get_output_encoding(arg);
1628                                let b = match utils::files::read_file(&out_f) {
1629                                    Ok(b) => b,
1630                                    Err(e) => {
1631                                        eprintln!("Error reading file {}: {}", out_f, e);
1632                                        COUNTER.inc_error();
1633                                        continue;
1634                                    }
1635                                };
1636                                let s = match utils::encoding::decode_to_string(enc, &b, true) {
1637                                    Ok(s) => s,
1638                                    Err(e) => {
1639                                        eprintln!("Error decoding string: {}", e);
1640                                        COUNTER.inc_error();
1641                                        continue;
1642                                    }
1643                                };
1644                                let mut parser = output_scripts::m3t::M3tParser::new(
1645                                    &s,
1646                                    arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
1647                                    arg.m3t_use_original_text,
1648                                );
1649                                match parser.parse() {
1650                                    Ok(mes) => mes,
1651                                    Err(e) => {
1652                                        eprintln!("Error parsing M3T: {}", e);
1653                                        COUNTER.inc_error();
1654                                        continue;
1655                                    }
1656                                }
1657                            }
1658                            types::OutputScriptType::Yaml => {
1659                                let enc = get_output_encoding(arg);
1660                                let b = match utils::files::read_file(&out_f) {
1661                                    Ok(b) => b,
1662                                    Err(e) => {
1663                                        eprintln!("Error reading file {}: {}", out_f, e);
1664                                        COUNTER.inc_error();
1665                                        continue;
1666                                    }
1667                                };
1668                                let s = match utils::encoding::decode_to_string(enc, &b, true) {
1669                                    Ok(s) => s,
1670                                    Err(e) => {
1671                                        eprintln!("Error decoding string: {}", e);
1672                                        COUNTER.inc_error();
1673                                        continue;
1674                                    }
1675                                };
1676                                match serde_yaml_ng::from_str::<Vec<types::Message>>(&s) {
1677                                    Ok(mes) => mes,
1678                                    Err(e) => {
1679                                        eprintln!("Error parsing YAML: {}", e);
1680                                        COUNTER.inc_error();
1681                                        continue;
1682                                    }
1683                                }
1684                            }
1685                            types::OutputScriptType::Pot | types::OutputScriptType::Po => {
1686                                let enc = get_output_encoding(arg);
1687                                let b = match utils::files::read_file(&out_f) {
1688                                    Ok(b) => b,
1689                                    Err(e) => {
1690                                        eprintln!("Error reading file {}: {}", out_f, e);
1691                                        COUNTER.inc_error();
1692                                        continue;
1693                                    }
1694                                };
1695                                let s = match utils::encoding::decode_to_string(enc, &b, true) {
1696                                    Ok(s) => s,
1697                                    Err(e) => {
1698                                        eprintln!("Error decoding string: {}", e);
1699                                        COUNTER.inc_error();
1700                                        continue;
1701                                    }
1702                                };
1703                                match output_scripts::po::PoParser::new(
1704                                    &s,
1705                                    arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
1706                                )
1707                                .parse()
1708                                {
1709                                    Ok(mes) => mes,
1710                                    Err(e) => {
1711                                        eprintln!("Error parsing PO: {}", e);
1712                                        COUNTER.inc_error();
1713                                        continue;
1714                                    }
1715                                }
1716                            }
1717                            types::OutputScriptType::Custom => Vec::new(),
1718                        };
1719                        if mes.is_empty() {
1720                            eprintln!(
1721                                "No messages found in {}, using file from original archive.",
1722                                out_f
1723                            );
1724                            continue;
1725                        }
1726                        match name_csv {
1727                            Some(name_table) => {
1728                                utils::name_replacement::replace_message(&mut mes, name_table);
1729                            }
1730                            None => {}
1731                        }
1732                        format::fmt_message(&mut mes, fmt.clone(), *builder.script_type())?;
1733                        mmes.insert(name, mes);
1734                    }
1735                    if mmes.is_empty() {
1736                        COUNTER.inc(types::ScriptResult::Ignored);
1737                        continue;
1738                    }
1739                    let encoding = get_patched_encoding(imp_cfg, builder);
1740                    match script_file.import_multiple_messages(
1741                        mmes,
1742                        writer,
1743                        f.name(),
1744                        encoding,
1745                        repl,
1746                    ) {
1747                        Ok(_) => {}
1748                        Err(e) => {
1749                            eprintln!("Error importing messages to script '{}': {}", filename, e);
1750                            COUNTER.inc_error();
1751                            if arg.backtrace {
1752                                eprintln!("Backtrace: {}", e.backtrace());
1753                            }
1754                            continue;
1755                        }
1756                    }
1757                    COUNTER.inc(types::ScriptResult::Ok);
1758                    continue;
1759                }
1760                #[cfg(feature = "image")]
1761                if script_file.is_image() {
1762                    let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
1763                    let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
1764                    if arg.output_no_extra_ext {
1765                        out_path.remove_all_extensions();
1766                    }
1767                    out_path.set_extension(out_type.as_ref());
1768                    if !out_path.exists() {
1769                        out_path = std::path::PathBuf::from(&odir).join(f.name());
1770                        if !out_path.exists() {
1771                            if imp_cfg.warn_when_output_file_not_found {
1772                                eprintln!(
1773                                    "Warning: File {} does not exist, using file from original archive.",
1774                                    out_path.display()
1775                                );
1776                                COUNTER.inc_warning();
1777                            }
1778                            match std::io::copy(&mut f, &mut writer) {
1779                                Ok(_) => {}
1780                                Err(e) => {
1781                                    eprintln!(
1782                                        "Error writing to file {}: {}",
1783                                        out_path.display(),
1784                                        e
1785                                    );
1786                                    COUNTER.inc_error();
1787                                    continue;
1788                                }
1789                            }
1790                        } else {
1791                            if let Some(dep_graph) = dep_graph.as_mut() {
1792                                dep_graph.1.push(out_path.to_string_lossy().into_owned());
1793                            }
1794                            let file = match std::fs::File::open(&out_path) {
1795                                Ok(f) => f,
1796                                Err(e) => {
1797                                    eprintln!("Error opening file {}: {}", out_path.display(), e);
1798                                    COUNTER.inc_error();
1799                                    continue;
1800                                }
1801                            };
1802                            let mut f = std::io::BufReader::new(file);
1803                            match std::io::copy(&mut f, &mut writer) {
1804                                Ok(_) => {}
1805                                Err(e) => {
1806                                    eprintln!(
1807                                        "Error writing to file {}: {}",
1808                                        out_path.display(),
1809                                        e
1810                                    );
1811                                    COUNTER.inc_error();
1812                                    continue;
1813                                }
1814                            }
1815                        }
1816                    }
1817                    if let Some(dep_graph) = dep_graph.as_mut() {
1818                        dep_graph.1.push(out_path.to_string_lossy().into_owned());
1819                    }
1820                    let img_data =
1821                        match utils::img::decode_img(out_type, &out_path.to_string_lossy()) {
1822                            Ok(data) => data,
1823                            Err(e) => {
1824                                eprintln!("Error decoding image {}: {}", out_path.display(), e);
1825                                COUNTER.inc_error();
1826                                continue;
1827                            }
1828                        };
1829                    if let Err(err) =
1830                        script_file.import_image(img_data, &out_path.to_string_lossy(), writer)
1831                    {
1832                        eprintln!("Error importing image to script '{}': {}", filename, err);
1833                        COUNTER.inc_error();
1834                        if arg.backtrace {
1835                            eprintln!("Backtrace: {}", err.backtrace());
1836                        }
1837                        continue;
1838                    }
1839                    continue;
1840                }
1841                let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
1842                if arg.output_no_extra_ext {
1843                    out_path.remove_all_extensions();
1844                }
1845                let ext = if of.is_custom() {
1846                    script_file.custom_output_extension()
1847                } else {
1848                    of.as_ref()
1849                };
1850                out_path.set_extension(ext);
1851                if !out_path.exists() {
1852                    out_path = std::path::PathBuf::from(&odir).join(f.name());
1853                    if !out_path.exists() {
1854                        if imp_cfg.warn_when_output_file_not_found {
1855                            eprintln!(
1856                                "Warning: File {} does not exist, using file from original archive.",
1857                                out_path.display()
1858                            );
1859                            COUNTER.inc_warning();
1860                        }
1861                        match std::io::copy(&mut f, &mut writer) {
1862                            Ok(_) => {}
1863                            Err(e) => {
1864                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
1865                                COUNTER.inc_error();
1866                                continue;
1867                            }
1868                        }
1869                        COUNTER.inc(types::ScriptResult::Ok);
1870                        continue;
1871                    } else {
1872                        if let Some(dep_graph) = dep_graph.as_mut() {
1873                            dep_graph.1.push(out_path.to_string_lossy().into_owned());
1874                        }
1875                        let file = match std::fs::File::open(&out_path) {
1876                            Ok(f) => f,
1877                            Err(e) => {
1878                                eprintln!("Error opening file {}: {}", out_path.display(), e);
1879                                COUNTER.inc_error();
1880                                continue;
1881                            }
1882                        };
1883                        let mut f = std::io::BufReader::new(file);
1884                        match std::io::copy(&mut f, &mut writer) {
1885                            Ok(_) => {}
1886                            Err(e) => {
1887                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
1888                                COUNTER.inc_error();
1889                                continue;
1890                            }
1891                        }
1892                        COUNTER.inc(types::ScriptResult::Ok);
1893                        continue;
1894                    }
1895                }
1896                if let Some(dep_graph) = dep_graph.as_mut() {
1897                    dep_graph.1.push(out_path.to_string_lossy().into_owned());
1898                }
1899                let mut mes = match of {
1900                    types::OutputScriptType::Json => {
1901                        let enc = get_output_encoding(arg);
1902                        let b = match utils::files::read_file(&out_path) {
1903                            Ok(b) => b,
1904                            Err(e) => {
1905                                eprintln!("Error reading file {}: {}", out_path.display(), e);
1906                                COUNTER.inc_error();
1907                                continue;
1908                            }
1909                        };
1910                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
1911                            Ok(s) => s,
1912                            Err(e) => {
1913                                eprintln!("Error decoding string: {}", e);
1914                                COUNTER.inc_error();
1915                                continue;
1916                            }
1917                        };
1918                        match serde_json::from_str::<Vec<types::Message>>(&s) {
1919                            Ok(mes) => mes,
1920                            Err(e) => {
1921                                eprintln!("Error parsing JSON: {}", e);
1922                                COUNTER.inc_error();
1923                                continue;
1924                            }
1925                        }
1926                    }
1927                    types::OutputScriptType::M3t
1928                    | types::OutputScriptType::M3ta
1929                    | types::OutputScriptType::M3tTxt => {
1930                        let enc = get_output_encoding(arg);
1931                        let b = match utils::files::read_file(&out_path) {
1932                            Ok(b) => b,
1933                            Err(e) => {
1934                                eprintln!("Error reading file {}: {}", out_path.display(), e);
1935                                COUNTER.inc_error();
1936                                continue;
1937                            }
1938                        };
1939                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
1940                            Ok(s) => s,
1941                            Err(e) => {
1942                                eprintln!("Error decoding string: {}", e);
1943                                COUNTER.inc_error();
1944                                continue;
1945                            }
1946                        };
1947                        let mut parser = output_scripts::m3t::M3tParser::new(
1948                            &s,
1949                            arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
1950                            arg.m3t_use_original_text,
1951                        );
1952                        match parser.parse() {
1953                            Ok(mes) => mes,
1954                            Err(e) => {
1955                                eprintln!("Error parsing M3T: {}", e);
1956                                COUNTER.inc_error();
1957                                continue;
1958                            }
1959                        }
1960                    }
1961                    types::OutputScriptType::Yaml => {
1962                        let enc = get_output_encoding(arg);
1963                        let b = match utils::files::read_file(&out_path) {
1964                            Ok(b) => b,
1965                            Err(e) => {
1966                                eprintln!("Error reading file {}: {}", out_path.display(), e);
1967                                COUNTER.inc_error();
1968                                continue;
1969                            }
1970                        };
1971                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
1972                            Ok(s) => s,
1973                            Err(e) => {
1974                                eprintln!("Error decoding string: {}", e);
1975                                COUNTER.inc_error();
1976                                continue;
1977                            }
1978                        };
1979                        match serde_yaml_ng::from_str::<Vec<types::Message>>(&s) {
1980                            Ok(mes) => mes,
1981                            Err(e) => {
1982                                eprintln!("Error parsing YAML: {}", e);
1983                                COUNTER.inc_error();
1984                                continue;
1985                            }
1986                        }
1987                    }
1988                    types::OutputScriptType::Pot | types::OutputScriptType::Po => {
1989                        let enc = get_output_encoding(arg);
1990                        let b = match utils::files::read_file(&out_path) {
1991                            Ok(b) => b,
1992                            Err(e) => {
1993                                eprintln!("Error reading file {}: {}", out_path.display(), e);
1994                                COUNTER.inc_error();
1995                                continue;
1996                            }
1997                        };
1998                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
1999                            Ok(s) => s,
2000                            Err(e) => {
2001                                eprintln!("Error decoding string: {}", e);
2002                                COUNTER.inc_error();
2003                                continue;
2004                            }
2005                        };
2006                        let mut parser = output_scripts::po::PoParser::new(
2007                            &s,
2008                            arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2009                        );
2010                        match parser.parse() {
2011                            Ok(mes) => mes,
2012                            Err(e) => {
2013                                eprintln!("Error parsing PO: {}", e);
2014                                COUNTER.inc_error();
2015                                continue;
2016                            }
2017                        }
2018                    }
2019                    types::OutputScriptType::Custom => {
2020                        Vec::new() // Custom scripts handle their own messages
2021                    }
2022                };
2023                if !of.is_custom() && mes.is_empty() {
2024                    eprintln!("No messages found in {}", f.name());
2025                    COUNTER.inc(types::ScriptResult::Ignored);
2026                    continue;
2027                }
2028                let encoding = get_patched_encoding(imp_cfg, builder);
2029                if of.is_custom() {
2030                    let enc = get_output_encoding(arg);
2031                    match script_file.custom_import(
2032                        &out_path.to_string_lossy(),
2033                        writer,
2034                        encoding,
2035                        enc,
2036                    ) {
2037                        Ok(_) => {}
2038                        Err(e) => {
2039                            eprintln!("Error importing custom script: {}", e);
2040                            COUNTER.inc_error();
2041                            continue;
2042                        }
2043                    }
2044                    COUNTER.inc(types::ScriptResult::Ok);
2045                    continue;
2046                }
2047                let fmt = match imp_cfg.patched_format {
2048                    Some(fmt) => match fmt {
2049                        types::FormatType::Fixed => types::FormatOptions::Fixed {
2050                            length: imp_cfg.patched_fixed_length.unwrap_or(32),
2051                            keep_original: imp_cfg.patched_keep_original,
2052                            break_words: imp_cfg.patched_break_words,
2053                            insert_fullwidth_space_at_line_start: imp_cfg
2054                                .patched_insert_fullwidth_space_at_line_start,
2055                            break_with_sentence: imp_cfg.patched_break_with_sentence,
2056                            #[cfg(feature = "jieba")]
2057                            break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
2058                            #[cfg(feature = "jieba")]
2059                            jieba_dict: arg.jieba_dict.clone(),
2060                            no_remove_space_at_line_start: imp_cfg
2061                                .patched_no_remove_space_at_line_start,
2062                        },
2063                        types::FormatType::None => types::FormatOptions::None,
2064                    },
2065                    None => script_file.default_format_type(),
2066                };
2067                match name_csv {
2068                    Some(name_table) => {
2069                        utils::name_replacement::replace_message(&mut mes, name_table);
2070                    }
2071                    None => {}
2072                }
2073                format::fmt_message(&mut mes, fmt, *builder.script_type())?;
2074                if let Err(e) = script_file.import_messages(
2075                    mes,
2076                    writer,
2077                    &out_path.to_string_lossy(),
2078                    encoding,
2079                    repl,
2080                ) {
2081                    eprintln!("Error importing messages: {}", e);
2082                    COUNTER.inc_error();
2083                    continue;
2084                }
2085            } else {
2086                let out_path = std::path::PathBuf::from(&odir).join(f.name());
2087                let size = if out_path.is_file() {
2088                    match std::fs::metadata(&out_path) {
2089                        Ok(meta) => Some(meta.len()),
2090                        Err(e) => {
2091                            eprintln!(
2092                                "Error getting metadata for file {}: {}",
2093                                out_path.display(),
2094                                e
2095                            );
2096                            COUNTER.inc_error();
2097                            continue;
2098                        }
2099                    }
2100                } else {
2101                    None
2102                };
2103                let mut writer = arch.new_file_non_seek(f.name(), size)?;
2104                if out_path.is_file() {
2105                    if let Some(dep_graph) = dep_graph.as_mut() {
2106                        dep_graph.1.push(out_path.to_string_lossy().into_owned());
2107                    }
2108                    let f = match std::fs::File::open(&out_path) {
2109                        Ok(f) => f,
2110                        Err(e) => {
2111                            eprintln!("Error opening file {}: {}", out_path.display(), e);
2112                            COUNTER.inc_error();
2113                            continue;
2114                        }
2115                    };
2116                    let mut f = std::io::BufReader::new(f);
2117                    match std::io::copy(&mut f, &mut writer) {
2118                        Ok(_) => {}
2119                        Err(e) => {
2120                            eprintln!("Error writing to file {}: {}", out_path.display(), e);
2121                            COUNTER.inc_error();
2122                            continue;
2123                        }
2124                    }
2125                } else {
2126                    eprintln!(
2127                        "Warning: File {} does not exist, use file from original archive.",
2128                        out_path.display()
2129                    );
2130                    COUNTER.inc_warning();
2131                    match std::io::copy(&mut f, &mut writer) {
2132                        Ok(_) => {}
2133                        Err(e) => {
2134                            eprintln!("Error writing to file {}: {}", out_path.display(), e);
2135                            COUNTER.inc_error();
2136                            continue;
2137                        }
2138                    }
2139                }
2140            }
2141            COUNTER.inc(types::ScriptResult::Ok);
2142        }
2143        arch.write_header()?;
2144        return Ok(types::ScriptResult::Ok);
2145    }
2146    #[cfg(feature = "image")]
2147    if script.is_image() {
2148        let out_type = arg.image_type.unwrap_or_else(|| {
2149            if root_dir.is_some() {
2150                types::ImageOutputType::Png
2151            } else {
2152                types::ImageOutputType::try_from(std::path::Path::new(&imp_cfg.output))
2153                    .unwrap_or(types::ImageOutputType::Png)
2154            }
2155        });
2156        let out_f = if let Some(root_dir) = root_dir {
2157            let f = std::path::PathBuf::from(filename);
2158            let mut pb = std::path::PathBuf::from(&imp_cfg.output);
2159            let rpath = utils::files::relative_path(root_dir, &f);
2160            if let Some(parent) = rpath.parent() {
2161                pb.push(parent);
2162            }
2163            if let Some(fname) = f.file_name() {
2164                pb.push(fname);
2165            }
2166            if arg.output_no_extra_ext {
2167                pb.remove_all_extensions();
2168            }
2169            pb.set_extension(out_type.as_ref());
2170            pb.to_string_lossy().into_owned()
2171        } else {
2172            imp_cfg.output.clone()
2173        };
2174        if let Some(dep_graph) = dep_graph.as_mut() {
2175            dep_graph.1.push(out_f.clone());
2176        }
2177        let data = utils::img::decode_img(out_type, &out_f)?;
2178        let patched_f = if let Some(root_dir) = root_dir {
2179            let f = std::path::PathBuf::from(filename);
2180            let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
2181            let rpath = utils::files::relative_path(root_dir, &f);
2182            if let Some(parent) = rpath.parent() {
2183                pb.push(parent);
2184            }
2185            if let Some(fname) = f.file_name() {
2186                pb.push(fname);
2187            }
2188            pb.set_extension(builder.extensions().first().unwrap_or(&""));
2189            pb.to_string_lossy().into_owned()
2190        } else {
2191            imp_cfg.patched.clone()
2192        };
2193        if let Some(dep_graph) = dep_graph.as_mut() {
2194            dep_graph.0 = patched_f.clone();
2195        }
2196        utils::files::make_sure_dir_exists(&patched_f)?;
2197        script.import_image_filename(data, &out_f, &patched_f)?;
2198        return Ok(types::ScriptResult::Ok);
2199    }
2200    let mut of = match &arg.output_type {
2201        Some(t) => t.clone(),
2202        None => script.default_output_script_type(),
2203    };
2204    if !script.is_output_supported(of) {
2205        of = script.default_output_script_type();
2206    }
2207    if !arg.no_multi_message && !of.is_custom() && script.multiple_message_files() {
2208        let out_dir = if let Some(root_dir) = root_dir {
2209            let f = std::path::PathBuf::from(filename);
2210            let mut pb = std::path::PathBuf::from(&imp_cfg.output);
2211            let rpath = utils::files::relative_path(root_dir, &f);
2212            if let Some(parent) = rpath.parent() {
2213                pb.push(parent);
2214            }
2215            if let Some(fname) = f.file_name() {
2216                pb.push(fname);
2217            }
2218            if arg.output_no_extra_ext {
2219                pb.remove_all_extensions();
2220            } else {
2221                pb.set_extension("");
2222            }
2223            pb.to_string_lossy().into_owned()
2224        } else {
2225            imp_cfg.output.clone()
2226        };
2227        let outfiles = utils::files::find_ext_files(&out_dir, false, &[of.as_ref()])?;
2228        if outfiles.is_empty() {
2229            eprintln!("No output files found");
2230            return Ok(types::ScriptResult::Ignored);
2231        }
2232        if let Some(dep_graph) = dep_graph.as_mut() {
2233            dep_graph.1.extend_from_slice(&outfiles);
2234        }
2235        let fmt = match imp_cfg.patched_format {
2236            Some(fmt) => match fmt {
2237                types::FormatType::Fixed => types::FormatOptions::Fixed {
2238                    length: imp_cfg.patched_fixed_length.unwrap_or(32),
2239                    keep_original: imp_cfg.patched_keep_original,
2240                    break_words: imp_cfg.patched_break_words,
2241                    insert_fullwidth_space_at_line_start: imp_cfg
2242                        .patched_insert_fullwidth_space_at_line_start,
2243                    break_with_sentence: imp_cfg.patched_break_with_sentence,
2244                    #[cfg(feature = "jieba")]
2245                    break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
2246                    #[cfg(feature = "jieba")]
2247                    jieba_dict: arg.jieba_dict.clone(),
2248                    no_remove_space_at_line_start: imp_cfg.patched_no_remove_space_at_line_start,
2249                },
2250                types::FormatType::None => types::FormatOptions::None,
2251            },
2252            None => script.default_format_type(),
2253        };
2254        let mut mmes = std::collections::HashMap::new();
2255        for out_f in outfiles {
2256            let name = utils::files::relative_path(&out_dir, &out_f)
2257                .with_extension("")
2258                .to_string_lossy()
2259                .into_owned();
2260            let mut mes = match of {
2261                types::OutputScriptType::Json => {
2262                    let enc = get_output_encoding(arg);
2263                    let b = utils::files::read_file(&out_f)?;
2264                    let s = utils::encoding::decode_to_string(enc, &b, true)?;
2265                    serde_json::from_str::<Vec<types::Message>>(&s)?
2266                }
2267                types::OutputScriptType::M3t
2268                | types::OutputScriptType::M3ta
2269                | types::OutputScriptType::M3tTxt => {
2270                    let enc = get_output_encoding(arg);
2271                    let b = utils::files::read_file(&out_f)?;
2272                    let s = utils::encoding::decode_to_string(enc, &b, true)?;
2273                    let mut parser = output_scripts::m3t::M3tParser::new(
2274                        &s,
2275                        arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2276                        arg.m3t_use_original_text,
2277                    );
2278                    parser.parse()?
2279                }
2280                types::OutputScriptType::Yaml => {
2281                    let enc = get_output_encoding(arg);
2282                    let b = utils::files::read_file(&out_f)?;
2283                    let s = utils::encoding::decode_to_string(enc, &b, true)?;
2284                    serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?
2285                }
2286                types::OutputScriptType::Pot | types::OutputScriptType::Po => {
2287                    let enc = get_output_encoding(arg);
2288                    let b = utils::files::read_file(&out_f)?;
2289                    let s = utils::encoding::decode_to_string(enc, &b, true)?;
2290                    let mut parser = output_scripts::po::PoParser::new(
2291                        &s,
2292                        arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2293                    );
2294                    parser.parse()?
2295                }
2296                types::OutputScriptType::Custom => {
2297                    Vec::new() // Custom scripts handle their own messages
2298                }
2299            };
2300            if mes.is_empty() {
2301                eprintln!("No messages found in {}", out_f);
2302                continue;
2303            }
2304            match name_csv {
2305                Some(name_table) => {
2306                    utils::name_replacement::replace_message(&mut mes, name_table);
2307                }
2308                None => {}
2309            }
2310            format::fmt_message(&mut mes, fmt.clone(), *builder.script_type())?;
2311            mmes.insert(name, mes);
2312        }
2313        let patched_f = if let Some(root_dir) = root_dir {
2314            let f = std::path::PathBuf::from(filename);
2315            let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
2316            let rpath = utils::files::relative_path(root_dir, &f);
2317            if let Some(parent) = rpath.parent() {
2318                pb.push(parent);
2319            }
2320            if let Some(fname) = f.file_name() {
2321                pb.push(fname);
2322            }
2323            pb.set_extension(builder.extensions().first().unwrap_or(&""));
2324            pb.to_string_lossy().into_owned()
2325        } else {
2326            imp_cfg.patched.clone()
2327        };
2328        if let Some(dep_graph) = dep_graph.as_mut() {
2329            dep_graph.0 = patched_f.clone();
2330        }
2331        utils::files::make_sure_dir_exists(&patched_f)?;
2332        let encoding = get_patched_encoding(imp_cfg, builder);
2333        script.import_multiple_messages_filename(mmes, &patched_f, encoding, repl)?;
2334        return Ok(types::ScriptResult::Ok);
2335    }
2336    let out_f = if let Some(root_dir) = root_dir {
2337        let f = std::path::PathBuf::from(filename);
2338        let mut pb = std::path::PathBuf::from(&imp_cfg.output);
2339        let rpath = utils::files::relative_path(root_dir, &f);
2340        if let Some(parent) = rpath.parent() {
2341            pb.push(parent);
2342        }
2343        if let Some(fname) = f.file_name() {
2344            pb.push(fname);
2345        }
2346        if arg.output_no_extra_ext {
2347            pb.remove_all_extensions();
2348        }
2349        pb.set_extension(if of.is_custom() {
2350            script.custom_output_extension()
2351        } else {
2352            of.as_ref()
2353        });
2354        pb.to_string_lossy().into_owned()
2355    } else {
2356        imp_cfg.output.clone()
2357    };
2358    if !std::fs::exists(&out_f).unwrap_or(false) {
2359        eprintln!("Output file does not exist");
2360        return Ok(types::ScriptResult::Ignored);
2361    }
2362    if let Some(dep_graph) = dep_graph.as_mut() {
2363        dep_graph.1.push(out_f.clone());
2364    }
2365    let mut mes = match of {
2366        types::OutputScriptType::Json => {
2367            let enc = get_output_encoding(arg);
2368            let b = utils::files::read_file(&out_f)?;
2369            let s = utils::encoding::decode_to_string(enc, &b, true)?;
2370            serde_json::from_str::<Vec<types::Message>>(&s)?
2371        }
2372        types::OutputScriptType::M3t
2373        | types::OutputScriptType::M3ta
2374        | types::OutputScriptType::M3tTxt => {
2375            let enc = get_output_encoding(arg);
2376            let b = utils::files::read_file(&out_f)?;
2377            let s = utils::encoding::decode_to_string(enc, &b, true)?;
2378            let mut parser = output_scripts::m3t::M3tParser::new(
2379                &s,
2380                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2381                arg.m3t_use_original_text,
2382            );
2383            parser.parse()?
2384        }
2385        types::OutputScriptType::Yaml => {
2386            let enc = get_output_encoding(arg);
2387            let b = utils::files::read_file(&out_f)?;
2388            let s = utils::encoding::decode_to_string(enc, &b, true)?;
2389            serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?
2390        }
2391        types::OutputScriptType::Pot | types::OutputScriptType::Po => {
2392            let enc = get_output_encoding(arg);
2393            let b = utils::files::read_file(&out_f)?;
2394            let s = utils::encoding::decode_to_string(enc, &b, true)?;
2395            let mut parser = output_scripts::po::PoParser::new(
2396                &s,
2397                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2398            );
2399            parser.parse()?
2400        }
2401        types::OutputScriptType::Custom => {
2402            Vec::new() // Custom scripts handle their own messages
2403        }
2404    };
2405    if !of.is_custom() && mes.is_empty() {
2406        eprintln!("No messages found");
2407        return Ok(types::ScriptResult::Ignored);
2408    }
2409    let encoding = get_patched_encoding(imp_cfg, builder);
2410    let patched_f = if let Some(root_dir) = root_dir {
2411        let f = std::path::PathBuf::from(filename);
2412        let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
2413        let rpath = utils::files::relative_path(root_dir, &f);
2414        if let Some(parent) = rpath.parent() {
2415            pb.push(parent);
2416        }
2417        if let Some(fname) = f.file_name() {
2418            pb.push(fname);
2419        }
2420        pb.set_extension(builder.extensions().first().unwrap_or(&""));
2421        pb.to_string_lossy().into_owned()
2422    } else {
2423        imp_cfg.patched.clone()
2424    };
2425    if let Some(dep_graph) = dep_graph.as_mut() {
2426        dep_graph.0 = patched_f.clone();
2427    }
2428    utils::files::make_sure_dir_exists(&patched_f)?;
2429    if of.is_custom() {
2430        let enc = get_output_encoding(arg);
2431        script.custom_import_filename(&out_f, &patched_f, encoding, enc)?;
2432        return Ok(types::ScriptResult::Ok);
2433    }
2434    let fmt = match imp_cfg.patched_format {
2435        Some(fmt) => match fmt {
2436            types::FormatType::Fixed => types::FormatOptions::Fixed {
2437                length: imp_cfg.patched_fixed_length.unwrap_or(32),
2438                keep_original: imp_cfg.patched_keep_original,
2439                break_words: imp_cfg.patched_break_words,
2440                insert_fullwidth_space_at_line_start: imp_cfg
2441                    .patched_insert_fullwidth_space_at_line_start,
2442                break_with_sentence: imp_cfg.patched_break_with_sentence,
2443                #[cfg(feature = "jieba")]
2444                break_chinese_words: !imp_cfg.patched_no_break_chinese_words,
2445                #[cfg(feature = "jieba")]
2446                jieba_dict: arg.jieba_dict.clone(),
2447                no_remove_space_at_line_start: imp_cfg.patched_no_remove_space_at_line_start,
2448            },
2449            types::FormatType::None => types::FormatOptions::None,
2450        },
2451        None => script.default_format_type(),
2452    };
2453    match name_csv {
2454        Some(name_table) => {
2455            utils::name_replacement::replace_message(&mut mes, name_table);
2456        }
2457        None => {}
2458    }
2459    format::fmt_message(&mut mes, fmt, *builder.script_type())?;
2460
2461    script.import_messages_filename(mes, &patched_f, encoding, repl)?;
2462    Ok(types::ScriptResult::Ok)
2463}
2464
2465pub fn pack_archive(
2466    input: &str,
2467    output: Option<&str>,
2468    arg: &args::Arg,
2469    config: std::sync::Arc<types::ExtraConfig>,
2470    backslash: bool,
2471) -> anyhow::Result<()> {
2472    let typ = match &arg.script_type {
2473        Some(t) => t,
2474        None => {
2475            return Err(anyhow::anyhow!("No script type specified"));
2476        }
2477    };
2478    let (mut files, isdir) = utils::files::collect_files(input, arg.recursive, true)
2479        .map_err(|e| anyhow::anyhow!("Error collecting files: {}", e))?;
2480    if !isdir {
2481        return Err(anyhow::anyhow!("Input must be a directory for packing"));
2482    }
2483    let re_files: Vec<String> = files
2484        .iter()
2485        .filter_map(|f| {
2486            std::path::PathBuf::from(f)
2487                .strip_prefix(input)
2488                .ok()
2489                .and_then(|p| {
2490                    p.to_str().map(|s| {
2491                        if backslash {
2492                            s.replace("/", "\\").trim_start_matches("\\").to_owned()
2493                        } else {
2494                            s.replace("\\", "/").trim_start_matches("/").to_owned()
2495                        }
2496                    })
2497                })
2498        })
2499        .collect();
2500    let mut reff = re_files.iter().map(|s| s.as_str()).collect::<Vec<_>>();
2501    let builder = scripts::BUILDER
2502        .iter()
2503        .find(|b| b.script_type() == typ)
2504        .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
2505    let output = match output {
2506        Some(output) => output.to_string(),
2507        None => {
2508            let mut pb = std::path::PathBuf::from(input);
2509            let ext = builder.extensions().first().unwrap_or(&"unk");
2510            pb.set_extension(ext);
2511            if pb.to_string_lossy() == input {
2512                pb.set_extension(format!("{}.{}", ext, ext));
2513            }
2514            pb.to_string_lossy().into_owned()
2515        }
2516    };
2517    let mut archive = builder.create_archive(
2518        &output,
2519        &reff,
2520        get_archived_encoding(arg, builder, get_encoding(arg, builder)),
2521        &config,
2522    )?;
2523    if let Some(pre) = archive.prelist()? {
2524        let mut index = 0;
2525        for name in pre {
2526            let name = name?;
2527            if let Some(pos) = reff.iter().position(|&n| n == name) {
2528                let re = reff.remove(pos);
2529                let ff = files.remove(pos);
2530                reff.insert(index, re);
2531                files.insert(index, ff);
2532                index += 1;
2533            } else {
2534                eprintln!("Warning: Prelist file {} not found in input files", name);
2535                COUNTER.inc_warning();
2536            }
2537        }
2538    }
2539    for (file, name) in files.iter().zip(reff) {
2540        let mut f = match std::fs::File::open(file) {
2541            Ok(f) => f,
2542            Err(e) => {
2543                eprintln!("Error opening file {}: {}", file, e);
2544                COUNTER.inc_error();
2545                continue;
2546            }
2547        };
2548        let size = match std::fs::metadata(file) {
2549            Ok(meta) => meta.len(),
2550            Err(e) => {
2551                eprintln!("Error getting metadata for file {}: {}", file, e);
2552                COUNTER.inc_error();
2553                continue;
2554            }
2555        };
2556        let mut wf = match archive.new_file_non_seek(name, Some(size)) {
2557            Ok(f) => f,
2558            Err(e) => {
2559                eprintln!("Error creating file {} in archive: {}", name, e);
2560                COUNTER.inc_error();
2561                continue;
2562            }
2563        };
2564        match std::io::copy(&mut f, &mut wf) {
2565            Ok(_) => {
2566                COUNTER.inc(types::ScriptResult::Ok);
2567            }
2568            Err(e) => {
2569                eprintln!("Error writing to file {} in archive: {}", name, e);
2570                COUNTER.inc_error();
2571                continue;
2572            }
2573        }
2574    }
2575    archive.write_header()?;
2576    Ok(())
2577}
2578
2579pub fn pack_archive_v2(
2580    input: &[&str],
2581    output: Option<&str>,
2582    arg: &args::Arg,
2583    config: std::sync::Arc<types::ExtraConfig>,
2584    backslash: bool,
2585    no_dir: bool,
2586    dep_file: Option<&str>,
2587) -> anyhow::Result<()> {
2588    let typ = match &arg.script_type {
2589        Some(t) => t,
2590        None => {
2591            return Err(anyhow::anyhow!("No script type specified"));
2592        }
2593    };
2594    // File List in real path
2595    let mut files = Vec::new();
2596    // File list in archive path
2597    let mut re_files = Vec::new();
2598    for i in input {
2599        let (fs, is_dir) = utils::files::collect_files(i, arg.recursive, true)?;
2600        if is_dir {
2601            files.extend_from_slice(&fs);
2602            for n in fs.iter() {
2603                if no_dir {
2604                    if let Some(p) = std::path::PathBuf::from(n).file_name() {
2605                        re_files.push(p.to_string_lossy().into_owned());
2606                    } else {
2607                        return Err(anyhow::anyhow!("Failed to get filename from {}", n));
2608                    }
2609                } else {
2610                    if let Some(p) = {
2611                        std::path::PathBuf::from(n)
2612                            .strip_prefix(i)
2613                            .ok()
2614                            .and_then(|p| {
2615                                p.to_str().map(|s| {
2616                                    if backslash {
2617                                        s.replace("/", "\\").trim_start_matches("\\").to_owned()
2618                                    } else {
2619                                        s.replace("\\", "/").trim_start_matches("/").to_owned()
2620                                    }
2621                                })
2622                            })
2623                    } {
2624                        re_files.push(p);
2625                    } else {
2626                        return Err(anyhow::anyhow!("Failed to get relative path from {}", n));
2627                    }
2628                }
2629            }
2630        } else {
2631            files.push(i.to_string());
2632            let p = std::path::PathBuf::from(i);
2633            if let Some(fname) = p.file_name() {
2634                re_files.push(fname.to_string_lossy().into_owned());
2635            } else {
2636                return Err(anyhow::anyhow!("Failed to get filename from {}", i));
2637            }
2638        }
2639    }
2640    let mut reff = re_files.iter().map(|s| s.as_str()).collect::<Vec<_>>();
2641    let builder = scripts::BUILDER
2642        .iter()
2643        .find(|b| b.script_type() == typ)
2644        .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
2645    let output = match output {
2646        Some(output) => output.to_string(),
2647        None => {
2648            let mut pb = std::path::PathBuf::from(input[0]);
2649            let ext = builder.extensions().first().unwrap_or(&"unk");
2650            pb.set_extension(ext);
2651            if pb.to_string_lossy() == input[0] {
2652                pb.set_extension(format!("{}.{}", ext, ext));
2653            }
2654            pb.to_string_lossy().into_owned()
2655        }
2656    };
2657    let mut archive = builder.create_archive(
2658        &output,
2659        &reff,
2660        get_archived_encoding(arg, builder, get_encoding(arg, builder)),
2661        &config,
2662    )?;
2663    if let Some(pre) = archive.prelist()? {
2664        let mut index = 0;
2665        for name in pre {
2666            let name = name?;
2667            if let Some(pos) = reff.iter().position(|&n| n == name) {
2668                let re = reff.remove(pos);
2669                let ff = files.remove(pos);
2670                reff.insert(index, re);
2671                files.insert(index, ff);
2672                index += 1;
2673            } else {
2674                eprintln!("Warning: Prelist file {} not found in input files", name);
2675                COUNTER.inc_warning();
2676            }
2677        }
2678    }
2679    if let Some(dep_file) = dep_file {
2680        let df = std::fs::File::create(dep_file)
2681            .map_err(|e| anyhow::anyhow!("Failed to create dep file {}: {}", dep_file, e))?;
2682        let mut df = std::io::BufWriter::new(df);
2683        use std::io::Write;
2684        write!(df, "{}:", escape_dep_string(&output))
2685            .map_err(|e| anyhow::anyhow!("Failed to write to dep file {}: {}", dep_file, e))?;
2686        for f in &files {
2687            write!(df, " {}", escape_dep_string(f))
2688                .map_err(|e| anyhow::anyhow!("Failed to write to dep file {}: {}", dep_file, e))?;
2689        }
2690        writeln!(df)
2691            .map_err(|e| anyhow::anyhow!("Failed to write to dep file {}: {}", dep_file, e))?;
2692    }
2693    for (file, name) in files.iter().zip(reff) {
2694        let mut f = match std::fs::File::open(file) {
2695            Ok(f) => f,
2696            Err(e) => {
2697                eprintln!("Error opening file {}: {}", file, e);
2698                COUNTER.inc_error();
2699                continue;
2700            }
2701        };
2702        let size = match std::fs::metadata(file) {
2703            Ok(meta) => meta.len(),
2704            Err(e) => {
2705                eprintln!("Error getting metadata for file {}: {}", file, e);
2706                COUNTER.inc_error();
2707                continue;
2708            }
2709        };
2710        let mut wf = match archive.new_file_non_seek(name, Some(size)) {
2711            Ok(f) => f,
2712            Err(e) => {
2713                eprintln!("Error creating file {} in archive: {}", name, e);
2714                COUNTER.inc_error();
2715                continue;
2716            }
2717        };
2718        match std::io::copy(&mut f, &mut wf) {
2719            Ok(_) => {
2720                COUNTER.inc(types::ScriptResult::Ok);
2721            }
2722            Err(e) => {
2723                eprintln!("Error writing to file {} in archive: {}", name, e);
2724                COUNTER.inc_error();
2725                continue;
2726            }
2727        }
2728    }
2729    archive.write_header()?;
2730    Ok(())
2731}
2732
2733pub fn unpack_archive(
2734    filename: &str,
2735    arg: &args::Arg,
2736    config: std::sync::Arc<types::ExtraConfig>,
2737    output: &Option<String>,
2738    root_dir: Option<&std::path::Path>,
2739) -> anyhow::Result<types::ScriptResult> {
2740    eprintln!("Unpacking {}", filename);
2741    let script = parse_script(filename, arg, config)?.0;
2742    if !script.is_archive() {
2743        return Ok(types::ScriptResult::Ignored);
2744    }
2745    let odir = match output.as_ref() {
2746        Some(output) => {
2747            let mut pb = std::path::PathBuf::from(output);
2748            let filename = std::path::PathBuf::from(filename);
2749            if let Some(root_dir) = root_dir {
2750                let rpath = utils::files::relative_path(root_dir, &filename);
2751                if let Some(parent) = rpath.parent() {
2752                    pb.push(parent);
2753                }
2754                if let Some(fname) = filename.file_name() {
2755                    pb.push(fname);
2756                }
2757            }
2758            pb.set_extension("");
2759            if let Some(ext) = script.archive_output_ext() {
2760                pb.set_extension(ext);
2761            }
2762            pb.to_string_lossy().into_owned()
2763        }
2764        None => {
2765            let mut pb = std::path::PathBuf::from(filename);
2766            pb.set_extension("");
2767            if let Some(ext) = script.archive_output_ext() {
2768                pb.set_extension(ext);
2769            }
2770            pb.to_string_lossy().into_owned()
2771        }
2772    };
2773    if !std::fs::exists(&odir)? {
2774        std::fs::create_dir_all(&odir)?;
2775    }
2776    for (index, filename) in script.iter_archive_filename()?.enumerate() {
2777        let filename = match filename {
2778            Ok(f) => f,
2779            Err(e) => {
2780                eprintln!("Error reading archive filename: {}", e);
2781                COUNTER.inc_error();
2782                if arg.backtrace {
2783                    eprintln!("Backtrace: {}", e.backtrace());
2784                }
2785                continue;
2786            }
2787        };
2788        let mut f = match script.open_file(index) {
2789            Ok(f) => f,
2790            Err(e) => {
2791                eprintln!("Error opening file {}: {}", filename, e);
2792                COUNTER.inc_error();
2793                if arg.backtrace {
2794                    eprintln!("Backtrace: {}", e.backtrace());
2795                }
2796                continue;
2797            }
2798        };
2799        let out_path = std::path::PathBuf::from(&odir).join(f.name());
2800        match utils::files::make_sure_dir_exists(&out_path) {
2801            Ok(_) => {}
2802            Err(e) => {
2803                eprintln!(
2804                    "Error creating parent directory for {}: {}",
2805                    out_path.display(),
2806                    e
2807                );
2808                COUNTER.inc_error();
2809                continue;
2810            }
2811        }
2812        match utils::files::write_file(&out_path) {
2813            Ok(mut fi) => match std::io::copy(&mut f, &mut fi) {
2814                Ok(_) => {}
2815                Err(e) => {
2816                    eprintln!("Error writing to file {}: {}", out_path.display(), e);
2817                    COUNTER.inc_error();
2818                    continue;
2819                }
2820            },
2821            Err(e) => {
2822                eprintln!("Error writing file {}: {}", out_path.display(), e);
2823                COUNTER.inc_error();
2824                continue;
2825            }
2826        }
2827        COUNTER.inc(types::ScriptResult::Ok);
2828    }
2829    Ok(types::ScriptResult::Ok)
2830}
2831
2832pub fn create_file(
2833    input: &str,
2834    output: Option<&str>,
2835    arg: &args::Arg,
2836    config: std::sync::Arc<types::ExtraConfig>,
2837) -> anyhow::Result<()> {
2838    let typ = match &arg.script_type {
2839        Some(t) => t,
2840        None => {
2841            return Err(anyhow::anyhow!("No script type specified"));
2842        }
2843    };
2844    let builder = scripts::BUILDER
2845        .iter()
2846        .find(|b| b.script_type() == typ)
2847        .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
2848
2849    #[cfg(feature = "image")]
2850    if builder.is_image() {
2851        if !builder.can_create_image_file() {
2852            return Err(anyhow::anyhow!(
2853                "Script type {:?} does not support image file creation",
2854                typ
2855            ));
2856        }
2857        let data = utils::img::decode_img(
2858            arg.image_type.unwrap_or_else(|| {
2859                types::ImageOutputType::try_from(std::path::Path::new(input))
2860                    .unwrap_or(types::ImageOutputType::Png)
2861            }),
2862            input,
2863        )?;
2864        let output = match output {
2865            Some(output) => output.to_string(),
2866            None => {
2867                let mut pb = std::path::PathBuf::from(input);
2868                let ext = builder.extensions().first().unwrap_or(&"");
2869                pb.set_extension(ext);
2870                if pb.to_string_lossy() == input {
2871                    if ext.is_empty() {
2872                        pb.set_extension("unk");
2873                    } else {
2874                        pb.set_extension(format!("{}.{}", ext, ext));
2875                    }
2876                }
2877                pb.to_string_lossy().into_owned()
2878            }
2879        };
2880        builder.create_image_file_filename(data, &output, input, &config)?;
2881        return Ok(());
2882    }
2883
2884    if !builder.can_create_file() {
2885        return Err(anyhow::anyhow!(
2886            "Script type {:?} does not support file creation",
2887            typ
2888        ));
2889    }
2890
2891    let output = match output {
2892        Some(output) => output.to_string(),
2893        None => {
2894            let mut pb = std::path::PathBuf::from(input);
2895            let ext = builder.extensions().first().unwrap_or(&"");
2896            pb.set_extension(ext);
2897            if pb.to_string_lossy() == input {
2898                if ext.is_empty() {
2899                    pb.set_extension("unk");
2900                } else {
2901                    pb.set_extension(format!("{}.{}", ext, ext));
2902                }
2903            }
2904            pb.to_string_lossy().into_owned()
2905        }
2906    };
2907
2908    crate::utils::files::make_sure_dir_exists(&output)?;
2909
2910    builder.create_file_filename(
2911        input,
2912        &output,
2913        get_encoding(arg, builder),
2914        get_output_encoding(arg),
2915        &config,
2916    )?;
2917    Ok(())
2918}
2919
2920pub fn parse_output_script_as_extend(
2921    input: &str,
2922    typ: types::OutputScriptType,
2923    arg: &args::Arg,
2924) -> anyhow::Result<Vec<types::ExtendedMessage>> {
2925    match typ {
2926        types::OutputScriptType::M3t
2927        | types::OutputScriptType::M3ta
2928        | types::OutputScriptType::M3tTxt => {
2929            let enc = get_input_output_script_encoding(arg);
2930            let b = utils::files::read_file(input)?;
2931            let s = utils::encoding::decode_to_string(enc, &b, true)?;
2932            let mut parser = output_scripts::m3t::M3tParser::new(
2933                &s,
2934                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2935                arg.m3t_use_original_text,
2936            );
2937            let mes = parser.parse_as_extend()?;
2938            Ok(mes)
2939        }
2940        types::OutputScriptType::Po | types::OutputScriptType::Pot => {
2941            let enc = get_input_output_script_encoding(arg);
2942            let b = utils::files::read_file(input)?;
2943            let s = utils::encoding::decode_to_string(enc, &b, true)?;
2944            let mut parser = output_scripts::po::PoParser::new(
2945                &s,
2946                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2947            );
2948            let mes = parser.parse_as_extend()?;
2949            Ok(mes)
2950        }
2951        _ => Err(anyhow::anyhow!(
2952            "Output script type {:?} does not support extended messages",
2953            typ
2954        )),
2955    }
2956}
2957
2958pub fn parse_output_script(
2959    input: &str,
2960    typ: types::OutputScriptType,
2961    arg: &args::Arg,
2962) -> anyhow::Result<Vec<types::Message>> {
2963    match typ {
2964        types::OutputScriptType::M3t
2965        | types::OutputScriptType::M3ta
2966        | types::OutputScriptType::M3tTxt => {
2967            let enc = get_input_output_script_encoding(arg);
2968            let b = utils::files::read_file(input)?;
2969            let s = utils::encoding::decode_to_string(enc, &b, true)?;
2970            let mut parser = output_scripts::m3t::M3tParser::new(
2971                &s,
2972                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2973                arg.m3t_use_original_text,
2974            );
2975            let mes = parser.parse()?;
2976            Ok(mes)
2977        }
2978        types::OutputScriptType::Po | types::OutputScriptType::Pot => {
2979            let enc = get_input_output_script_encoding(arg);
2980            let b = utils::files::read_file(input)?;
2981            let s = utils::encoding::decode_to_string(enc, &b, true)?;
2982            let mut parser = output_scripts::po::PoParser::new(
2983                &s,
2984                arg.llm_trans_mark.as_ref().map(|s| s.as_str()),
2985            );
2986            let mes = parser.parse()?;
2987            Ok(mes)
2988        }
2989        types::OutputScriptType::Json => {
2990            let enc = get_input_output_script_encoding(arg);
2991            let b = utils::files::read_file(input)?;
2992            let s = utils::encoding::decode_to_string(enc, &b, true)?;
2993            let mes = serde_json::from_str::<Vec<types::Message>>(&s)?;
2994            Ok(mes)
2995        }
2996        types::OutputScriptType::Yaml => {
2997            let enc = get_input_output_script_encoding(arg);
2998            let b = utils::files::read_file(input)?;
2999            let s = utils::encoding::decode_to_string(enc, &b, true)?;
3000            let mes = serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?;
3001            Ok(mes)
3002        }
3003        _ => Err(anyhow::anyhow!(
3004            "Output script type {:?} does not support message parsing",
3005            typ
3006        )),
3007    }
3008}
3009
3010pub fn dump_output_script_as_extend(
3011    output: &str,
3012    typ: types::OutputScriptType,
3013    mes: &[types::ExtendedMessage],
3014    arg: &args::Arg,
3015) -> anyhow::Result<()> {
3016    match typ {
3017        types::OutputScriptType::M3t
3018        | types::OutputScriptType::M3ta
3019        | types::OutputScriptType::M3tTxt => {
3020            let enc = get_output_encoding(arg);
3021            let s = output_scripts::m3t::M3tDumper::dump_extended(mes);
3022            let b = utils::encoding::encode_string(enc, &s, false)?;
3023            utils::files::write_file(output)?.write_all(&b)?;
3024            Ok(())
3025        }
3026        types::OutputScriptType::Po | types::OutputScriptType::Pot => {
3027            let enc = get_output_encoding(arg);
3028            let s = output_scripts::po::PoDumper::new();
3029            let s = s.dump_extended(mes, enc)?;
3030            let b = utils::encoding::encode_string(enc, &s, false)?;
3031            utils::files::write_file(output)?.write_all(&b)?;
3032            Ok(())
3033        }
3034        _ => Err(anyhow::anyhow!(
3035            "Output script type {:?} does not support extended messages",
3036            typ
3037        )),
3038    }
3039}
3040
3041pub fn dump_output_script(
3042    output: &str,
3043    typ: types::OutputScriptType,
3044    mes: &[types::Message],
3045    arg: &args::Arg,
3046) -> anyhow::Result<()> {
3047    match typ {
3048        types::OutputScriptType::M3t
3049        | types::OutputScriptType::M3ta
3050        | types::OutputScriptType::M3tTxt => {
3051            let enc = get_output_encoding(arg);
3052            let s = output_scripts::m3t::M3tDumper::dump(mes, arg.m3t_no_quote);
3053            let b = utils::encoding::encode_string(enc, &s, false)?;
3054            utils::files::write_file(output)?.write_all(&b)?;
3055            Ok(())
3056        }
3057        types::OutputScriptType::Po | types::OutputScriptType::Pot => {
3058            let enc = get_output_encoding(arg);
3059            let s = output_scripts::po::PoDumper::new();
3060            let s = s.dump(mes, enc)?;
3061            let b = utils::encoding::encode_string(enc, &s, false)?;
3062            utils::files::write_file(output)?.write_all(&b)?;
3063            Ok(())
3064        }
3065        types::OutputScriptType::Json => {
3066            let enc = get_output_encoding(arg);
3067            let s = serde_json::to_string_pretty(mes)?;
3068            let b = utils::encoding::encode_string(enc, &s, false)?;
3069            utils::files::write_file(output)?.write_all(&b)?;
3070            Ok(())
3071        }
3072        types::OutputScriptType::Yaml => {
3073            let enc = get_output_encoding(arg);
3074            let s = serde_yaml_ng::to_string(mes)?;
3075            let b = utils::encoding::encode_string(enc, &s, false)?;
3076            utils::files::write_file(output)?.write_all(&b)?;
3077            Ok(())
3078        }
3079        _ => Err(anyhow::anyhow!(
3080            "Output script type {:?} does not support message dumping",
3081            typ
3082        )),
3083    }
3084}
3085
3086pub fn convert_file(
3087    input: &str,
3088    input_type: types::OutputScriptType,
3089    output: Option<&str>,
3090    output_type: types::OutputScriptType,
3091    arg: &args::Arg,
3092    root_dir: Option<&std::path::Path>,
3093) -> anyhow::Result<types::ScriptResult> {
3094    let input_support_src = input_type.is_src_supported();
3095    let output_support_src = output_type.is_src_supported();
3096    let output = match output {
3097        Some(output) => match root_dir {
3098            Some(root_dir) => {
3099                let f = std::path::PathBuf::from(input);
3100                let mut pb = std::path::PathBuf::from(output);
3101                let rpath = utils::files::relative_path(root_dir, &f);
3102                if let Some(parent) = rpath.parent() {
3103                    pb.push(parent);
3104                }
3105                if let Some(fname) = f.file_name() {
3106                    pb.push(fname);
3107                }
3108                if arg.output_no_extra_ext {
3109                    pb.remove_all_extensions();
3110                }
3111                pb.set_extension(output_type.as_ref());
3112                pb.to_string_lossy().into_owned()
3113            }
3114            None => output.to_string(),
3115        },
3116        None => {
3117            let mut pb = std::path::PathBuf::from(input);
3118            if arg.output_no_extra_ext {
3119                pb.remove_all_extensions();
3120            }
3121            pb.set_extension(output_type.as_ref());
3122            pb.to_string_lossy().into_owned()
3123        }
3124    };
3125    if input_support_src && output_support_src {
3126        let input_mes = parse_output_script_as_extend(input, input_type, arg)?;
3127        dump_output_script_as_extend(&output, output_type, &input_mes, arg)?;
3128        return Ok(types::ScriptResult::Ok);
3129    }
3130    let input_mes = parse_output_script(input, input_type, arg)?;
3131    dump_output_script(&output, output_type, &input_mes, arg)?;
3132    Ok(types::ScriptResult::Ok)
3133}
3134
3135lazy_static::lazy_static! {
3136    static ref COUNTER: utils::counter::Counter = utils::counter::Counter::new();
3137    static ref EXIT_LISTENER: std::sync::Mutex<std::collections::BTreeMap<usize, Box<dyn Fn() + Send + Sync>>> = std::sync::Mutex::new(std::collections::BTreeMap::new());
3138    #[allow(unused)]
3139    static ref EXIT_LISTENER_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
3140}
3141
3142#[allow(dead_code)]
3143fn add_exit_listener<F: Fn() + Send + Sync + 'static>(f: F) -> usize {
3144    let id = EXIT_LISTENER_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
3145    EXIT_LISTENER
3146        .lock()
3147        .unwrap_or_else(|err| err.into_inner())
3148        .insert(id, Box::new(f));
3149    id
3150}
3151
3152#[allow(dead_code)]
3153fn remove_exit_listener(id: usize) {
3154    EXIT_LISTENER
3155        .lock()
3156        .unwrap_or_else(|err| err.into_inner())
3157        .remove(&id);
3158}
3159
3160fn main() {
3161    let _ = ctrlc::try_set_handler(|| {
3162        let listeners = EXIT_LISTENER.lock().unwrap_or_else(|err| err.into_inner());
3163        for (_, f) in listeners.iter() {
3164            f();
3165        }
3166        eprintln!("Aborted.");
3167        eprintln!("{}", std::ops::Deref::deref(&COUNTER));
3168        std::process::exit(1);
3169    });
3170    let arg = args::parse_args();
3171    let argn = std::sync::Arc::new(arg.clone());
3172    if arg.backtrace {
3173        unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
3174    }
3175    let cfg = std::sync::Arc::new(types::ExtraConfig {
3176        #[cfg(feature = "circus")]
3177        circus_mes_type: arg.circus_mes_type.clone(),
3178        #[cfg(feature = "escude-arc")]
3179        escude_fake_compress: arg.escude_fake_compress,
3180        #[cfg(feature = "escude")]
3181        escude_enum_scr: arg.escude_enum_scr.clone(),
3182        #[cfg(feature = "bgi")]
3183        bgi_import_duplicate: arg.bgi_import_duplicate,
3184        #[cfg(feature = "bgi")]
3185        bgi_disable_append: arg.bgi_disable_append,
3186        #[cfg(feature = "image")]
3187        image_type: arg.image_type.clone(),
3188        #[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
3189        bgi_is_sysgrp_arc: arg.bgi_is_sysgrp_arc.clone(),
3190        #[cfg(feature = "bgi-img")]
3191        bgi_img_scramble: arg.bgi_img_scramble.clone(),
3192        #[cfg(feature = "cat-system-arc")]
3193        cat_system_int_encrypt_password: args::get_cat_system_int_encrypt_password(&arg)
3194            .expect("Failed to get CatSystem2 int encrypt password"),
3195        #[cfg(feature = "cat-system-img")]
3196        cat_system_image_canvas: arg.cat_system_image_canvas,
3197        #[cfg(feature = "kirikiri")]
3198        kirikiri_language_index: arg.kirikiri_language_index.clone(),
3199        #[cfg(feature = "kirikiri")]
3200        kirikiri_export_chat: arg.kirikiri_export_chat,
3201        #[cfg(feature = "kirikiri")]
3202        kirikiri_chat_key: arg.kirikiri_chat_key.clone(),
3203        #[cfg(feature = "kirikiri")]
3204        kirikiri_chat_json: args::load_kirikiri_chat_json(&arg)
3205            .expect("Failed to load Kirikiri chat JSON"),
3206        #[cfg(feature = "kirikiri")]
3207        kirikiri_languages: arg
3208            .kirikiri_languages
3209            .clone()
3210            .map(|s| std::sync::Arc::new(s)),
3211        #[cfg(feature = "kirikiri")]
3212        kirikiri_remove_empty_lines: arg.kirikiri_remove_empty_lines,
3213        #[cfg(feature = "kirikiri")]
3214        kirikiri_name_commands: std::sync::Arc::new(std::collections::HashSet::from_iter(
3215            arg.kirikiri_name_commands.iter().cloned(),
3216        )),
3217        #[cfg(feature = "kirikiri")]
3218        kirikiri_message_commands: std::sync::Arc::new(std::collections::HashSet::from_iter(
3219            arg.kirikiri_message_commands.iter().cloned(),
3220        )),
3221        #[cfg(feature = "bgi-arc")]
3222        bgi_compress_file: arg.bgi_compress_file,
3223        #[cfg(feature = "bgi-arc")]
3224        bgi_compress_min_len: arg.bgi_compress_min_len,
3225        #[cfg(feature = "emote-img")]
3226        emote_pimg_overlay: arg.emote_pimg_overlay,
3227        #[cfg(feature = "artemis-arc")]
3228        artemis_arc_disable_xor: arg.artemis_arc_disable_xor,
3229        #[cfg(feature = "artemis")]
3230        artemis_indent: arg.artemis_indent,
3231        #[cfg(feature = "artemis")]
3232        artemis_no_indent: arg.artemis_no_indent,
3233        #[cfg(feature = "artemis")]
3234        artemis_max_line_width: arg.artemis_max_line_width,
3235        #[cfg(feature = "artemis")]
3236        artemis_ast_lang: arg.artemis_ast_lang.clone(),
3237        #[cfg(feature = "cat-system")]
3238        cat_system_cstl_lang: arg.cat_system_cstl_lang.clone(),
3239        #[cfg(feature = "flate2")]
3240        zlib_compression_level: arg.zlib_compression_level,
3241        #[cfg(feature = "image")]
3242        png_compression_level: arg.png_compression_level,
3243        #[cfg(feature = "circus-img")]
3244        circus_crx_keep_original_bpp: arg.circus_crx_keep_original_bpp,
3245        #[cfg(feature = "circus-img")]
3246        circus_crx_zstd: arg.circus_crx_zstd,
3247        #[cfg(feature = "zstd")]
3248        zstd_compression_level: arg.zstd_compression_level,
3249        #[cfg(feature = "circus-img")]
3250        circus_crx_mode: arg.circus_crx_mode,
3251        #[cfg(feature = "ex-hibit")]
3252        ex_hibit_rld_xor_key: args::load_ex_hibit_rld_xor_key(&arg)
3253            .expect("Failed to load RLD XOR key"),
3254        #[cfg(feature = "ex-hibit")]
3255        ex_hibit_rld_def_xor_key: args::load_ex_hibit_rld_def_xor_key(&arg)
3256            .expect("Failed to load RLD DEF XOR key"),
3257        #[cfg(feature = "ex-hibit")]
3258        ex_hibit_rld_keys: scripts::ex_hibit::rld::load_keys(arg.ex_hibit_rld_keys.as_ref())
3259            .expect("Failed to load RLD keys"),
3260        #[cfg(feature = "ex-hibit")]
3261        ex_hibit_rld_def_keys: scripts::ex_hibit::rld::load_keys(
3262            arg.ex_hibit_rld_def_keys.as_ref(),
3263        )
3264        .expect("Failed to load RLD DEF keys"),
3265        #[cfg(feature = "mozjpeg")]
3266        jpeg_quality: arg.jpeg_quality,
3267        #[cfg(feature = "webp")]
3268        webp_lossless: arg.webp_lossless,
3269        #[cfg(feature = "webp")]
3270        webp_quality: arg.webp_quality,
3271        #[cfg(feature = "circus-img")]
3272        circus_crx_canvas: arg.circus_crx_canvas,
3273        custom_yaml: arg.custom_yaml.unwrap_or_else(|| {
3274            arg.output_type
3275                .map(|s| s == types::OutputScriptType::Yaml)
3276                .unwrap_or(false)
3277        }),
3278        #[cfg(feature = "entis-gls")]
3279        entis_gls_srcxml_lang: arg.entis_gls_srcxml_lang.clone(),
3280        #[cfg(feature = "will-plus")]
3281        will_plus_ws2_no_disasm: arg.will_plus_ws2_no_disasm,
3282        #[cfg(feature = "artemis-panmimisoft")]
3283        artemis_panmimisoft_txt_blacklist_names: std::sync::Arc::new(
3284            args::get_artemis_panmimisoft_txt_blacklist_names(&arg).unwrap(),
3285        ),
3286        #[cfg(feature = "artemis-panmimisoft")]
3287        artemis_panmimisoft_txt_lang: arg.artemis_panmimisoft_txt_lang.clone(),
3288        #[cfg(feature = "lossless-audio")]
3289        lossless_audio_fmt: arg.lossless_audio_fmt,
3290        #[cfg(feature = "audio-flac")]
3291        flac_compression_level: arg.flac_compression_level,
3292        #[cfg(feature = "artemis")]
3293        artemis_asb_format_lua: !arg.artemis_asb_no_format_lua,
3294        #[cfg(feature = "kirikiri")]
3295        kirikiri_title: arg.kirikiri_title,
3296        #[cfg(feature = "favorite")]
3297        favorite_hcb_filter_ascii: !arg.favorite_hcb_no_filter_ascii,
3298        #[cfg(feature = "bgi-img")]
3299        bgi_img_workers: arg.bgi_img_workers,
3300        #[cfg(feature = "image-jxl")]
3301        jxl_lossless: !arg.jxl_lossy,
3302        #[cfg(feature = "image-jxl")]
3303        jxl_distance: arg.jxl_distance,
3304        #[cfg(feature = "image-jxl")]
3305        jxl_workers: arg.jxl_workers,
3306        #[cfg(feature = "emote-img")]
3307        psb_process_tlg: !arg.psb_no_process_tlg,
3308        #[cfg(feature = "softpal-img")]
3309        pgd_fake_compress: !arg.pgd_compress,
3310        #[cfg(feature = "softpal")]
3311        softpal_add_message_index: arg.softpal_add_message_index,
3312        #[cfg(feature = "kirikiri")]
3313        kirikiri_chat_multilang: !arg.kirikiri_chat_no_multilang,
3314        #[cfg(feature = "kirikiri-arc")]
3315        xp3_simple_crypt: !arg.xp3_no_simple_crypt,
3316        #[cfg(feature = "kirikiri-arc")]
3317        xp3_mdf_decompress: !arg.xp3_no_mdf_decompress,
3318        #[cfg(feature = "kirikiri-arc")]
3319        xp3_segmenter: arg.xp3_segmenter,
3320        #[cfg(feature = "kirikiri-arc")]
3321        xp3_compress_files: !arg.xp3_no_compress_files,
3322        #[cfg(feature = "kirikiri-arc")]
3323        xp3_compress_index: !arg.xp3_no_compress_index,
3324        #[cfg(feature = "kirikiri-arc")]
3325        xp3_compress_workers: arg.xp3_compress_workers,
3326        #[cfg(feature = "kirikiri-arc")]
3327        xp3_zstd: arg.xp3_zstd,
3328        #[cfg(feature = "kirikiri-arc")]
3329        xp3_zopfli: arg.xp3_zopfli,
3330        #[cfg(feature = "kirikiri-arc")]
3331        xp3_pack_workers: arg.xp3_pack_workers,
3332        #[cfg(feature = "kirikiri")]
3333        kirikiri_language_insert: arg.kirikiri_language_insert,
3334        #[cfg(feature = "musica-arc")]
3335        musica_game_title: arg.musica_game_title.clone(),
3336        #[cfg(feature = "musica-arc")]
3337        musica_xor_key: arg.musica_xor_key,
3338        #[cfg(feature = "musica-arc")]
3339        musica_compress: arg.musica_compress,
3340        #[cfg(feature = "kirikiri-arc")]
3341        xp3_no_adler: arg.xp3_no_adler,
3342        #[cfg(feature = "bgi")]
3343        bgi_add_space: arg.bgi_add_space,
3344        #[cfg(feature = "escude")]
3345        escude_op: arg.escude_op,
3346        #[cfg(feature = "zopfli")]
3347        zopfli_iteration_count: arg.zopfli_iteration_count,
3348        #[cfg(feature = "zopfli")]
3349        zopfli_iterations_without_improvement: arg.zopfli_iterations_without_improvement,
3350        #[cfg(feature = "zopfli")]
3351        zopfli_maximum_block_splits: arg.zopfli_maximum_block_splits,
3352        #[cfg(feature = "artemis-panmimisoft")]
3353        artemis_panmimisoft_txt_multi_lang: arg.artemis_panmimisoft_txt_multi_lang,
3354        #[cfg(feature = "entis-gls")]
3355        entis_gls_csx_disasm: arg.entis_gls_csx_disasm,
3356        #[cfg(feature = "entis-gls")]
3357        entis_gls_csx_lf: arg.entis_gls_csx_lf.clone(),
3358        #[cfg(feature = "entis-gls")]
3359        entis_gls_csx_ver: arg.entis_gls_csx_ver,
3360        #[cfg(feature = "entis-gls")]
3361        entis_gls_csx_v2_ver: arg.entis_gls_csx_v2_ver,
3362        #[cfg(feature = "entis-gls")]
3363        entis_gls_csx_no_part_label: arg.entis_gls_csx_no_part_label,
3364        #[cfg(feature = "qlie-img")]
3365        qlie_abmp10_process_abmp10: !arg.qlie_abmp10_no_process_abmp10,
3366        #[cfg(feature = "qlie-arc")]
3367        qlie_pack_keyfile: arg.qlie_pack_keyfile.clone(),
3368        #[cfg(feature = "qlie-arc")]
3369        qlie_pack_compress_files: arg.qlie_pack_compress_files,
3370        #[cfg(feature = "qlie-img")]
3371        qlie_dpng_use_raw_png: arg.qlie_dpng_use_raw_png,
3372        #[cfg(feature = "qlie-img")]
3373        qlie_dpng_psd: arg.qlie_dpng_psd,
3374        #[cfg(feature = "utils-psd")]
3375        psd_compress: !arg.psd_no_compress,
3376        #[cfg(feature = "emote-img")]
3377        emote_pimg_psd: arg.emote_pimg_psd,
3378        #[cfg(feature = "kirikiri")]
3379        kirikiri_ks_hitret: arg.kirikiri_ks_hitret,
3380        #[cfg(feature = "kirikiri")]
3381        kirikiri_ks_lf: arg.kirikiri_ks_lf.clone(),
3382        #[cfg(feature = "kirikiri")]
3383        kirikiri_message_tags: std::sync::Arc::new(std::collections::HashSet::from_iter(
3384            arg.kirikiri_message_tags.iter().cloned(),
3385        )),
3386        #[cfg(feature = "kirikiri")]
3387        kirikiri_ks_bom: arg.kirikiri_ks_bom,
3388        #[cfg(feature = "emote-img")]
3389        bc7: arg.bc7,
3390    });
3391    match &arg.command {
3392        args::Command::Export { input, output } => {
3393            let (scripts, is_dir) =
3394                utils::files::collect_files(input, arg.recursive, false).unwrap();
3395            if is_dir {
3396                match &output {
3397                    Some(output) => {
3398                        let op = std::path::Path::new(output);
3399                        if op.exists() {
3400                            if !op.is_dir() {
3401                                eprintln!("Output path is not a directory");
3402                                std::process::exit(
3403                                    argn.exit_code_all_failed.unwrap_or(argn.exit_code),
3404                                );
3405                            }
3406                        } else {
3407                            std::fs::create_dir_all(op).unwrap();
3408                        }
3409                    }
3410                    None => {}
3411                }
3412            }
3413            let root_dir = if is_dir {
3414                Some(std::path::Path::new(input))
3415            } else {
3416                None
3417            };
3418            #[cfg(feature = "image")]
3419            let img_threadpool = if arg.image_workers > 1 {
3420                let tp = std::sync::Arc::new(
3421                    utils::threadpool::ThreadPool::<Result<(), anyhow::Error>>::new(
3422                        arg.image_workers,
3423                        Some("img-output-worker-"),
3424                        false,
3425                    )
3426                    .expect("Failed to create image thread pool"),
3427                );
3428                let tp2 = tp.clone();
3429                let id = add_exit_listener(move || {
3430                    for r in tp2.take_results() {
3431                        if let Err(e) = r {
3432                            eprintln!("{}", e);
3433                            COUNTER.inc_error();
3434                        } else {
3435                            COUNTER.inc(types::ScriptResult::Ok);
3436                        }
3437                    }
3438                });
3439                Some((tp, id))
3440            } else {
3441                None
3442            };
3443            for script in scripts.iter() {
3444                #[cfg(feature = "image")]
3445                let re = export_script(
3446                    &script,
3447                    &arg,
3448                    cfg.clone(),
3449                    output,
3450                    root_dir,
3451                    img_threadpool.as_ref().map(|(t, _)| &**t),
3452                );
3453                #[cfg(not(feature = "image"))]
3454                let re = export_script(&script, &arg, cfg.clone(), output, root_dir);
3455                match re {
3456                    Ok(s) => {
3457                        COUNTER.inc(s);
3458                    }
3459                    Err(e) => {
3460                        COUNTER.inc_error();
3461                        eprintln!("Error exporting {}: {}", script, e);
3462                        if arg.backtrace {
3463                            eprintln!("Backtrace: {}", e.backtrace());
3464                        }
3465                    }
3466                }
3467                #[cfg(feature = "image")]
3468                img_threadpool.as_ref().map(|(t, _)| {
3469                    for r in t.take_results() {
3470                        if let Err(e) = r {
3471                            COUNTER.inc_error();
3472                            eprintln!("{}", e);
3473                        } else {
3474                            COUNTER.inc(types::ScriptResult::Ok);
3475                        }
3476                    }
3477                });
3478            }
3479            #[cfg(feature = "image")]
3480            img_threadpool.map(|(t, id)| {
3481                t.join();
3482                remove_exit_listener(id);
3483                for r in t.take_results() {
3484                    if let Err(e) = r {
3485                        COUNTER.inc_error();
3486                        eprintln!("{}", e);
3487                    } else {
3488                        COUNTER.inc(types::ScriptResult::Ok);
3489                    }
3490                }
3491            });
3492        }
3493        args::Command::Import(args) => {
3494            let name_csv = match &args.name_csv {
3495                Some(name_csv) => {
3496                    let name_table = utils::name_replacement::read_csv(name_csv).unwrap();
3497                    Some(name_table)
3498                }
3499                None => None,
3500            };
3501            let repl = std::sync::Arc::new(match &args.replacement_json {
3502                Some(replacement_json) => {
3503                    let b = utils::files::read_file(replacement_json).unwrap();
3504                    let s = String::from_utf8(b).unwrap();
3505                    let table = serde_json::from_str::<types::ReplacementTable>(&s).unwrap();
3506                    Some(table)
3507                }
3508                None => None,
3509            });
3510            let (scripts, is_dir) =
3511                utils::files::collect_files(&args.input, arg.recursive, false).unwrap();
3512            if is_dir {
3513                let pb = std::path::Path::new(&args.patched);
3514                if pb.exists() {
3515                    if !pb.is_dir() {
3516                        eprintln!("Patched path is not a directory");
3517                        std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
3518                    }
3519                } else {
3520                    std::fs::create_dir_all(pb).unwrap();
3521                }
3522            }
3523            let root_dir = if is_dir {
3524                Some(std::path::Path::new(&args.input))
3525            } else {
3526                None
3527            };
3528            let workers = if args.jobs > 1 {
3529                Some(
3530                    utils::threadpool::ThreadPool::<()>::new(
3531                        args.jobs,
3532                        Some("import-worker-"),
3533                        true,
3534                    )
3535                    .unwrap(),
3536                )
3537            } else {
3538                None
3539            };
3540            let dep_files = if args.dep_file.is_some() {
3541                Some(std::sync::Arc::new(std::sync::Mutex::new(
3542                    std::collections::HashMap::new(),
3543                )))
3544            } else {
3545                None
3546            };
3547            for script in scripts.iter() {
3548                if let Some(workers) = workers.as_ref() {
3549                    let arg = argn.clone();
3550                    let cfg = cfg.clone();
3551                    let script = script.clone();
3552                    let name_csv = name_csv.as_ref().map(|s| s.clone());
3553                    let repl = repl.clone();
3554                    let root_dir = root_dir.map(|s| s.to_path_buf());
3555                    let args = args.clone();
3556                    let dep_files = dep_files.clone();
3557                    if let Err(e) = workers.execute(
3558                        move |_| {
3559                            let mut dep_graph = if dep_files.is_some() {
3560                                Some((String::new(), Vec::new()))
3561                            } else {
3562                                None
3563                            };
3564                            let re = import_script(
3565                                &script,
3566                                &arg,
3567                                cfg,
3568                                &args,
3569                                root_dir.as_ref().map(|s| s.as_path()),
3570                                name_csv.as_ref(),
3571                                (*repl).as_ref(),
3572                                dep_graph.as_mut(),
3573                            );
3574                            match re {
3575                                Ok(s) => {
3576                                    COUNTER.inc(s);
3577                                    if let Some((fname, deps)) = dep_graph {
3578                                        if let Some(dep_files) = dep_files {
3579                                            let mut lock =
3580                                                crate::ext::mutex::MutexExt::lock_blocking(
3581                                                    dep_files.as_ref(),
3582                                                );
3583                                            lock.insert(fname, deps);
3584                                        }
3585                                    }
3586                                }
3587                                Err(e) => {
3588                                    COUNTER.inc_error();
3589                                    eprintln!("Error exporting {}: {}", script, e);
3590                                    if arg.backtrace {
3591                                        eprintln!("Backtrace: {}", e.backtrace());
3592                                    }
3593                                }
3594                            }
3595                        },
3596                        true,
3597                    ) {
3598                        COUNTER.inc_error();
3599                        eprintln!("Error executing import worker: {}", e);
3600                    }
3601                } else {
3602                    let mut dep_graph = if dep_files.is_some() {
3603                        Some((String::new(), Vec::new()))
3604                    } else {
3605                        None
3606                    };
3607                    let re = import_script(
3608                        &script,
3609                        &arg,
3610                        cfg.clone(),
3611                        args,
3612                        root_dir,
3613                        name_csv.as_ref(),
3614                        (*repl).as_ref(),
3615                        dep_graph.as_mut(),
3616                    );
3617                    match re {
3618                        Ok(s) => {
3619                            COUNTER.inc(s);
3620                            if let Some((fname, deps)) = dep_graph {
3621                                if let Some(dep_files) = dep_files.as_ref() {
3622                                    let mut lock = crate::ext::mutex::MutexExt::lock_blocking(
3623                                        dep_files.as_ref(),
3624                                    );
3625                                    lock.insert(fname, deps);
3626                                }
3627                            }
3628                        }
3629                        Err(e) => {
3630                            COUNTER.inc_error();
3631                            eprintln!("Error exporting {}: {}", script, e);
3632                            if arg.backtrace {
3633                                eprintln!("Backtrace: {}", e.backtrace());
3634                            }
3635                        }
3636                    }
3637                }
3638            }
3639            if let Some(map) = dep_files {
3640                let lock = crate::ext::mutex::MutexExt::lock_blocking(map.as_ref());
3641                if let Some(dep_file) = &args.dep_file {
3642                    let df = std::fs::File::create(dep_file).unwrap();
3643                    let mut df = std::io::BufWriter::new(df);
3644                    use std::io::Write;
3645                    for (fname, deps) in lock.iter() {
3646                        write!(df, "{}:", escape_dep_string(fname)).unwrap();
3647                        for d in deps {
3648                            write!(df, " {}", escape_dep_string(d)).unwrap();
3649                        }
3650                        writeln!(df).unwrap();
3651                    }
3652                }
3653            }
3654        }
3655        args::Command::Pack {
3656            input,
3657            output,
3658            backslash,
3659        } => {
3660            let re = pack_archive(
3661                input,
3662                output.as_ref().map(|s| s.as_str()),
3663                &arg,
3664                cfg.clone(),
3665                *backslash,
3666            );
3667            if let Err(e) = re {
3668                COUNTER.inc_error();
3669                eprintln!("Error packing archive: {}", e);
3670            }
3671        }
3672        args::Command::Unpack { input, output } => {
3673            let (scripts, is_dir) = utils::files::collect_arc_files(input, arg.recursive).unwrap();
3674            if is_dir {
3675                match &output {
3676                    Some(output) => {
3677                        let op = std::path::Path::new(output);
3678                        if op.exists() {
3679                            if !op.is_dir() {
3680                                eprintln!("Output path is not a directory");
3681                                std::process::exit(
3682                                    argn.exit_code_all_failed.unwrap_or(argn.exit_code),
3683                                );
3684                            }
3685                        } else {
3686                            std::fs::create_dir_all(op).unwrap();
3687                        }
3688                    }
3689                    None => {}
3690                }
3691            }
3692            let root_dir = if is_dir {
3693                Some(std::path::Path::new(input))
3694            } else {
3695                None
3696            };
3697            for script in scripts.iter() {
3698                let re = unpack_archive(&script, &arg, cfg.clone(), output, root_dir);
3699                match re {
3700                    Ok(s) => {
3701                        COUNTER.inc(s);
3702                    }
3703                    Err(e) => {
3704                        COUNTER.inc_error();
3705                        eprintln!("Error unpacking {}: {}", script, e);
3706                        if arg.backtrace {
3707                            eprintln!("Backtrace: {}", e.backtrace());
3708                        }
3709                    }
3710                }
3711            }
3712        }
3713        args::Command::Create { input, output } => {
3714            let re = create_file(
3715                input,
3716                output.as_ref().map(|s| s.as_str()),
3717                &arg,
3718                cfg.clone(),
3719            );
3720            if let Err(e) = re {
3721                COUNTER.inc_error();
3722                eprintln!("Error creating file: {}", e);
3723                if arg.backtrace {
3724                    eprintln!("Backtrace: {}", e.backtrace());
3725                }
3726            }
3727        }
3728        args::Command::PackV2 {
3729            output,
3730            input,
3731            backslash,
3732            no_dir,
3733            dep_file,
3734        } => {
3735            if !input.is_empty() {
3736                let input = input.iter().map(|s| s.as_str()).collect::<Vec<_>>();
3737                let re = pack_archive_v2(
3738                    &input,
3739                    output.as_ref().map(|s| s.as_str()),
3740                    &arg,
3741                    cfg.clone(),
3742                    *backslash,
3743                    *no_dir,
3744                    dep_file.as_ref().map(|s| s.as_str()),
3745                );
3746                if let Err(e) = re {
3747                    COUNTER.inc_error();
3748                    eprintln!("Error packing archive: {}", e);
3749                }
3750            } else {
3751                eprintln!("No input files specified for packing.");
3752            }
3753        }
3754        args::Command::Convert {
3755            input_type,
3756            output_type,
3757            input,
3758            output,
3759        } => {
3760            if input_type.is_custom() {
3761                eprintln!("Custom input type is not supported for conversion.");
3762                std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
3763            }
3764            if output_type.is_custom() {
3765                eprintln!("Custom output type is not supported for conversion.");
3766                std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
3767            }
3768            let (scripts, is_dir) =
3769                utils::files::collect_ext_files(input, arg.recursive, &[input_type.as_ref()])
3770                    .unwrap();
3771            if is_dir {
3772                match &output {
3773                    Some(output) => {
3774                        let op = std::path::Path::new(output);
3775                        if op.exists() {
3776                            if !op.is_dir() {
3777                                eprintln!("Output path is not a directory");
3778                                std::process::exit(
3779                                    argn.exit_code_all_failed.unwrap_or(argn.exit_code),
3780                                );
3781                            }
3782                        } else {
3783                            std::fs::create_dir_all(op).unwrap();
3784                        }
3785                    }
3786                    None => {}
3787                }
3788            }
3789            let root_dir = if is_dir {
3790                Some(std::path::Path::new(input))
3791            } else {
3792                None
3793            };
3794            for script in scripts.iter() {
3795                let re = convert_file(
3796                    &script,
3797                    *input_type,
3798                    output.as_ref().map(|s| s.as_str()),
3799                    *output_type,
3800                    &arg,
3801                    root_dir,
3802                );
3803                match re {
3804                    Ok(s) => {
3805                        COUNTER.inc(s);
3806                    }
3807                    Err(e) => {
3808                        COUNTER.inc_error();
3809                        eprintln!("Error converting {}: {}", script, e);
3810                        if arg.backtrace {
3811                            eprintln!("Backtrace: {}", e.backtrace());
3812                        }
3813                    }
3814                }
3815            }
3816        }
3817    }
3818    let counter = std::ops::Deref::deref(&COUNTER);
3819    eprintln!("{}", counter);
3820    if counter.all_failed() {
3821        std::process::exit(argn.exit_code_all_failed.unwrap_or(argn.exit_code));
3822    } else if counter.has_error() {
3823        std::process::exit(argn.exit_code);
3824    }
3825}