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