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