msg_tool/
args.rs

1use crate::types::*;
2use clap::{ArgAction, ArgGroup, Parser, Subcommand};
3
4#[cfg(feature = "flate2")]
5fn parse_compression_level(level: &str) -> Result<u32, String> {
6    let lower = level.to_ascii_lowercase();
7    if lower == "none" {
8        return Ok(0);
9    } else if lower == "best" {
10        return Ok(9);
11    } else if lower == "default" {
12        return Ok(6);
13    } else if lower == "fast" {
14        return Ok(1);
15    }
16    clap_num::number_range(level, 0, 9)
17}
18
19#[cfg(feature = "mozjpeg")]
20fn parse_jpeg_quality(quality: &str) -> Result<u8, String> {
21    let lower = quality.to_ascii_lowercase();
22    if lower == "best" {
23        return Ok(100);
24    }
25    clap_num::number_range(quality, 0, 100)
26}
27
28#[cfg(feature = "zstd")]
29fn parse_zstd_compression_level(level: &str) -> Result<i32, String> {
30    let lower = level.to_ascii_lowercase();
31    if lower == "default" {
32        return Ok(3);
33    } else if lower == "best" {
34        return Ok(22);
35    }
36    clap_num::number_range(level, 0, 22)
37}
38
39#[cfg(feature = "webp")]
40fn parse_webp_quality(quality: &str) -> Result<u8, String> {
41    let lower = quality.to_ascii_lowercase();
42    if lower == "best" {
43        return Ok(100);
44    }
45    clap_num::number_range(quality, 0, 100)
46}
47
48/// Tools for export and import scripts
49#[derive(Parser, Debug)]
50#[clap(
51    group = ArgGroup::new("encodingg").multiple(false),
52    group = ArgGroup::new("output_encodingg").multiple(false),
53    group = ArgGroup::new("archive_encodingg").multiple(false),
54    group = ArgGroup::new("artemis_indentg").multiple(false),
55    group = ArgGroup::new("ex_hibit_rld_xor_keyg").multiple(false),
56    group = ArgGroup::new("ex_hibit_rld_def_xor_keyg").multiple(false),
57    group = ArgGroup::new("webp_qualityg").multiple(false),
58    group = ArgGroup::new("cat_system_int_encrypt_passwordg").multiple(false),
59)]
60#[command(
61    version,
62    about,
63    long_about = "Tools for export and import scripts\nhttps://github.com/lifegpc/msg-tool"
64)]
65pub struct Arg {
66    #[arg(short = 't', long, value_enum, global = true)]
67    /// Script type
68    pub script_type: Option<ScriptType>,
69    #[arg(short = 'T', long, value_enum, global = true)]
70    /// Output script type
71    pub output_type: Option<OutputScriptType>,
72    #[cfg(feature = "image")]
73    #[arg(short = 'i', long, value_enum, global = true)]
74    /// Output image type
75    pub image_type: Option<ImageOutputType>,
76    #[arg(short = 'e', long, value_enum, global = true, group = "encodingg")]
77    /// Script encoding
78    pub encoding: Option<TextEncoding>,
79    #[cfg(windows)]
80    #[arg(short = 'c', long, value_enum, global = true, group = "encodingg")]
81    /// Script code page
82    pub code_page: Option<u32>,
83    #[arg(
84        short = 'E',
85        long,
86        value_enum,
87        global = true,
88        group = "output_encodingg"
89    )]
90    /// Output text encoding
91    pub output_encoding: Option<TextEncoding>,
92    #[cfg(windows)]
93    #[arg(
94        short = 'C',
95        long,
96        value_enum,
97        global = true,
98        group = "output_encodingg"
99    )]
100    /// Output code page
101    pub output_code_page: Option<u32>,
102    #[arg(
103        short = 'a',
104        long,
105        value_enum,
106        global = true,
107        group = "archive_encodingg"
108    )]
109    /// Archive filename encoding
110    pub archive_encoding: Option<TextEncoding>,
111    #[cfg(windows)]
112    #[arg(
113        short = 'A',
114        long,
115        value_enum,
116        global = true,
117        group = "archive_encodingg"
118    )]
119    /// Archive code page
120    pub archive_code_page: Option<u32>,
121    #[cfg(feature = "circus")]
122    #[arg(long, value_enum, global = true)]
123    /// Circus Game
124    pub circus_mes_type: Option<CircusMesType>,
125    #[arg(short, long, action = ArgAction::SetTrue, global = true)]
126    /// Search for script files in the directory recursively
127    pub recursive: bool,
128    #[arg(global = true, action = ArgAction::SetTrue, short, long)]
129    /// Print backtrace on error
130    pub backtrace: bool,
131    #[cfg(feature = "escude-arc")]
132    #[arg(long, action = ArgAction::SetTrue, global = true)]
133    /// Whether to use fake compression for Escude archive
134    pub escude_fake_compress: bool,
135    #[cfg(feature = "escude")]
136    #[arg(long, global = true)]
137    /// The path to the Escude enum script file (enum_scr.bin)
138    pub escude_enum_scr: Option<String>,
139    #[cfg(feature = "bgi")]
140    #[arg(long, action = ArgAction::SetTrue, global = true)]
141    /// Duplicate same strings when importing into BGI scripts.
142    /// Enable this will cause BGI scripts to become very large.
143    pub bgi_import_duplicate: bool,
144    #[cfg(feature = "bgi")]
145    #[arg(long, action = ArgAction::SetTrue, global = true, alias = "bgi-no-append")]
146    /// Disable appending new strings to the end of BGI scripts.
147    /// Disable may cause BGI scripts broken.
148    pub bgi_disable_append: bool,
149    #[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
150    #[arg(long, global = true)]
151    /// Detect all files in BGI archive as SysGrp Images. By default, only files which name is `sysgrp.arc` will enabled this.
152    pub bgi_is_sysgrp_arc: Option<bool>,
153    #[cfg(feature = "bgi-img")]
154    #[arg(long, global = true)]
155    /// Whether to create scrambled SysGrp images. When in import mode, the default value depends on the original image.
156    /// When in creation mode, it is not enabled by default.
157    pub bgi_img_scramble: Option<bool>,
158    #[cfg(feature = "cat-system-arc")]
159    #[arg(long, global = true, group = "cat_system_int_encrypt_passwordg")]
160    /// CatSystem2 engine int archive password
161    pub cat_system_int_encrypt_password: Option<String>,
162    #[cfg(feature = "cat-system-arc")]
163    #[arg(long, global = true, group = "cat_system_int_encrypt_passwordg")]
164    /// The path to the CatSystem2 engine executable file. Used to get the int archive password.
165    pub cat_system_int_exe: Option<String>,
166    #[cfg(feature = "cat-system-img")]
167    #[arg(long, global = true, action = ArgAction::SetTrue)]
168    /// Draw CatSystem2 image on canvas (if canvas width and height are specified in file)
169    pub cat_system_image_canvas: bool,
170    #[cfg(feature = "kirikiri")]
171    #[arg(long, global = true)]
172    /// Kirikiri language index in script. If not specified, the first language will be used.
173    pub kirikiri_language_index: Option<usize>,
174    #[cfg(feature = "kirikiri")]
175    #[arg(long, global = true, action = ArgAction::SetTrue)]
176    /// Export COMU message to extra json file. (for Kirikiri SCN script.)
177    /// Only CIRCUS's game have COMU message.
178    pub kirikiri_export_comumode: bool,
179    #[cfg(feature = "kirikiri")]
180    #[arg(long, global = true)]
181    /// Kirikiri COMU message translation file. (Map<String, String>, key is original text, value is translated text.)
182    pub kirikiri_comumode_json: Option<String>,
183    #[cfg(feature = "kirikiri")]
184    #[arg(long, global = true, action = ArgAction::SetTrue, alias = "kr-no-empty-lines", alias = "kirikiri-no-empty-lines")]
185    /// Remove empty lines in Kirikiri KS script.
186    pub kirikiri_remove_empty_lines: bool,
187    #[cfg(feature = "kirikiri")]
188    #[arg(
189        long,
190        global = true,
191        value_delimiter = ',',
192        default_value = "nm,set_title,speaker,Talk,talk,cn,name,名前"
193    )]
194    /// Kirikiri name commands, used to extract names from ks script.
195    pub kirikiri_name_commands: Vec<String>,
196    #[cfg(feature = "kirikiri")]
197    #[arg(
198        long,
199        global = true,
200        value_delimiter = ',',
201        default_value = "sel01,sel02,sel03,sel04,AddSelect,ruby,exlink,e_xlink"
202    )]
203    /// Kirikiri message commands, used to extract more message from ks script.
204    pub kirikiri_message_commands: Vec<String>,
205    #[cfg(feature = "image")]
206    #[arg(short = 'f', long, global = true)]
207    /// Output multiple image as `<basename>_<name>.<ext>` instead of `<basename>/<name>.<ext>`
208    pub image_output_flat: bool,
209    #[cfg(feature = "bgi-arc")]
210    #[arg(long, global = true, action = ArgAction::SetTrue)]
211    /// Whether to compress files in BGI archive when packing BGI archive.
212    pub bgi_compress_file: bool,
213    #[cfg(feature = "bgi-arc")]
214    #[arg(long, global = true, default_value_t = 3, value_parser = crate::scripts::bgi::archive::dsc::parse_min_length)]
215    /// Minimum length of match size for DSC compression. Possible values are 2-256.
216    pub bgi_compress_min_len: usize,
217    #[cfg(feature = "emote-img")]
218    #[arg(long, global = true)]
219    /// Whether to overlay PIMG images. (By default, true if all layers are not group layers.)
220    pub emote_pimg_overlay: Option<bool>,
221    #[cfg(feature = "artemis-arc")]
222    #[arg(long, global = true)]
223    /// Disable Artemis archive (.pfs) XOR encryption when packing.
224    pub artemis_arc_disable_xor: bool,
225    #[cfg(feature = "artemis")]
226    #[arg(long, global = true, group = "artemis_indentg")]
227    /// Artemis script indent size, used to format Artemis script.
228    /// Default is 4 spaces.
229    pub artemis_indent: Option<usize>,
230    #[cfg(feature = "artemis")]
231    #[arg(long, global = true, action = ArgAction::SetTrue, group = "artemis_indentg")]
232    /// Disable Artemis script indent, used to format Artemis script.
233    pub artemis_no_indent: bool,
234    #[cfg(feature = "artemis")]
235    #[arg(long, global = true, default_value_t = 100)]
236    /// Max line width in Artemis script, used to format Artemis script.
237    pub artemis_max_line_width: usize,
238    #[cfg(feature = "artemis")]
239    #[arg(long, global = true)]
240    /// Specify the language of Artemis AST script.
241    /// If not specified, the first language will be used.
242    pub artemis_ast_lang: Option<String>,
243    #[cfg(feature = "artemis")]
244    #[arg(
245        long,
246        global = true,
247        value_delimiter = ',',
248        default_value = "遅延イベントCG,遅延背景,bgv_in,イベントCG,遅延ポップアップ"
249    )]
250    /// Artemis Engine blacklist tag names for TXT script.
251    /// This is used to ignore these tags when finding names in Artemis TXT script.
252    pub artemis_txt_blacklist_names: Vec<String>,
253    #[cfg(feature = "artemis")]
254    #[arg(long, global = true)]
255    /// Specify the language of Artemis TXT script.
256    /// If not specified, the first language will be used.
257    pub artemis_txt_lang: Option<String>,
258    #[cfg(feature = "cat-system")]
259    #[arg(long, global = true)]
260    /// CatSystem2 CSTL script language, used to extract messages from CSTL script.
261    /// If not specified, the first language will be used.
262    pub cat_system_cstl_lang: Option<String>,
263    #[cfg(feature = "flate2")]
264    #[arg(short = 'z', long, global = true, value_name = "LEVEL", value_parser = parse_compression_level, default_value_t = 6)]
265    /// Zlib compression level. 0 means no compression, 9 means best compression.
266    pub zlib_compression_level: u32,
267    #[cfg(feature = "image")]
268    #[arg(short = 'g', long, global = true, value_enum, default_value_t = PngCompressionLevel::Fast)]
269    /// PNG compression level.
270    pub png_compression_level: PngCompressionLevel,
271    #[cfg(feature = "circus-img")]
272    #[arg(long, global = true, action = ArgAction::SetTrue)]
273    /// Keep original BPP when importing Circus CRX images.
274    pub circus_crx_keep_original_bpp: bool,
275    #[cfg(feature = "circus-img")]
276    #[arg(long, global = true, action = ArgAction::SetTrue)]
277    /// Use zstd compression for Circus CRX images. (CIRCUS Engine don't support this. Hook is required.)
278    pub circus_crx_zstd: bool,
279    #[cfg(feature = "zstd")]
280    #[arg(short = 'Z', long, global = true, value_name = "LEVEL", value_parser = parse_zstd_compression_level, default_value_t = 3)]
281    /// Zstd compression level. 0 means default compression level (3), 22 means best compression.
282    pub zstd_compression_level: i32,
283    #[cfg(feature = "circus-img")]
284    #[arg(long, global = true, value_enum, default_value_t = crate::scripts::circus::image::crx::CircusCrxMode::Auto)]
285    /// Circus CRX image row type mode
286    pub circus_crx_mode: crate::scripts::circus::image::crx::CircusCrxMode,
287    #[cfg(feature = "circus-img")]
288    #[arg(long, global = true, action = ArgAction::SetTrue)]
289    /// Draw Circus CRX images on canvas (if canvas width and height are specified in file)
290    pub circus_crx_canvas: bool,
291    #[arg(short = 'F', long, global = true, action = ArgAction::SetTrue)]
292    /// Force all files in archive to be treated as script files.
293    pub force_script: bool,
294    #[cfg(feature = "ex-hibit")]
295    #[arg(
296        long,
297        global = true,
298        value_name = "HEX",
299        group = "ex_hibit_rld_xor_keyg"
300    )]
301    /// ExHibit xor key for rld script, in hexadecimal format. (e.g. `12345678`)
302    /// Use https://github.com/ZQF-ReVN/RxExHIBIT to find the key.
303    pub ex_hibit_rld_xor_key: Option<String>,
304    #[cfg(feature = "ex-hibit")]
305    #[arg(
306        long,
307        global = true,
308        value_name = "PATH",
309        group = "ex_hibit_rld_xor_keyg"
310    )]
311    /// ExHibit rld xor key file, which contains the xor key in hexadecimal format. (e.g. `0x12345678`)
312    pub ex_hibit_rld_xor_key_file: Option<String>,
313    #[cfg(feature = "ex-hibit")]
314    #[arg(
315        long,
316        global = true,
317        value_name = "HEX",
318        group = "ex_hibit_rld_def_xor_keyg"
319    )]
320    /// ExHibit rld def.rld xor key, in hexadecimal format. (e.g. `12345678`)
321    pub ex_hibit_rld_def_xor_key: Option<String>,
322    #[cfg(feature = "ex-hibit")]
323    #[arg(
324        long,
325        global = true,
326        value_name = "PATH",
327        group = "ex_hibit_rld_def_xor_keyg"
328    )]
329    /// ExHibit rld def.rld xor key file, which contains the xor key in hexadecimal format. (e.g. `0x12345678`)
330    pub ex_hibit_rld_def_xor_key_file: Option<String>,
331    #[cfg(feature = "ex-hibit")]
332    #[arg(long, global = true, value_name = "PATH")]
333    /// Path to the ExHibit rld keys file, which contains the keys in BINARY format.
334    /// Use https://github.com/ZQF-ReVN/RxExHIBIT to get this file.
335    pub ex_hibit_rld_keys: Option<String>,
336    #[cfg(feature = "ex-hibit")]
337    #[arg(long, global = true, value_name = "PATH")]
338    /// Path to the ExHibit rld def keys file, which contains the keys in BINARY format.
339    pub ex_hibit_rld_def_keys: Option<String>,
340    #[cfg(feature = "mozjpeg")]
341    #[arg(short = 'j', long, global = true, default_value_t = 80, value_parser = parse_jpeg_quality)]
342    /// JPEG quality for output images, 0-100. 100 means best quality.
343    pub jpeg_quality: u8,
344    #[cfg(feature = "webp")]
345    #[arg(short = 'w', long, global = true, group = "webp_qualityg")]
346    /// Use WebP lossless compression for output images.
347    pub webp_lossless: bool,
348    #[cfg(feature = "webp")]
349    #[arg(short = 'W', long, global = true, value_name = "QUALITY", group = "webp_qualityg", value_parser = parse_webp_quality, default_value_t = 80)]
350    /// WebP quality for output images, 0-100. 100 means best quality.
351    pub webp_quality: u8,
352    #[arg(long, global = true)]
353    /// Try use YAML format instead of JSON when custom exporting.
354    /// By default, this is based on output type. But can be overridden by this option.
355    pub custom_yaml: Option<bool>,
356    #[cfg(feature = "entis-gls")]
357    #[arg(long, global = true)]
358    /// Entis GLS srcxml script language, used to extract messages from srcxml script.
359    /// If not specified, the first language will be used.
360    pub entis_gls_srcxml_lang: Option<String>,
361    #[cfg(feature = "will-plus")]
362    #[arg(long, global = true)]
363    /// Disable disassembly for WillPlus ws2 script.
364    /// Use another parser to parse the script.
365    /// Should only be used when the default parser not works well.
366    pub will_plus_ws2_no_disasm: bool,
367    #[command(subcommand)]
368    /// Command
369    pub command: Command,
370}
371
372#[derive(Parser, Debug)]
373#[clap(group = ArgGroup::new("patched_encodingg").multiple(false), group = ArgGroup::new("patched_archive_encodingg").multiple(false))]
374pub struct ImportArgs {
375    /// Input script file or directory
376    pub input: String,
377    /// Text file or directory
378    pub output: String,
379    /// Patched script file or directory
380    pub patched: String,
381    #[arg(short = 'p', long, group = "patched_encodingg")]
382    /// Patched script encoding
383    pub patched_encoding: Option<TextEncoding>,
384    #[cfg(windows)]
385    #[arg(short = 'P', long, group = "patched_encodingg")]
386    /// Patched script code page
387    pub patched_code_page: Option<u32>,
388    #[arg(long, value_enum, group = "patched_archive_encodingg", alias = "pa")]
389    /// Patched archive filename encoding
390    pub patched_archive_encoding: Option<TextEncoding>,
391    #[cfg(windows)]
392    #[arg(long, value_enum, group = "patched_archive_encodingg", alias = "PA")]
393    /// Patched archive code page
394    pub patched_archive_code_page: Option<u32>,
395    #[arg(long)]
396    /// Patched script format type
397    pub patched_format: Option<FormatType>,
398    #[arg(long)]
399    /// Fixed length of one line in patched script (for fixed format)
400    pub patched_fixed_length: Option<usize>,
401    #[arg(long, action = ArgAction::SetTrue)]
402    /// Keep original line breaks in patched script (for fixed format)
403    pub patched_keep_original: bool,
404    #[arg(long)]
405    /// Name table file
406    pub name_csv: Option<String>,
407    #[arg(long)]
408    /// Replacement table file
409    pub replacement_json: Option<String>,
410    #[arg(long, action = ArgAction::SetTrue)]
411    pub warn_when_output_file_not_found: bool,
412}
413
414#[derive(Subcommand, Debug)]
415/// Commands
416pub enum Command {
417    /// Extract from script
418    Export {
419        /// Input script file or directory
420        input: String,
421        /// Output file or directory
422        output: Option<String>,
423    },
424    /// Import to script
425    Import(ImportArgs),
426    /// Pack files to archive
427    Pack {
428        /// Input directory
429        input: String,
430        /// Output archive file
431        output: Option<String>,
432    },
433    /// Unpack archive to directory
434    Unpack {
435        /// Input archive file
436        input: String,
437        /// Output directory
438        output: Option<String>,
439    },
440    /// Create a new script file
441    Create {
442        /// Input script
443        input: String,
444        /// Output script file
445        output: Option<String>,
446    },
447}
448
449pub fn parse_args() -> Arg {
450    Arg::parse()
451}
452
453#[cfg(feature = "ex-hibit")]
454pub fn load_ex_hibit_rld_xor_key(arg: &Arg) -> anyhow::Result<Option<u32>> {
455    if let Some(key) = &arg.ex_hibit_rld_xor_key {
456        if key.starts_with("0x") {
457            return Ok(Some(u32::from_str_radix(&key[2..], 16)?));
458        } else {
459            return Ok(Some(u32::from_str_radix(key, 16)?));
460        }
461    }
462    if let Some(file) = &arg.ex_hibit_rld_xor_key_file {
463        let key = std::fs::read_to_string(file)?.trim().to_string();
464        if key.starts_with("0x") {
465            return Ok(Some(u32::from_str_radix(&key[2..], 16)?));
466        } else {
467            return Ok(Some(u32::from_str_radix(&key, 16)?));
468        }
469    }
470    Ok(None)
471}
472
473#[cfg(feature = "ex-hibit")]
474pub fn load_ex_hibit_rld_def_xor_key(arg: &crate::args::Arg) -> anyhow::Result<Option<u32>> {
475    if let Some(key) = &arg.ex_hibit_rld_def_xor_key {
476        if key.starts_with("0x") {
477            return Ok(Some(u32::from_str_radix(&key[2..], 16)?));
478        } else {
479            return Ok(Some(u32::from_str_radix(key, 16)?));
480        }
481    }
482    if let Some(file) = &arg.ex_hibit_rld_def_xor_key_file {
483        let key = std::fs::read_to_string(file)?.trim().to_string();
484        if key.starts_with("0x") {
485            return Ok(Some(u32::from_str_radix(&key[2..], 16)?));
486        } else {
487            return Ok(Some(u32::from_str_radix(&key, 16)?));
488        }
489    }
490    Ok(None)
491}
492
493#[cfg(feature = "cat-system-arc")]
494pub fn get_cat_system_int_encrypt_password(arg: &Arg) -> anyhow::Result<Option<String>> {
495    if let Some(exe) = &arg.cat_system_int_exe {
496        return Ok(Some(
497            crate::scripts::cat_system::archive::int::get_password_from_exe(exe)?,
498        ));
499    }
500    if let Some(password) = &arg.cat_system_int_encrypt_password {
501        return Ok(Some(password.clone()));
502    }
503    Ok(None)
504}