msg_tool/
main.rs

1#![cfg_attr(any(docsrs, feature = "unstable"), feature(doc_auto_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 scripts::base::ArchiveContent;
11
12fn get_encoding(
13    arg: &args::Arg,
14    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
15) -> types::Encoding {
16    match &arg.encoding {
17        Some(enc) => {
18            return match enc {
19                &types::TextEncoding::Default => builder.default_encoding(),
20                &types::TextEncoding::Auto => types::Encoding::Auto,
21                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
22                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
23                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
24            };
25        }
26        None => {}
27    }
28    #[cfg(windows)]
29    match &arg.code_page {
30        Some(code_page) => {
31            return types::Encoding::CodePage(*code_page);
32        }
33        None => {}
34    }
35    builder.default_encoding()
36}
37
38fn get_archived_encoding(
39    arg: &args::Arg,
40    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
41    encoding: types::Encoding,
42) -> types::Encoding {
43    match &arg.archive_encoding {
44        Some(enc) => {
45            return match enc {
46                &types::TextEncoding::Default => builder
47                    .default_archive_encoding()
48                    .unwrap_or_else(|| builder.default_encoding()),
49                &types::TextEncoding::Auto => types::Encoding::Auto,
50                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
51                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
52                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
53            };
54        }
55        None => {}
56    }
57    #[cfg(windows)]
58    match &arg.archive_code_page {
59        Some(code_page) => {
60            return types::Encoding::CodePage(*code_page);
61        }
62        None => {}
63    }
64    builder.default_archive_encoding().unwrap_or(encoding)
65}
66
67fn get_output_encoding(arg: &args::Arg) -> types::Encoding {
68    match &arg.output_encoding {
69        Some(enc) => {
70            return match enc {
71                &types::TextEncoding::Default => types::Encoding::Utf8,
72                &types::TextEncoding::Auto => types::Encoding::Utf8,
73                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
74                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
75                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
76            };
77        }
78        None => {}
79    }
80    #[cfg(windows)]
81    match &arg.output_code_page {
82        Some(code_page) => {
83            return types::Encoding::CodePage(*code_page);
84        }
85        None => {}
86    }
87    types::Encoding::Utf8
88}
89
90fn get_patched_encoding(
91    arg: &args::ImportArgs,
92    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
93) -> types::Encoding {
94    match &arg.patched_encoding {
95        Some(enc) => {
96            return match enc {
97                &types::TextEncoding::Default => builder.default_patched_encoding(),
98                &types::TextEncoding::Auto => types::Encoding::Utf8,
99                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
100                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
101                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
102            };
103        }
104        None => {}
105    }
106    #[cfg(windows)]
107    match &arg.patched_code_page {
108        Some(code_page) => {
109            return types::Encoding::CodePage(*code_page);
110        }
111        None => {}
112    }
113    builder.default_patched_encoding()
114}
115
116fn get_patched_archive_encoding(
117    arg: &args::ImportArgs,
118    builder: &Box<dyn scripts::ScriptBuilder + Send + Sync>,
119    encoding: types::Encoding,
120) -> types::Encoding {
121    match &arg.patched_archive_encoding {
122        Some(enc) => {
123            return match enc {
124                &types::TextEncoding::Default => {
125                    builder.default_archive_encoding().unwrap_or(encoding)
126                }
127                &types::TextEncoding::Auto => types::Encoding::Utf8,
128                &types::TextEncoding::Cp932 => types::Encoding::Cp932,
129                &types::TextEncoding::Utf8 => types::Encoding::Utf8,
130                &types::TextEncoding::Gb2312 => types::Encoding::Gb2312,
131            };
132        }
133        None => {}
134    }
135    #[cfg(windows)]
136    match &arg.patched_archive_code_page {
137        Some(code_page) => {
138            return types::Encoding::CodePage(*code_page);
139        }
140        None => {}
141    }
142    builder.default_archive_encoding().unwrap_or(encoding)
143}
144
145pub fn parse_script(
146    filename: &str,
147    arg: &args::Arg,
148    config: &types::ExtraConfig,
149) -> anyhow::Result<(
150    Box<dyn scripts::Script>,
151    &'static Box<dyn scripts::ScriptBuilder + Send + Sync>,
152)> {
153    match &arg.script_type {
154        Some(typ) => {
155            for builder in scripts::BUILDER.iter() {
156                if typ == builder.script_type() {
157                    let encoding = get_encoding(arg, builder);
158                    let archive_encoding = get_archived_encoding(arg, builder, encoding);
159                    return Ok((
160                        builder.build_script_from_file(
161                            filename,
162                            encoding,
163                            archive_encoding,
164                            config,
165                            None,
166                        )?,
167                        builder,
168                    ));
169                }
170            }
171        }
172        _ => {}
173    }
174    let mut exts_builder = Vec::new();
175    for builder in scripts::BUILDER.iter() {
176        let exts = builder.extensions();
177        for ext in exts {
178            if filename.to_lowercase().ends_with(ext) {
179                exts_builder.push(builder);
180                break;
181            }
182        }
183    }
184    let exts_builder = if exts_builder.is_empty() {
185        scripts::BUILDER.iter().collect::<Vec<_>>()
186    } else {
187        exts_builder
188    };
189    if exts_builder.len() == 1 {
190        let builder = exts_builder.first().unwrap();
191        let encoding = get_encoding(arg, builder);
192        let archive_encoding = get_archived_encoding(arg, builder, encoding);
193        return Ok((
194            builder.build_script_from_file(filename, encoding, archive_encoding, config, None)?,
195            builder,
196        ));
197    }
198    let mut buf = [0u8; 1024];
199    let mut size = 0;
200    if filename != "-" {
201        let mut f = std::fs::File::open(filename)?;
202        size = std::io::Read::read(&mut f, &mut buf)?;
203    }
204    let mut scores = Vec::new();
205    for builder in exts_builder.iter() {
206        if let Some(score) = builder.is_this_format(filename, &buf, size) {
207            scores.push((score, builder));
208        }
209    }
210    if scores.is_empty() {
211        return Err(anyhow::anyhow!("Unsupported script type"));
212    }
213    let max_score = scores.iter().map(|s| s.0).max().unwrap();
214    let mut best_builders = Vec::new();
215    for (score, builder) in scores.iter() {
216        if *score == max_score {
217            best_builders.push(builder);
218        }
219    }
220    if best_builders.len() == 1 {
221        let builder = best_builders.first().unwrap();
222        let encoding = get_encoding(arg, builder);
223        let archive_encoding = get_archived_encoding(arg, builder, encoding);
224        return Ok((
225            builder.build_script_from_file(filename, encoding, archive_encoding, config, None)?,
226            builder,
227        ));
228    }
229    if best_builders.len() > 1 {
230        eprintln!(
231            "Multiple script types found for {}: {:?}",
232            filename, best_builders
233        );
234        return Err(anyhow::anyhow!("Multiple script types found"));
235    }
236    Err(anyhow::anyhow!("Unsupported script type"))
237}
238
239pub fn parse_script_from_archive<'a>(
240    file: &mut Box<dyn ArchiveContent + 'a>,
241    arg: &args::Arg,
242    config: &types::ExtraConfig,
243    archive: &Box<dyn scripts::Script>,
244) -> anyhow::Result<(
245    Box<dyn scripts::Script>,
246    &'static Box<dyn scripts::ScriptBuilder + Send + Sync>,
247)> {
248    match file.script_type() {
249        Some(typ) => {
250            for builder in scripts::BUILDER.iter() {
251                if typ == builder.script_type() {
252                    let encoding = get_encoding(arg, builder);
253                    let archive_encoding = get_archived_encoding(arg, builder, encoding);
254                    return Ok((
255                        builder.build_script(
256                            file.data()?,
257                            file.name(),
258                            encoding,
259                            archive_encoding,
260                            config,
261                            Some(archive),
262                        )?,
263                        builder,
264                    ));
265                }
266            }
267        }
268        _ => {}
269    }
270    let mut exts_builder = Vec::new();
271    for builder in scripts::BUILDER.iter() {
272        let exts = builder.extensions();
273        for ext in exts {
274            if file.name().to_lowercase().ends_with(ext) {
275                exts_builder.push(builder);
276                break;
277            }
278        }
279    }
280    let exts_builder = if exts_builder.is_empty() {
281        scripts::BUILDER.iter().collect::<Vec<_>>()
282    } else {
283        exts_builder
284    };
285    if exts_builder.len() == 1 {
286        let builder = exts_builder.first().unwrap();
287        let encoding = get_encoding(arg, builder);
288        let archive_encoding = get_archived_encoding(arg, builder, encoding);
289        return Ok((
290            builder.build_script(
291                file.data()?,
292                file.name(),
293                encoding,
294                archive_encoding,
295                config,
296                Some(archive),
297            )?,
298            builder,
299        ));
300    }
301    let buf = file.data()?;
302    let mut scores = Vec::new();
303    for builder in exts_builder.iter() {
304        if let Some(score) = builder.is_this_format(file.name(), buf.as_slice(), buf.len()) {
305            scores.push((score, builder));
306        }
307    }
308    if scores.is_empty() {
309        return Err(anyhow::anyhow!("Unsupported script type"));
310    }
311    let max_score = scores.iter().map(|s| s.0).max().unwrap();
312    let mut best_builders = Vec::new();
313    for (score, builder) in scores.iter() {
314        if *score == max_score {
315            best_builders.push(builder);
316        }
317    }
318    if best_builders.len() == 1 {
319        let builder = best_builders.first().unwrap();
320        let encoding = get_encoding(arg, builder);
321        let archive_encoding = get_archived_encoding(arg, builder, encoding);
322        return Ok((
323            builder.build_script(
324                buf,
325                file.name(),
326                encoding,
327                archive_encoding,
328                config,
329                Some(archive),
330            )?,
331            builder,
332        ));
333    }
334    if best_builders.len() > 1 {
335        eprintln!(
336            "Multiple script types found for {}: {:?}",
337            file.name(),
338            best_builders
339        );
340        return Err(anyhow::anyhow!("Multiple script types found"));
341    }
342    Err(anyhow::anyhow!("Unsupported script type"))
343}
344
345pub fn export_script(
346    filename: &str,
347    arg: &args::Arg,
348    config: &types::ExtraConfig,
349    output: &Option<String>,
350    root_dir: Option<&std::path::Path>,
351) -> anyhow::Result<types::ScriptResult> {
352    eprintln!("Exporting {}", filename);
353    let script = parse_script(filename, arg, config)?.0;
354    if script.is_archive() {
355        let odir = match output.as_ref() {
356            Some(output) => {
357                let mut pb = std::path::PathBuf::from(output);
358                let filename = std::path::PathBuf::from(filename);
359                if let Some(root_dir) = root_dir {
360                    let rpath = utils::files::relative_path(root_dir, &filename);
361                    if let Some(parent) = rpath.parent() {
362                        pb.push(parent);
363                    }
364                    if let Some(fname) = filename.file_name() {
365                        pb.push(fname);
366                    }
367                }
368                pb.set_extension("");
369                if let Some(ext) = script.archive_output_ext() {
370                    pb.set_extension(ext);
371                }
372                pb.to_string_lossy().into_owned()
373            }
374            None => {
375                let mut pb = std::path::PathBuf::from(filename);
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        };
383        if !std::fs::exists(&odir)? {
384            std::fs::create_dir_all(&odir)?;
385        }
386        for (i, filename) in script.iter_archive_filename()?.enumerate() {
387            let filename = match filename {
388                Ok(f) => f,
389                Err(e) => {
390                    eprintln!("Error reading archive filename: {}", e);
391                    COUNTER.inc_error();
392                    if arg.backtrace {
393                        eprintln!("Backtrace: {}", e.backtrace());
394                    }
395                    continue;
396                }
397            };
398            let mut f = match script.open_file(i) {
399                Ok(f) => f,
400                Err(e) => {
401                    eprintln!("Error opening file {}: {}", filename, e);
402                    COUNTER.inc_error();
403                    if arg.backtrace {
404                        eprintln!("Backtrace: {}", e.backtrace());
405                    }
406                    continue;
407                }
408            };
409            if arg.force_script || f.is_script() {
410                let (script_file, _) = match parse_script_from_archive(&mut f, arg, config, &script)
411                {
412                    Ok(s) => s,
413                    Err(e) => {
414                        eprintln!("Error parsing script '{}' from archive: {}", filename, e);
415                        COUNTER.inc_error();
416                        if arg.backtrace {
417                            eprintln!("Backtrace: {}", e.backtrace());
418                        }
419                        continue;
420                    }
421                };
422                #[cfg(feature = "image")]
423                if script_file.is_image() {
424                    if script_file.is_multi_image() {
425                        for i in script_file.export_multi_image()? {
426                            let img_data = match i {
427                                Ok(data) => data,
428                                Err(e) => {
429                                    eprintln!("Error exporting image: {}", e);
430                                    COUNTER.inc_error();
431                                    if arg.backtrace {
432                                        eprintln!("Backtrace: {}", e.backtrace());
433                                    }
434                                    continue;
435                                }
436                            };
437                            let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
438                            let mut out_path = std::path::PathBuf::from(&odir);
439                            if !arg.image_output_flat {
440                                out_path.push(f.name());
441                                out_path.set_extension("");
442                                out_path.push(img_data.name);
443                            } else {
444                                let name = std::path::Path::new(f.name());
445                                out_path.push(format!(
446                                    "{}_{}",
447                                    name.file_stem().unwrap_or_default().to_string_lossy(),
448                                    img_data.name
449                                ));
450                            }
451                            out_path.set_extension(out_type.as_ref());
452                            match utils::files::make_sure_dir_exists(&out_path) {
453                                Ok(_) => {}
454                                Err(e) => {
455                                    eprintln!(
456                                        "Error creating parent directory for {}: {}",
457                                        out_path.display(),
458                                        e
459                                    );
460                                    COUNTER.inc_error();
461                                    continue;
462                                }
463                            }
464                            utils::img::encode_img(
465                                img_data.data,
466                                out_type,
467                                &out_path.to_string_lossy(),
468                                config,
469                            )?;
470                            COUNTER.inc(types::ScriptResult::Ok);
471                        }
472                        COUNTER.inc(types::ScriptResult::Ok);
473                        continue;
474                    }
475                    let img_data = match script_file.export_image() {
476                        Ok(data) => data,
477                        Err(e) => {
478                            eprintln!("Error exporting image: {}", e);
479                            COUNTER.inc_error();
480                            if arg.backtrace {
481                                eprintln!("Backtrace: {}", e.backtrace());
482                            }
483                            continue;
484                        }
485                    };
486                    let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
487                    let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
488                    out_path.set_extension(out_type.as_ref());
489                    match utils::files::make_sure_dir_exists(&out_path) {
490                        Ok(_) => {}
491                        Err(e) => {
492                            eprintln!(
493                                "Error creating parent directory for {}: {}",
494                                out_path.display(),
495                                e
496                            );
497                            COUNTER.inc_error();
498                            continue;
499                        }
500                    }
501                    match utils::img::encode_img(
502                        img_data,
503                        out_type,
504                        &out_path.to_string_lossy(),
505                        config,
506                    ) {
507                        Ok(_) => {}
508                        Err(e) => {
509                            eprintln!("Error encoding image: {}", e);
510                            COUNTER.inc_error();
511                            continue;
512                        }
513                    }
514                    COUNTER.inc(types::ScriptResult::Ok);
515                    continue;
516                }
517                let mut of = match &arg.output_type {
518                    Some(t) => t.clone(),
519                    None => script_file.default_output_script_type(),
520                };
521                if !script_file.is_output_supported(of) {
522                    of = script_file.default_output_script_type();
523                }
524                let mes = if of.is_custom() {
525                    Vec::new()
526                } else {
527                    match script_file.extract_messages() {
528                        Ok(mes) => mes,
529                        Err(e) => {
530                            eprintln!("Error extracting messages from {}: {}", f.name(), e);
531                            COUNTER.inc_error();
532                            if arg.backtrace {
533                                eprintln!("Backtrace: {}", e.backtrace());
534                            }
535                            continue;
536                        }
537                    }
538                };
539                if !of.is_custom() && mes.is_empty() {
540                    eprintln!("No messages found in {}", f.name());
541                    COUNTER.inc(types::ScriptResult::Ignored);
542                    continue;
543                }
544                let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
545                out_path.set_extension(if of.is_custom() {
546                    script_file.custom_output_extension()
547                } else {
548                    of.as_ref()
549                });
550                match utils::files::make_sure_dir_exists(&out_path) {
551                    Ok(_) => {}
552                    Err(e) => {
553                        eprintln!(
554                            "Error creating parent directory for {}: {}",
555                            out_path.display(),
556                            e
557                        );
558                        COUNTER.inc_error();
559                        continue;
560                    }
561                }
562                match of {
563                    types::OutputScriptType::Json => {
564                        let enc = get_output_encoding(arg);
565                        let s = match serde_json::to_string_pretty(&mes) {
566                            Ok(s) => s,
567                            Err(e) => {
568                                eprintln!("Error serializing messages to JSON: {}", e);
569                                COUNTER.inc_error();
570                                continue;
571                            }
572                        };
573                        let b = match utils::encoding::encode_string(enc, &s, false) {
574                            Ok(b) => b,
575                            Err(e) => {
576                                eprintln!("Error encoding string: {}", e);
577                                COUNTER.inc_error();
578                                continue;
579                            }
580                        };
581                        let mut f = match utils::files::write_file(&out_path) {
582                            Ok(f) => f,
583                            Err(e) => {
584                                eprintln!("Error writing file {}: {}", out_path.display(), e);
585                                COUNTER.inc_error();
586                                continue;
587                            }
588                        };
589                        match f.write_all(&b) {
590                            Ok(_) => {}
591                            Err(e) => {
592                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
593                                COUNTER.inc_error();
594                                continue;
595                            }
596                        }
597                    }
598                    types::OutputScriptType::M3t => {
599                        let enc = get_output_encoding(arg);
600                        let s = output_scripts::m3t::M3tDumper::dump(&mes);
601                        let b = match utils::encoding::encode_string(enc, &s, false) {
602                            Ok(b) => b,
603                            Err(e) => {
604                                eprintln!("Error encoding string: {}", e);
605                                COUNTER.inc_error();
606                                continue;
607                            }
608                        };
609                        let mut f = match utils::files::write_file(&out_path) {
610                            Ok(f) => f,
611                            Err(e) => {
612                                eprintln!("Error writing file {}: {}", out_path.display(), e);
613                                COUNTER.inc_error();
614                                continue;
615                            }
616                        };
617                        match f.write_all(&b) {
618                            Ok(_) => {}
619                            Err(e) => {
620                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
621                                COUNTER.inc_error();
622                                continue;
623                            }
624                        }
625                    }
626                    types::OutputScriptType::Yaml => {
627                        let enc = get_output_encoding(arg);
628                        let s = match serde_yaml_ng::to_string(&mes) {
629                            Ok(s) => s,
630                            Err(e) => {
631                                eprintln!("Error serializing messages to YAML: {}", e);
632                                COUNTER.inc_error();
633                                continue;
634                            }
635                        };
636                        let b = match utils::encoding::encode_string(enc, &s, false) {
637                            Ok(b) => b,
638                            Err(e) => {
639                                eprintln!("Error encoding string: {}", e);
640                                COUNTER.inc_error();
641                                continue;
642                            }
643                        };
644                        let mut f = match utils::files::write_file(&out_path) {
645                            Ok(f) => f,
646                            Err(e) => {
647                                eprintln!("Error writing file {}: {}", out_path.display(), e);
648                                COUNTER.inc_error();
649                                continue;
650                            }
651                        };
652                        match f.write_all(&b) {
653                            Ok(_) => {}
654                            Err(e) => {
655                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
656                                COUNTER.inc_error();
657                                continue;
658                            }
659                        }
660                    }
661                    types::OutputScriptType::Custom => {
662                        let enc = get_output_encoding(arg);
663                        if let Err(e) = script_file.custom_export(&out_path, enc) {
664                            eprintln!("Error exporting custom script: {}", e);
665                            COUNTER.inc_error();
666                            continue;
667                        }
668                    }
669                }
670            } else {
671                let out_path = std::path::PathBuf::from(&odir).join(f.name());
672                match utils::files::make_sure_dir_exists(&out_path) {
673                    Ok(_) => {}
674                    Err(e) => {
675                        eprintln!(
676                            "Error creating parent directory for {}: {}",
677                            out_path.display(),
678                            e
679                        );
680                        COUNTER.inc_error();
681                        continue;
682                    }
683                }
684                match utils::files::write_file(&out_path) {
685                    Ok(mut fi) => match std::io::copy(&mut f, &mut fi) {
686                        Ok(_) => {}
687                        Err(e) => {
688                            eprintln!("Error writing to file {}: {}", out_path.display(), e);
689                            COUNTER.inc_error();
690                            continue;
691                        }
692                    },
693                    Err(e) => {
694                        eprintln!("Error writing file {}: {}", out_path.display(), e);
695                        COUNTER.inc_error();
696                        continue;
697                    }
698                }
699            }
700            COUNTER.inc(types::ScriptResult::Ok);
701        }
702        return Ok(types::ScriptResult::Ok);
703    }
704    #[cfg(feature = "image")]
705    if script.is_image() {
706        if script.is_multi_image() {
707            for i in script.export_multi_image()? {
708                let img_data = match i {
709                    Ok(data) => data,
710                    Err(e) => {
711                        eprintln!("Error exporting image: {}", e);
712                        COUNTER.inc_error();
713                        if arg.backtrace {
714                            eprintln!("Backtrace: {}", e.backtrace());
715                        }
716                        continue;
717                    }
718                };
719                let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
720                let f = match output.as_ref() {
721                    Some(output) => {
722                        if let Some(root_dir) = root_dir {
723                            let f = std::path::PathBuf::from(filename);
724                            let mut pb = std::path::PathBuf::from(output);
725                            let rpath = utils::files::relative_path(root_dir, &f);
726                            if let Some(parent) = rpath.parent() {
727                                pb.push(parent);
728                            }
729                            if !arg.image_output_flat {
730                                if let Some(fname) = f.file_name() {
731                                    pb.push(fname);
732                                    pb.set_extension("");
733                                }
734                                pb.push(img_data.name);
735                            } else {
736                                pb.push(format!(
737                                    "{}_{}",
738                                    f.file_stem().unwrap_or_default().to_string_lossy(),
739                                    img_data.name
740                                ));
741                            }
742                            pb.set_extension(out_type.as_ref());
743                            pb.to_string_lossy().into_owned()
744                        } else {
745                            let mut pb = std::path::PathBuf::from(output);
746                            if arg.image_output_flat {
747                                let f = std::path::PathBuf::from(filename);
748                                pb.push(format!(
749                                    "{}_{}",
750                                    f.file_stem().unwrap_or_default().to_string_lossy(),
751                                    img_data.name
752                                ));
753                            } else {
754                                pb.push(img_data.name);
755                            }
756                            pb.set_extension(out_type.as_ref());
757                            pb.to_string_lossy().into_owned()
758                        }
759                    }
760                    None => {
761                        let mut pb = std::path::PathBuf::from(filename);
762                        if arg.image_output_flat {
763                            let f = std::path::PathBuf::from(filename);
764                            pb.set_file_name(format!(
765                                "{}_{}",
766                                f.file_stem().unwrap_or_default().to_string_lossy(),
767                                img_data.name
768                            ));
769                        } else {
770                            pb.set_extension("");
771                            pb.push(img_data.name);
772                        }
773                        pb.set_extension(out_type.as_ref());
774                        pb.to_string_lossy().into_owned()
775                    }
776                };
777                match utils::files::make_sure_dir_exists(&f) {
778                    Ok(_) => {}
779                    Err(e) => {
780                        eprintln!("Error creating parent directory for {}: {}", f, e);
781                        COUNTER.inc_error();
782                        continue;
783                    }
784                }
785                utils::img::encode_img(img_data.data, out_type, &f, config)?;
786                COUNTER.inc(types::ScriptResult::Ok);
787            }
788            return Ok(types::ScriptResult::Ok);
789        }
790        let img_data = script.export_image()?;
791        let out_type = arg.image_type.unwrap_or_else(|| {
792            if root_dir.is_some() {
793                types::ImageOutputType::Png
794            } else {
795                output
796                    .as_ref()
797                    .and_then(|s| types::ImageOutputType::try_from(std::path::Path::new(s)).ok())
798                    .unwrap_or(types::ImageOutputType::Png)
799            }
800        });
801        let f = if filename == "-" {
802            String::from("-")
803        } else {
804            match output.as_ref() {
805                Some(output) => {
806                    if let Some(root_dir) = root_dir {
807                        let f = std::path::PathBuf::from(filename);
808                        let mut pb = std::path::PathBuf::from(output);
809                        let rpath = utils::files::relative_path(root_dir, &f);
810                        if let Some(parent) = rpath.parent() {
811                            pb.push(parent);
812                        }
813                        if let Some(fname) = f.file_name() {
814                            pb.push(fname);
815                        }
816                        pb.set_extension(out_type.as_ref());
817                        pb.to_string_lossy().into_owned()
818                    } else {
819                        output.clone()
820                    }
821                }
822                None => {
823                    let mut pb = std::path::PathBuf::from(filename);
824                    pb.set_extension(out_type.as_ref());
825                    pb.to_string_lossy().into_owned()
826                }
827            }
828        };
829        utils::files::make_sure_dir_exists(&f)?;
830        utils::img::encode_img(img_data, out_type, &f, config)?;
831        return Ok(types::ScriptResult::Ok);
832    }
833    let mut of = match &arg.output_type {
834        Some(t) => t.clone(),
835        None => script.default_output_script_type(),
836    };
837    if !script.is_output_supported(of) {
838        of = script.default_output_script_type();
839    }
840    let mes = if of.is_custom() {
841        Vec::new()
842    } else {
843        script.extract_messages()?
844    };
845    if !of.is_custom() && mes.is_empty() {
846        eprintln!("No messages found");
847        return Ok(types::ScriptResult::Ignored);
848    }
849    let ext = if of.is_custom() {
850        script.custom_output_extension()
851    } else {
852        of.as_ref()
853    };
854    let f = if filename == "-" {
855        String::from("-")
856    } else {
857        match output.as_ref() {
858            Some(output) => {
859                if let Some(root_dir) = root_dir {
860                    let f = std::path::PathBuf::from(filename);
861                    let mut pb = std::path::PathBuf::from(output);
862                    let rpath = utils::files::relative_path(root_dir, &f);
863                    if let Some(parent) = rpath.parent() {
864                        pb.push(parent);
865                    }
866                    if let Some(fname) = f.file_name() {
867                        pb.push(fname);
868                    }
869                    pb.set_extension(ext);
870                    pb.to_string_lossy().into_owned()
871                } else {
872                    output.clone()
873                }
874            }
875            None => {
876                let mut pb = std::path::PathBuf::from(filename);
877                pb.set_extension(ext);
878                pb.to_string_lossy().into_owned()
879            }
880        }
881    };
882    utils::files::make_sure_dir_exists(&f)?;
883    match of {
884        types::OutputScriptType::Json => {
885            let enc = get_output_encoding(arg);
886            let s = serde_json::to_string_pretty(&mes)?;
887            let b = utils::encoding::encode_string(enc, &s, false)?;
888            let mut f = utils::files::write_file(&f)?;
889            f.write_all(&b)?;
890        }
891        types::OutputScriptType::M3t => {
892            let enc = get_output_encoding(arg);
893            let s = output_scripts::m3t::M3tDumper::dump(&mes);
894            let b = utils::encoding::encode_string(enc, &s, false)?;
895            let mut f = utils::files::write_file(&f)?;
896            f.write_all(&b)?;
897        }
898        types::OutputScriptType::Yaml => {
899            let enc = get_output_encoding(arg);
900            let s = serde_yaml_ng::to_string(&mes)?;
901            let b = utils::encoding::encode_string(enc, &s, false)?;
902            let mut f = utils::files::write_file(&f)?;
903            f.write_all(&b)?;
904        }
905        types::OutputScriptType::Custom => {
906            let enc = get_output_encoding(arg);
907            script.custom_export(f.as_ref(), enc)?;
908        }
909    }
910    Ok(types::ScriptResult::Ok)
911}
912
913pub fn import_script(
914    filename: &str,
915    arg: &args::Arg,
916    config: &types::ExtraConfig,
917    imp_cfg: &args::ImportArgs,
918    root_dir: Option<&std::path::Path>,
919    name_csv: Option<&std::collections::HashMap<String, String>>,
920    repl: Option<&types::ReplacementTable>,
921) -> anyhow::Result<types::ScriptResult> {
922    eprintln!("Importing {}", filename);
923    let (script, builder) = parse_script(filename, arg, config)?;
924    if script.is_archive() {
925        let odir = {
926            let mut pb = std::path::PathBuf::from(&imp_cfg.output);
927            let filename = std::path::PathBuf::from(filename);
928            if let Some(root_dir) = root_dir {
929                let rpath = utils::files::relative_path(root_dir, &filename);
930                if let Some(parent) = rpath.parent() {
931                    pb.push(parent);
932                }
933                if let Some(fname) = filename.file_name() {
934                    pb.push(fname);
935                }
936            }
937            pb.set_extension("");
938            if let Some(ext) = script.archive_output_ext() {
939                pb.set_extension(ext);
940            }
941            pb.to_string_lossy().into_owned()
942        };
943        let files: Vec<_> = script.iter_archive_filename()?.collect();
944        let files = files.into_iter().filter_map(|f| f.ok()).collect::<Vec<_>>();
945        let patched_f = if let Some(root_dir) = root_dir {
946            let f = std::path::PathBuf::from(filename);
947            let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
948            let rpath = utils::files::relative_path(root_dir, &f);
949            if let Some(parent) = rpath.parent() {
950                pb.push(parent);
951            }
952            if let Some(fname) = f.file_name() {
953                pb.push(fname);
954            }
955            pb.set_extension(builder.extensions().first().unwrap_or(&""));
956            pb.to_string_lossy().into_owned()
957        } else {
958            imp_cfg.patched.clone()
959        };
960        let files: Vec<_> = files.iter().map(|s| s.as_str()).collect();
961        let pencoding = get_patched_encoding(imp_cfg, builder);
962        let enc = get_patched_archive_encoding(imp_cfg, builder, pencoding);
963        utils::files::make_sure_dir_exists(&patched_f)?;
964        let mut arch = builder.create_archive(&patched_f, &files, enc, config)?;
965        for (index, filename) in script.iter_archive_filename()?.enumerate() {
966            let filename = match filename {
967                Ok(f) => f,
968                Err(e) => {
969                    eprintln!("Error reading archive filename: {}", e);
970                    COUNTER.inc_error();
971                    if arg.backtrace {
972                        eprintln!("Backtrace: {}", e.backtrace());
973                    }
974                    continue;
975                }
976            };
977            let mut f = match script.open_file(index) {
978                Ok(f) => f,
979                Err(e) => {
980                    eprintln!("Error opening file {}: {}", filename, e);
981                    COUNTER.inc_error();
982                    if arg.backtrace {
983                        eprintln!("Backtrace: {}", e.backtrace());
984                    }
985                    continue;
986                }
987            };
988            let mut writer = arch.new_file(f.name())?;
989            if arg.force_script || f.is_script() {
990                let (script_file, _) = match parse_script_from_archive(&mut f, arg, config, &script)
991                {
992                    Ok(s) => s,
993                    Err(e) => {
994                        eprintln!("Error parsing script '{}' from archive: {}", filename, e);
995                        COUNTER.inc_error();
996                        if arg.backtrace {
997                            eprintln!("Backtrace: {}", e.backtrace());
998                        }
999                        continue;
1000                    }
1001                };
1002                let mut of = match &arg.output_type {
1003                    Some(t) => t.clone(),
1004                    None => script_file.default_output_script_type(),
1005                };
1006                if !script_file.is_output_supported(of) {
1007                    of = script_file.default_output_script_type();
1008                }
1009                let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
1010                let ext = if of.is_custom() {
1011                    script_file.custom_output_extension()
1012                } else {
1013                    of.as_ref()
1014                };
1015                out_path.set_extension(ext);
1016                if !out_path.exists() {
1017                    out_path = std::path::PathBuf::from(&odir).join(f.name());
1018                    if !out_path.exists() {
1019                        if imp_cfg.warn_when_output_file_not_found {
1020                            eprintln!(
1021                                "Warning: File {} does not exist, using file from original archive.",
1022                                out_path.display()
1023                            );
1024                            COUNTER.inc_warning();
1025                        }
1026                        match std::io::copy(&mut f, &mut writer) {
1027                            Ok(_) => {}
1028                            Err(e) => {
1029                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
1030                                COUNTER.inc_error();
1031                                continue;
1032                            }
1033                        }
1034                        COUNTER.inc(types::ScriptResult::Ok);
1035                        continue;
1036                    } else {
1037                        let file = match std::fs::File::open(&out_path) {
1038                            Ok(f) => f,
1039                            Err(e) => {
1040                                eprintln!("Error opening file {}: {}", out_path.display(), e);
1041                                COUNTER.inc_error();
1042                                continue;
1043                            }
1044                        };
1045                        let mut f = std::io::BufReader::new(file);
1046                        match std::io::copy(&mut f, &mut writer) {
1047                            Ok(_) => {}
1048                            Err(e) => {
1049                                eprintln!("Error writing to file {}: {}", out_path.display(), e);
1050                                COUNTER.inc_error();
1051                                continue;
1052                            }
1053                        }
1054                        COUNTER.inc(types::ScriptResult::Ok);
1055                        continue;
1056                    }
1057                }
1058                let mut mes = match of {
1059                    types::OutputScriptType::Json => {
1060                        let enc = get_output_encoding(arg);
1061                        let b = match utils::files::read_file(&out_path) {
1062                            Ok(b) => b,
1063                            Err(e) => {
1064                                eprintln!("Error reading file {}: {}", out_path.display(), e);
1065                                COUNTER.inc_error();
1066                                continue;
1067                            }
1068                        };
1069                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
1070                            Ok(s) => s,
1071                            Err(e) => {
1072                                eprintln!("Error decoding string: {}", e);
1073                                COUNTER.inc_error();
1074                                continue;
1075                            }
1076                        };
1077                        match serde_json::from_str::<Vec<types::Message>>(&s) {
1078                            Ok(mes) => mes,
1079                            Err(e) => {
1080                                eprintln!("Error parsing JSON: {}", e);
1081                                COUNTER.inc_error();
1082                                continue;
1083                            }
1084                        }
1085                    }
1086                    types::OutputScriptType::M3t => {
1087                        let enc = get_output_encoding(arg);
1088                        let b = match utils::files::read_file(&out_path) {
1089                            Ok(b) => b,
1090                            Err(e) => {
1091                                eprintln!("Error reading file {}: {}", out_path.display(), e);
1092                                COUNTER.inc_error();
1093                                continue;
1094                            }
1095                        };
1096                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
1097                            Ok(s) => s,
1098                            Err(e) => {
1099                                eprintln!("Error decoding string: {}", e);
1100                                COUNTER.inc_error();
1101                                continue;
1102                            }
1103                        };
1104                        let mut parser = output_scripts::m3t::M3tParser::new(&s);
1105                        match parser.parse() {
1106                            Ok(mes) => mes,
1107                            Err(e) => {
1108                                eprintln!("Error parsing M3T: {}", e);
1109                                COUNTER.inc_error();
1110                                continue;
1111                            }
1112                        }
1113                    }
1114                    types::OutputScriptType::Yaml => {
1115                        let enc = get_output_encoding(arg);
1116                        let b = match utils::files::read_file(&out_path) {
1117                            Ok(b) => b,
1118                            Err(e) => {
1119                                eprintln!("Error reading file {}: {}", out_path.display(), e);
1120                                COUNTER.inc_error();
1121                                continue;
1122                            }
1123                        };
1124                        let s = match utils::encoding::decode_to_string(enc, &b, true) {
1125                            Ok(s) => s,
1126                            Err(e) => {
1127                                eprintln!("Error decoding string: {}", e);
1128                                COUNTER.inc_error();
1129                                continue;
1130                            }
1131                        };
1132                        match serde_yaml_ng::from_str::<Vec<types::Message>>(&s) {
1133                            Ok(mes) => mes,
1134                            Err(e) => {
1135                                eprintln!("Error parsing YAML: {}", e);
1136                                COUNTER.inc_error();
1137                                continue;
1138                            }
1139                        }
1140                    }
1141                    types::OutputScriptType::Custom => {
1142                        Vec::new() // Custom scripts handle their own messages
1143                    }
1144                };
1145                if !of.is_custom() && mes.is_empty() {
1146                    eprintln!("No messages found in {}", f.name());
1147                    COUNTER.inc(types::ScriptResult::Ignored);
1148                    continue;
1149                }
1150                let encoding = get_patched_encoding(imp_cfg, builder);
1151                if of.is_custom() {
1152                    let enc = get_output_encoding(arg);
1153                    match script_file.custom_import(
1154                        &out_path.to_string_lossy(),
1155                        writer,
1156                        encoding,
1157                        enc,
1158                    ) {
1159                        Ok(_) => {}
1160                        Err(e) => {
1161                            eprintln!("Error importing custom script: {}", e);
1162                            COUNTER.inc_error();
1163                            continue;
1164                        }
1165                    }
1166                    COUNTER.inc(types::ScriptResult::Ok);
1167                    continue;
1168                }
1169                let fmt = match imp_cfg.patched_format {
1170                    Some(fmt) => match fmt {
1171                        types::FormatType::Fixed => types::FormatOptions::Fixed {
1172                            length: imp_cfg.patched_fixed_length.unwrap_or(32),
1173                            keep_original: imp_cfg.patched_keep_original,
1174                        },
1175                        types::FormatType::None => types::FormatOptions::None,
1176                    },
1177                    None => script_file.default_format_type(),
1178                };
1179                match name_csv {
1180                    Some(name_table) => {
1181                        utils::name_replacement::replace_message(&mut mes, name_table);
1182                    }
1183                    None => {}
1184                }
1185                format::fmt_message(&mut mes, fmt, *builder.script_type());
1186                if let Err(e) = script_file.import_messages(
1187                    mes,
1188                    writer,
1189                    &out_path.to_string_lossy(),
1190                    encoding,
1191                    repl,
1192                ) {
1193                    eprintln!("Error importing messages: {}", e);
1194                    COUNTER.inc_error();
1195                    continue;
1196                }
1197            } else {
1198                let out_path = std::path::PathBuf::from(&odir).join(f.name());
1199                if out_path.is_file() {
1200                    let f = match std::fs::File::open(&out_path) {
1201                        Ok(f) => f,
1202                        Err(e) => {
1203                            eprintln!("Error opening file {}: {}", out_path.display(), e);
1204                            COUNTER.inc_error();
1205                            continue;
1206                        }
1207                    };
1208                    let mut f = std::io::BufReader::new(f);
1209                    match std::io::copy(&mut f, &mut writer) {
1210                        Ok(_) => {}
1211                        Err(e) => {
1212                            eprintln!("Error writing to file {}: {}", out_path.display(), e);
1213                            COUNTER.inc_error();
1214                            continue;
1215                        }
1216                    }
1217                } else {
1218                    eprintln!(
1219                        "Warning: File {} does not exist, use file from original archive.",
1220                        out_path.display()
1221                    );
1222                    COUNTER.inc_warning();
1223                    match std::io::copy(&mut f, &mut writer) {
1224                        Ok(_) => {}
1225                        Err(e) => {
1226                            eprintln!("Error writing to file {}: {}", out_path.display(), e);
1227                            COUNTER.inc_error();
1228                            continue;
1229                        }
1230                    }
1231                }
1232            }
1233            COUNTER.inc(types::ScriptResult::Ok);
1234        }
1235        arch.write_header()?;
1236        return Ok(types::ScriptResult::Ok);
1237    }
1238    #[cfg(feature = "image")]
1239    if script.is_image() {
1240        let out_type = arg.image_type.unwrap_or_else(|| {
1241            if root_dir.is_some() {
1242                types::ImageOutputType::Png
1243            } else {
1244                types::ImageOutputType::try_from(std::path::Path::new(&imp_cfg.output))
1245                    .unwrap_or(types::ImageOutputType::Png)
1246            }
1247        });
1248        let out_f = if let Some(root_dir) = root_dir {
1249            let f = std::path::PathBuf::from(filename);
1250            let mut pb = std::path::PathBuf::from(&imp_cfg.output);
1251            let rpath = utils::files::relative_path(root_dir, &f);
1252            if let Some(parent) = rpath.parent() {
1253                pb.push(parent);
1254            }
1255            if let Some(fname) = f.file_name() {
1256                pb.push(fname);
1257            }
1258            pb.set_extension(out_type.as_ref());
1259            pb.to_string_lossy().into_owned()
1260        } else {
1261            imp_cfg.output.clone()
1262        };
1263        let data = utils::img::decode_img(out_type, &out_f)?;
1264        let patched_f = if let Some(root_dir) = root_dir {
1265            let f = std::path::PathBuf::from(filename);
1266            let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
1267            let rpath = utils::files::relative_path(root_dir, &f);
1268            if let Some(parent) = rpath.parent() {
1269                pb.push(parent);
1270            }
1271            if let Some(fname) = f.file_name() {
1272                pb.push(fname);
1273            }
1274            pb.set_extension(builder.extensions().first().unwrap_or(&""));
1275            pb.to_string_lossy().into_owned()
1276        } else {
1277            imp_cfg.patched.clone()
1278        };
1279        utils::files::make_sure_dir_exists(&patched_f)?;
1280        script.import_image_filename(data, &patched_f)?;
1281        return Ok(types::ScriptResult::Ok);
1282    }
1283    let mut of = match &arg.output_type {
1284        Some(t) => t.clone(),
1285        None => script.default_output_script_type(),
1286    };
1287    if !script.is_output_supported(of) {
1288        of = script.default_output_script_type();
1289    }
1290    let out_f = if let Some(root_dir) = root_dir {
1291        let f = std::path::PathBuf::from(filename);
1292        let mut pb = std::path::PathBuf::from(&imp_cfg.output);
1293        let rpath = utils::files::relative_path(root_dir, &f);
1294        if let Some(parent) = rpath.parent() {
1295            pb.push(parent);
1296        }
1297        if let Some(fname) = f.file_name() {
1298            pb.push(fname);
1299        }
1300        pb.set_extension(of.as_ref());
1301        pb.to_string_lossy().into_owned()
1302    } else {
1303        imp_cfg.output.clone()
1304    };
1305    if !std::fs::exists(&out_f).unwrap_or(false) {
1306        eprintln!("Output file does not exist");
1307        return Ok(types::ScriptResult::Ignored);
1308    }
1309    let mut mes = match of {
1310        types::OutputScriptType::Json => {
1311            let enc = get_output_encoding(arg);
1312            let b = utils::files::read_file(&out_f)?;
1313            let s = utils::encoding::decode_to_string(enc, &b, true)?;
1314            serde_json::from_str::<Vec<types::Message>>(&s)?
1315        }
1316        types::OutputScriptType::M3t => {
1317            let enc = get_output_encoding(arg);
1318            let b = utils::files::read_file(&out_f)?;
1319            let s = utils::encoding::decode_to_string(enc, &b, true)?;
1320            let mut parser = output_scripts::m3t::M3tParser::new(&s);
1321            parser.parse()?
1322        }
1323        types::OutputScriptType::Yaml => {
1324            let enc = get_output_encoding(arg);
1325            let b = utils::files::read_file(&out_f)?;
1326            let s = utils::encoding::decode_to_string(enc, &b, true)?;
1327            serde_yaml_ng::from_str::<Vec<types::Message>>(&s)?
1328        }
1329        types::OutputScriptType::Custom => {
1330            Vec::new() // Custom scripts handle their own messages
1331        }
1332    };
1333    if !of.is_custom() && mes.is_empty() {
1334        eprintln!("No messages found");
1335        return Ok(types::ScriptResult::Ignored);
1336    }
1337    let encoding = get_patched_encoding(imp_cfg, builder);
1338    let patched_f = if let Some(root_dir) = root_dir {
1339        let f = std::path::PathBuf::from(filename);
1340        let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
1341        let rpath = utils::files::relative_path(root_dir, &f);
1342        if let Some(parent) = rpath.parent() {
1343            pb.push(parent);
1344        }
1345        if let Some(fname) = f.file_name() {
1346            pb.push(fname);
1347        }
1348        pb.set_extension(builder.extensions().first().unwrap_or(&""));
1349        pb.to_string_lossy().into_owned()
1350    } else {
1351        imp_cfg.patched.clone()
1352    };
1353    utils::files::make_sure_dir_exists(&patched_f)?;
1354    if of.is_custom() {
1355        let enc = get_output_encoding(arg);
1356        script.custom_import_filename(&out_f, &patched_f, encoding, enc)?;
1357        return Ok(types::ScriptResult::Ok);
1358    }
1359    let fmt = match imp_cfg.patched_format {
1360        Some(fmt) => match fmt {
1361            types::FormatType::Fixed => types::FormatOptions::Fixed {
1362                length: imp_cfg.patched_fixed_length.unwrap_or(32),
1363                keep_original: imp_cfg.patched_keep_original,
1364            },
1365            types::FormatType::None => types::FormatOptions::None,
1366        },
1367        None => script.default_format_type(),
1368    };
1369    match name_csv {
1370        Some(name_table) => {
1371            utils::name_replacement::replace_message(&mut mes, name_table);
1372        }
1373        None => {}
1374    }
1375    format::fmt_message(&mut mes, fmt, *builder.script_type());
1376
1377    script.import_messages_filename(mes, &patched_f, encoding, repl)?;
1378    Ok(types::ScriptResult::Ok)
1379}
1380
1381pub fn pack_archive(
1382    input: &str,
1383    output: Option<&str>,
1384    arg: &args::Arg,
1385    config: &types::ExtraConfig,
1386) -> anyhow::Result<()> {
1387    let typ = match &arg.script_type {
1388        Some(t) => t,
1389        None => {
1390            return Err(anyhow::anyhow!("No script type specified"));
1391        }
1392    };
1393    let (files, isdir) = utils::files::collect_files(input, arg.recursive, true)
1394        .map_err(|e| anyhow::anyhow!("Error collecting files: {}", e))?;
1395    if !isdir {
1396        return Err(anyhow::anyhow!("Input must be a directory for packing"));
1397    }
1398    let re_files: Vec<String> = files
1399        .iter()
1400        .filter_map(|f| {
1401            std::path::PathBuf::from(f)
1402                .strip_prefix(input)
1403                .ok()
1404                .and_then(|p| {
1405                    p.to_str()
1406                        .map(|s| s.replace("\\", "/").trim_start_matches("/").to_owned())
1407                })
1408        })
1409        .collect();
1410    let reff = re_files.iter().map(|s| s.as_str()).collect::<Vec<_>>();
1411    let builder = scripts::BUILDER
1412        .iter()
1413        .find(|b| b.script_type() == typ)
1414        .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
1415    let output = match output {
1416        Some(output) => output.to_string(),
1417        None => {
1418            let mut pb = std::path::PathBuf::from(input);
1419            let ext = builder.extensions().first().unwrap_or(&"unk");
1420            pb.set_extension(ext);
1421            if pb.to_string_lossy() == input {
1422                pb.set_extension(format!("{}.{}", ext, ext));
1423            }
1424            pb.to_string_lossy().into_owned()
1425        }
1426    };
1427    let mut archive = builder.create_archive(
1428        &output,
1429        &reff,
1430        get_archived_encoding(arg, builder, get_encoding(arg, builder)),
1431        config,
1432    )?;
1433    for (file, name) in files.iter().zip(reff) {
1434        let mut f = match std::fs::File::open(file) {
1435            Ok(f) => f,
1436            Err(e) => {
1437                eprintln!("Error opening file {}: {}", file, e);
1438                COUNTER.inc_error();
1439                continue;
1440            }
1441        };
1442        let mut wf = match archive.new_file(name) {
1443            Ok(f) => f,
1444            Err(e) => {
1445                eprintln!("Error creating file {} in archive: {}", name, e);
1446                COUNTER.inc_error();
1447                continue;
1448            }
1449        };
1450        match std::io::copy(&mut f, &mut wf) {
1451            Ok(_) => {
1452                COUNTER.inc(types::ScriptResult::Ok);
1453            }
1454            Err(e) => {
1455                eprintln!("Error writing to file {} in archive: {}", name, e);
1456                COUNTER.inc_error();
1457                continue;
1458            }
1459        }
1460    }
1461    archive.write_header()?;
1462    Ok(())
1463}
1464
1465pub fn unpack_archive(
1466    filename: &str,
1467    arg: &args::Arg,
1468    config: &types::ExtraConfig,
1469    output: &Option<String>,
1470    root_dir: Option<&std::path::Path>,
1471) -> anyhow::Result<types::ScriptResult> {
1472    eprintln!("Unpacking {}", filename);
1473    let script = parse_script(filename, arg, config)?.0;
1474    if !script.is_archive() {
1475        return Ok(types::ScriptResult::Ignored);
1476    }
1477    let odir = match output.as_ref() {
1478        Some(output) => {
1479            let mut pb = std::path::PathBuf::from(output);
1480            let filename = std::path::PathBuf::from(filename);
1481            if let Some(root_dir) = root_dir {
1482                let rpath = utils::files::relative_path(root_dir, &filename);
1483                if let Some(parent) = rpath.parent() {
1484                    pb.push(parent);
1485                }
1486                if let Some(fname) = filename.file_name() {
1487                    pb.push(fname);
1488                }
1489            }
1490            pb.set_extension("");
1491            if let Some(ext) = script.archive_output_ext() {
1492                pb.set_extension(ext);
1493            }
1494            pb.to_string_lossy().into_owned()
1495        }
1496        None => {
1497            let mut pb = std::path::PathBuf::from(filename);
1498            pb.set_extension("");
1499            if let Some(ext) = script.archive_output_ext() {
1500                pb.set_extension(ext);
1501            }
1502            pb.to_string_lossy().into_owned()
1503        }
1504    };
1505    if !std::fs::exists(&odir)? {
1506        std::fs::create_dir_all(&odir)?;
1507    }
1508    for (index, filename) in script.iter_archive_filename()?.enumerate() {
1509        let filename = match filename {
1510            Ok(f) => f,
1511            Err(e) => {
1512                eprintln!("Error reading archive filename: {}", e);
1513                COUNTER.inc_error();
1514                if arg.backtrace {
1515                    eprintln!("Backtrace: {}", e.backtrace());
1516                }
1517                continue;
1518            }
1519        };
1520        let mut f = match script.open_file(index) {
1521            Ok(f) => f,
1522            Err(e) => {
1523                eprintln!("Error opening file {}: {}", filename, e);
1524                COUNTER.inc_error();
1525                if arg.backtrace {
1526                    eprintln!("Backtrace: {}", e.backtrace());
1527                }
1528                continue;
1529            }
1530        };
1531        let out_path = std::path::PathBuf::from(&odir).join(f.name());
1532        match utils::files::make_sure_dir_exists(&out_path) {
1533            Ok(_) => {}
1534            Err(e) => {
1535                eprintln!(
1536                    "Error creating parent directory for {}: {}",
1537                    out_path.display(),
1538                    e
1539                );
1540                COUNTER.inc_error();
1541                continue;
1542            }
1543        }
1544        match utils::files::write_file(&out_path) {
1545            Ok(mut fi) => match std::io::copy(&mut f, &mut fi) {
1546                Ok(_) => {}
1547                Err(e) => {
1548                    eprintln!("Error writing to file {}: {}", out_path.display(), e);
1549                    COUNTER.inc_error();
1550                    continue;
1551                }
1552            },
1553            Err(e) => {
1554                eprintln!("Error writing file {}: {}", out_path.display(), e);
1555                COUNTER.inc_error();
1556                continue;
1557            }
1558        }
1559        COUNTER.inc(types::ScriptResult::Ok);
1560    }
1561    Ok(types::ScriptResult::Ok)
1562}
1563
1564pub fn create_file(
1565    input: &str,
1566    output: Option<&str>,
1567    arg: &args::Arg,
1568    config: &types::ExtraConfig,
1569) -> anyhow::Result<()> {
1570    let typ = match &arg.script_type {
1571        Some(t) => t,
1572        None => {
1573            return Err(anyhow::anyhow!("No script type specified"));
1574        }
1575    };
1576    let builder = scripts::BUILDER
1577        .iter()
1578        .find(|b| b.script_type() == typ)
1579        .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
1580
1581    #[cfg(feature = "image")]
1582    if builder.is_image() {
1583        if !builder.can_create_image_file() {
1584            return Err(anyhow::anyhow!(
1585                "Script type {:?} does not support image file creation",
1586                typ
1587            ));
1588        }
1589        let data = utils::img::decode_img(
1590            arg.image_type.unwrap_or_else(|| {
1591                types::ImageOutputType::try_from(std::path::Path::new(input))
1592                    .unwrap_or(types::ImageOutputType::Png)
1593            }),
1594            input,
1595        )?;
1596        let output = match output {
1597            Some(output) => output.to_string(),
1598            None => {
1599                let mut pb = std::path::PathBuf::from(input);
1600                let ext = builder.extensions().first().unwrap_or(&"");
1601                pb.set_extension(ext);
1602                if pb.to_string_lossy() == input {
1603                    if ext.is_empty() {
1604                        pb.set_extension("unk");
1605                    } else {
1606                        pb.set_extension(format!("{}.{}", ext, ext));
1607                    }
1608                }
1609                pb.to_string_lossy().into_owned()
1610            }
1611        };
1612        builder.create_image_file_filename(data, &output, config)?;
1613        return Ok(());
1614    }
1615
1616    if !builder.can_create_file() {
1617        return Err(anyhow::anyhow!(
1618            "Script type {:?} does not support file creation",
1619            typ
1620        ));
1621    }
1622
1623    let output = match output {
1624        Some(output) => output.to_string(),
1625        None => {
1626            let mut pb = std::path::PathBuf::from(input);
1627            let ext = builder.extensions().first().unwrap_or(&"");
1628            pb.set_extension(ext);
1629            if pb.to_string_lossy() == input {
1630                if ext.is_empty() {
1631                    pb.set_extension("unk");
1632                } else {
1633                    pb.set_extension(format!("{}.{}", ext, ext));
1634                }
1635            }
1636            pb.to_string_lossy().into_owned()
1637        }
1638    };
1639
1640    builder.create_file_filename(
1641        input,
1642        &output,
1643        get_encoding(arg, builder),
1644        get_output_encoding(arg),
1645        config,
1646    )?;
1647    Ok(())
1648}
1649
1650lazy_static::lazy_static! {
1651    static ref COUNTER: utils::counter::Counter = utils::counter::Counter::new();
1652}
1653
1654fn main() {
1655    let _ = ctrlc::try_set_handler(|| {
1656        eprintln!("Aborted.");
1657        eprintln!("{}", std::ops::Deref::deref(&COUNTER));
1658        std::process::exit(1);
1659    });
1660    let arg = args::parse_args();
1661    if arg.backtrace {
1662        unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") };
1663    }
1664    let cfg = types::ExtraConfig {
1665        #[cfg(feature = "circus")]
1666        circus_mes_type: arg.circus_mes_type.clone(),
1667        #[cfg(feature = "escude-arc")]
1668        escude_fake_compress: arg.escude_fake_compress,
1669        #[cfg(feature = "escude")]
1670        escude_enum_scr: arg.escude_enum_scr.clone(),
1671        #[cfg(feature = "bgi")]
1672        bgi_import_duplicate: arg.bgi_import_duplicate,
1673        #[cfg(feature = "bgi")]
1674        bgi_disable_append: arg.bgi_disable_append,
1675        #[cfg(feature = "image")]
1676        image_type: arg.image_type.clone(),
1677        #[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
1678        bgi_is_sysgrp_arc: arg.bgi_is_sysgrp_arc.clone(),
1679        #[cfg(feature = "bgi-img")]
1680        bgi_img_scramble: arg.bgi_img_scramble.clone(),
1681        #[cfg(feature = "cat-system-arc")]
1682        cat_system_int_encrypt_password: args::get_cat_system_int_encrypt_password(&arg)
1683            .expect("Failed to get CatSystem2 int encrypt password"),
1684        #[cfg(feature = "cat-system-img")]
1685        cat_system_image_canvas: arg.cat_system_image_canvas,
1686        #[cfg(feature = "kirikiri")]
1687        kirikiri_language_index: arg.kirikiri_language_index.clone(),
1688        #[cfg(feature = "kirikiri")]
1689        kirikiri_export_comumode: arg.kirikiri_export_comumode,
1690        #[cfg(feature = "kirikiri")]
1691        kirikiri_comumode_json: arg
1692            .kirikiri_comumode_json
1693            .as_ref()
1694            .map(|s| scripts::kirikiri::read_kirikiri_comu_json(s).unwrap()),
1695        #[cfg(feature = "kirikiri")]
1696        kirikiri_remove_empty_lines: arg.kirikiri_remove_empty_lines,
1697        #[cfg(feature = "kirikiri")]
1698        kirikiri_name_commands: std::sync::Arc::new(std::collections::HashSet::from_iter(
1699            arg.kirikiri_name_commands.iter().cloned(),
1700        )),
1701        #[cfg(feature = "kirikiri")]
1702        kirikiri_message_commands: std::sync::Arc::new(std::collections::HashSet::from_iter(
1703            arg.kirikiri_message_commands.iter().cloned(),
1704        )),
1705        #[cfg(feature = "bgi-arc")]
1706        bgi_compress_file: arg.bgi_compress_file,
1707        #[cfg(feature = "bgi-arc")]
1708        bgi_compress_min_len: arg.bgi_compress_min_len,
1709        #[cfg(feature = "emote-img")]
1710        emote_pimg_overlay: arg.emote_pimg_overlay,
1711        #[cfg(feature = "artemis-arc")]
1712        artemis_arc_disable_xor: arg.artemis_arc_disable_xor,
1713        #[cfg(feature = "artemis")]
1714        artemis_indent: arg.artemis_indent,
1715        #[cfg(feature = "artemis")]
1716        artemis_no_indent: arg.artemis_no_indent,
1717        #[cfg(feature = "artemis")]
1718        artemis_max_line_width: arg.artemis_max_line_width,
1719        #[cfg(feature = "artemis")]
1720        artemis_ast_lang: arg.artemis_ast_lang.clone(),
1721        #[cfg(feature = "cat-system")]
1722        cat_system_cstl_lang: arg.cat_system_cstl_lang.clone(),
1723        #[cfg(feature = "flate2")]
1724        zlib_compression_level: arg.zlib_compression_level,
1725        #[cfg(feature = "image")]
1726        png_compression_level: arg.png_compression_level,
1727        #[cfg(feature = "circus-img")]
1728        circus_crx_keep_original_bpp: arg.circus_crx_keep_original_bpp,
1729        #[cfg(feature = "circus-img")]
1730        circus_crx_zstd: arg.circus_crx_zstd,
1731        #[cfg(feature = "zstd")]
1732        zstd_compression_level: arg.zstd_compression_level,
1733        #[cfg(feature = "circus-img")]
1734        circus_crx_mode: arg.circus_crx_mode,
1735        #[cfg(feature = "ex-hibit")]
1736        ex_hibit_rld_xor_key: args::load_ex_hibit_rld_xor_key(&arg)
1737            .expect("Failed to load RLD XOR key"),
1738        #[cfg(feature = "ex-hibit")]
1739        ex_hibit_rld_def_xor_key: args::load_ex_hibit_rld_def_xor_key(&arg)
1740            .expect("Failed to load RLD DEF XOR key"),
1741        #[cfg(feature = "ex-hibit")]
1742        ex_hibit_rld_keys: scripts::ex_hibit::rld::load_keys(arg.ex_hibit_rld_keys.as_ref())
1743            .expect("Failed to load RLD keys"),
1744        #[cfg(feature = "ex-hibit")]
1745        ex_hibit_rld_def_keys: scripts::ex_hibit::rld::load_keys(
1746            arg.ex_hibit_rld_def_keys.as_ref(),
1747        )
1748        .expect("Failed to load RLD DEF keys"),
1749        #[cfg(feature = "mozjpeg")]
1750        jpeg_quality: arg.jpeg_quality,
1751        #[cfg(feature = "webp")]
1752        webp_lossless: arg.webp_lossless,
1753        #[cfg(feature = "webp")]
1754        webp_quality: arg.webp_quality,
1755        #[cfg(feature = "circus-img")]
1756        circus_crx_canvas: arg.circus_crx_canvas,
1757        custom_yaml: arg.custom_yaml.unwrap_or_else(|| {
1758            arg.output_type
1759                .map(|s| s == types::OutputScriptType::Yaml)
1760                .unwrap_or(false)
1761        }),
1762        #[cfg(feature = "entis-gls")]
1763        entis_gls_srcxml_lang: arg.entis_gls_srcxml_lang.clone(),
1764        #[cfg(feature = "will-plus")]
1765        will_plus_ws2_no_disasm: arg.will_plus_ws2_no_disasm,
1766        #[cfg(feature = "artemis")]
1767        artemis_txt_blacklist_names: std::sync::Arc::new(std::collections::HashSet::from_iter(
1768            arg.artemis_txt_blacklist_names.iter().cloned(),
1769        )),
1770        #[cfg(feature = "artemis")]
1771        artemis_txt_lang: arg.artemis_txt_lang.clone(),
1772    };
1773    match &arg.command {
1774        args::Command::Export { input, output } => {
1775            let (scripts, is_dir) =
1776                utils::files::collect_files(input, arg.recursive, false).unwrap();
1777            if is_dir {
1778                match &output {
1779                    Some(output) => {
1780                        let op = std::path::Path::new(output);
1781                        if op.exists() {
1782                            if !op.is_dir() {
1783                                eprintln!("Output path is not a directory");
1784                                return;
1785                            }
1786                        } else {
1787                            std::fs::create_dir_all(op).unwrap();
1788                        }
1789                    }
1790                    None => {}
1791                }
1792            }
1793            let root_dir = if is_dir {
1794                Some(std::path::Path::new(input))
1795            } else {
1796                None
1797            };
1798            for script in scripts.iter() {
1799                let re = export_script(&script, &arg, &cfg, output, root_dir);
1800                match re {
1801                    Ok(s) => {
1802                        COUNTER.inc(s);
1803                    }
1804                    Err(e) => {
1805                        COUNTER.inc_error();
1806                        eprintln!("Error exporting {}: {}", script, e);
1807                        if arg.backtrace {
1808                            eprintln!("Backtrace: {}", e.backtrace());
1809                        }
1810                    }
1811                }
1812            }
1813        }
1814        args::Command::Import(args) => {
1815            let name_csv = match &args.name_csv {
1816                Some(name_csv) => {
1817                    let name_table = utils::name_replacement::read_csv(name_csv).unwrap();
1818                    Some(name_table)
1819                }
1820                None => None,
1821            };
1822            let repl = match &args.replacement_json {
1823                Some(replacement_json) => {
1824                    let b = utils::files::read_file(replacement_json).unwrap();
1825                    let s = String::from_utf8(b).unwrap();
1826                    let table = serde_json::from_str::<types::ReplacementTable>(&s).unwrap();
1827                    Some(table)
1828                }
1829                None => None,
1830            };
1831            let (scripts, is_dir) =
1832                utils::files::collect_files(&args.input, arg.recursive, false).unwrap();
1833            if is_dir {
1834                let pb = std::path::Path::new(&args.patched);
1835                if pb.exists() {
1836                    if !pb.is_dir() {
1837                        eprintln!("Patched path is not a directory");
1838                        return;
1839                    }
1840                } else {
1841                    std::fs::create_dir_all(pb).unwrap();
1842                }
1843            }
1844            let root_dir = if is_dir {
1845                Some(std::path::Path::new(&args.input))
1846            } else {
1847                None
1848            };
1849            for script in scripts.iter() {
1850                let re = import_script(
1851                    &script,
1852                    &arg,
1853                    &cfg,
1854                    args,
1855                    root_dir,
1856                    name_csv.as_ref(),
1857                    repl.as_ref(),
1858                );
1859                match re {
1860                    Ok(s) => {
1861                        COUNTER.inc(s);
1862                    }
1863                    Err(e) => {
1864                        COUNTER.inc_error();
1865                        eprintln!("Error exporting {}: {}", script, e);
1866                        if arg.backtrace {
1867                            eprintln!("Backtrace: {}", e.backtrace());
1868                        }
1869                    }
1870                }
1871            }
1872        }
1873        args::Command::Pack { input, output } => {
1874            let re = pack_archive(input, output.as_ref().map(|s| s.as_str()), &arg, &cfg);
1875            if let Err(e) = re {
1876                COUNTER.inc_error();
1877                eprintln!("Error packing archive: {}", e);
1878            }
1879        }
1880        args::Command::Unpack { input, output } => {
1881            let (scripts, is_dir) = utils::files::collect_arc_files(input, arg.recursive).unwrap();
1882            if is_dir {
1883                match &output {
1884                    Some(output) => {
1885                        let op = std::path::Path::new(output);
1886                        if op.exists() {
1887                            if !op.is_dir() {
1888                                eprintln!("Output path is not a directory");
1889                                return;
1890                            }
1891                        } else {
1892                            std::fs::create_dir_all(op).unwrap();
1893                        }
1894                    }
1895                    None => {}
1896                }
1897            }
1898            let root_dir = if is_dir {
1899                Some(std::path::Path::new(input))
1900            } else {
1901                None
1902            };
1903            for script in scripts.iter() {
1904                let re = unpack_archive(&script, &arg, &cfg, output, root_dir);
1905                match re {
1906                    Ok(s) => {
1907                        COUNTER.inc(s);
1908                    }
1909                    Err(e) => {
1910                        COUNTER.inc_error();
1911                        eprintln!("Error unpacking {}: {}", script, e);
1912                        if arg.backtrace {
1913                            eprintln!("Backtrace: {}", e.backtrace());
1914                        }
1915                    }
1916                }
1917            }
1918        }
1919        args::Command::Create { input, output } => {
1920            let re = create_file(input, output.as_ref().map(|s| s.as_str()), &arg, &cfg);
1921            if let Err(e) = re {
1922                COUNTER.inc_error();
1923                eprintln!("Error creating file: {}", e);
1924                if arg.backtrace {
1925                    eprintln!("Backtrace: {}", e.backtrace());
1926                }
1927            }
1928        }
1929    }
1930    eprintln!("{}", std::ops::Deref::deref(&COUNTER));
1931}