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