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