msg_tool/
types.rs

1//! Basic types
2use clap::ValueEnum;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
7#[serde(untagged, rename_all = "camelCase")]
8/// Text Encoding
9pub enum Encoding {
10    /// Automatically detect encoding
11    Auto,
12    /// UTF-8 encoding
13    Utf8,
14    /// Shift-JIS encoding
15    Cp932,
16    /// GB2312 encoding
17    Gb2312,
18    /// UTF-16 Little Endian encoding
19    Utf16LE,
20    /// Code page encoding (Windows only)
21    #[cfg(windows)]
22    CodePage(u32),
23}
24
25impl Default for Encoding {
26    fn default() -> Self {
27        Encoding::Utf8
28    }
29}
30
31impl Encoding {
32    /// Returns true if the encoding is Shift-JIS (CP932).
33    pub fn is_jis(&self) -> bool {
34        match self {
35            Self::Cp932 => true,
36            #[cfg(windows)]
37            Self::CodePage(code_page) => *code_page == 932,
38            _ => false,
39        }
40    }
41
42    /// Returns true if the encoding is UTF-16LE.
43    pub fn is_utf16le(&self) -> bool {
44        match self {
45            Self::Utf16LE => true,
46            #[cfg(windows)]
47            Self::CodePage(code_page) => *code_page == 1200,
48            _ => false,
49        }
50    }
51
52    /// Returns true if the encoding is UTF8.
53    pub fn is_utf8(&self) -> bool {
54        match self {
55            Self::Utf8 => true,
56            #[cfg(windows)]
57            Self::CodePage(code_page) => *code_page == 65001,
58            _ => false,
59        }
60    }
61
62    /// Returns the charset name
63    pub fn charset(&self) -> Option<&'static str> {
64        match self {
65            Self::Auto => None,
66            Self::Utf8 => Some("UTF-8"),
67            Self::Cp932 => Some("shift_jis"),
68            Self::Gb2312 => Some("gbk"),
69            Self::Utf16LE => Some("utf-16le"),
70            #[cfg(windows)]
71            Self::CodePage(code_page) => match *code_page {
72                932 => Some("shift_jis"),
73                65001 => Some("utf-8"),
74                1200 => Some("utf-16le"),
75                936 => Some("gbk"),
76                _ => None,
77            },
78        }
79    }
80}
81
82#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
83/// Text Encoding (for CLI)
84pub enum TextEncoding {
85    /// Use script's default encoding
86    Default,
87    /// Automatically detect encoding
88    Auto,
89    /// UTF-8 encoding
90    Utf8,
91    #[value(alias("jis"))]
92    /// Shift-JIS encoding
93    Cp932,
94    #[value(alias("gbk"))]
95    /// GB2312 encoding
96    Gb2312,
97}
98
99#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
100/// Script type
101pub enum OutputScriptType {
102    /// Text script
103    M3t,
104    /// Same as M3t, buf different extension
105    M3ta,
106    /// Same as M3t, buf different extension
107    M3tTxt,
108    /// JSON which can be used for GalTransl
109    Json,
110    /// YAML (same as JSON, but with YAML syntax)
111    Yaml,
112    /// Gettext .pot file
113    Pot,
114    /// Gettext .po file
115    Po,
116    /// Custom output
117    Custom,
118}
119
120impl OutputScriptType {
121    /// Returns true if the script type is custom.
122    pub fn is_custom(&self) -> bool {
123        matches!(self, OutputScriptType::Custom)
124    }
125
126    /// Returns true if the script type is M3t/M3ta/M3tTxt.
127    pub fn is_m3t(&self) -> bool {
128        matches!(
129            self,
130            OutputScriptType::M3t | OutputScriptType::M3ta | OutputScriptType::M3tTxt
131        )
132    }
133}
134
135impl AsRef<str> for OutputScriptType {
136    /// Returns the extension for the script type.
137    fn as_ref(&self) -> &str {
138        match self {
139            OutputScriptType::M3t => "m3t",
140            OutputScriptType::M3ta => "m3ta",
141            OutputScriptType::M3tTxt => "txt",
142            OutputScriptType::Json => "json",
143            OutputScriptType::Yaml => "yaml",
144            OutputScriptType::Pot => "pot",
145            OutputScriptType::Po => "po",
146            OutputScriptType::Custom => "",
147        }
148    }
149}
150
151#[cfg(feature = "circus")]
152#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
153/// Circus MES game
154pub enum CircusMesType {
155    /// fortissimo//Akkord:Bsusvier
156    Ffexa,
157    /// fortissimo EXS//Akkord:nächsten Phase
158    Ffexs,
159    /// Eternal Fantasy
160    Ef,
161    /// D.C.〜ダ・カーポ〜 温泉編
162    Dcos,
163    /// ことり Love Ex P
164    Ktlep,
165    /// D.C.WhiteSeason
166    Dcws,
167    /// D.C. Summer Vacation
168    Dcsv,
169    /// D.C.P.C.(Vista)
170    Dcpc,
171    /// D.C.〜ダ・カーポ〜 MEMORIES DISC
172    Dcmems,
173    /// D.C. Dream X’mas
174    Dcdx,
175    /// D.C.A.S. 〜ダ・カーポ〜アフターシーズンズ
176    Dcas,
177    /// D.C.II 春風のアルティメットバトル!
178    Dcbs,
179    /// D.C.II Fall in Love
180    Dc2fl,
181    /// D.C.II 春風のアルティメットバトル!
182    Dc2bs,
183    /// D.C.II Dearest Marriage
184    Dc2dm,
185    /// D.C.II 〜featuring Yun2〜
186    Dc2fy,
187    /// D.C.II C.C. 月島小恋のらぶらぶバスルーム
188    Dc2cckko,
189    /// D.C.II C.C. 音姫先生のどきどき特別授業
190    Dc2ccotm,
191    /// D.C.II Spring Celebration
192    Dc2sc,
193    /// D.C.II To You
194    Dc2ty,
195    /// D.C.II P.C.
196    Dc2pc,
197    /// D.C.III RX-rated
198    Dc3rx,
199    /// D.C.III P.P.~ダ・カーポIII プラチナパートナー~
200    Dc3pp,
201    /// D.C.III WithYou
202    Dc3wy,
203    /// D.C.III DreamDays
204    Dc3dd,
205    /// D.C.4 ~ダ・カーポ4~
206    Dc4,
207    /// D.C.4 Plus Harmony 〜ダ・カーポ4〜 プラスハーモニー
208    Dc4ph,
209    /// D.S. -Dal Segno-
210    Ds,
211    /// D.S.i.F. -Dal Segno- in Future
212    Dsif,
213    /// てんぷれ!
214    Tmpl,
215    /// 百花百狼/Hyakka Hyakurou
216    Nightshade,
217}
218
219#[cfg(feature = "circus")]
220impl AsRef<str> for CircusMesType {
221    /// Returns the name.
222    fn as_ref(&self) -> &str {
223        match self {
224            CircusMesType::Ffexa => "ffexa",
225            CircusMesType::Ffexs => "ffexs",
226            CircusMesType::Ef => "ef",
227            CircusMesType::Dcos => "dcos",
228            CircusMesType::Ktlep => "ktlep",
229            CircusMesType::Dcws => "dcws",
230            CircusMesType::Dcsv => "dcsv",
231            CircusMesType::Dcpc => "dcpc",
232            CircusMesType::Dcmems => "dcmems",
233            CircusMesType::Dcdx => "dcdx",
234            CircusMesType::Dcas => "dcas",
235            CircusMesType::Dcbs => "dcbs",
236            CircusMesType::Dc2fl => "dc2fl",
237            CircusMesType::Dc2bs => "dc2bs",
238            CircusMesType::Dc2dm => "dc2dm",
239            CircusMesType::Dc2fy => "dc2fy",
240            CircusMesType::Dc2cckko => "dc2cckko",
241            CircusMesType::Dc2ccotm => "dc2ccotm",
242            CircusMesType::Dc2sc => "dc2sc",
243            CircusMesType::Dc2ty => "dc2ty",
244            CircusMesType::Dc2pc => "dc2pc",
245            CircusMesType::Dc3rx => "dc3rx",
246            CircusMesType::Dc3pp => "dc3pp",
247            CircusMesType::Dc3wy => "dc3wy",
248            CircusMesType::Dc3dd => "dc3dd",
249            CircusMesType::Dc4 => "dc4",
250            CircusMesType::Dc4ph => "dc4ph",
251            CircusMesType::Ds => "ds",
252            CircusMesType::Dsif => "dsif",
253            CircusMesType::Tmpl => "tmpl",
254            CircusMesType::Nightshade => "nightshade",
255        }
256    }
257}
258
259/// Extra configuration options for the script.
260#[derive(Debug, Clone, msg_tool_macro::Default)]
261pub struct ExtraConfig {
262    #[cfg(feature = "circus")]
263    /// Circus Game for circus MES script.
264    pub circus_mes_type: Option<CircusMesType>,
265    #[cfg(feature = "escude-arc")]
266    /// Whether to use fake compression for Escude archive
267    pub escude_fake_compress: bool,
268    #[cfg(feature = "escude")]
269    /// The path to the Escude enum script file (enum_scr.bin)
270    pub escude_enum_scr: Option<String>,
271    #[cfg(feature = "bgi")]
272    /// Duplicate same strings when importing into BGI scripts.
273    /// Enable this will cause BGI scripts to become very large.
274    pub bgi_import_duplicate: bool,
275    #[cfg(feature = "bgi")]
276    /// Disable appending new strings to the end of BGI scripts.
277    /// Disable may cause BGI scripts broken.
278    pub bgi_disable_append: bool,
279    #[cfg(feature = "image")]
280    /// Output image type
281    pub image_type: Option<ImageOutputType>,
282    #[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
283    /// Detect all files in BGI archive as SysGrp Images. By default, only files which name is `sysgrp.arc` will enabled this.
284    pub bgi_is_sysgrp_arc: Option<bool>,
285    #[cfg(feature = "bgi-img")]
286    /// Whether to create scrambled SysGrp images. When in import mode, the default value depends on the original image.
287    /// When in creation mode, it is not enabled by default.
288    pub bgi_img_scramble: Option<bool>,
289    #[cfg(feature = "cat-system-arc")]
290    /// CatSystem2 engine int archive password
291    pub cat_system_int_encrypt_password: Option<String>,
292    #[cfg(feature = "cat-system-img")]
293    /// Draw CatSystem2 image on canvas (if canvas width and height are specified in file)
294    pub cat_system_image_canvas: bool,
295    #[cfg(feature = "kirikiri")]
296    /// Kirikiri language index in script. If not specified, the first language will be used.
297    pub kirikiri_language_index: Option<usize>,
298    #[cfg(feature = "kirikiri")]
299    /// Export chat message to extra json file. (for Kirikiri SCN script.)
300    /// For example, CIRCUS's comu message. Yuzusoft's phone chat message.
301    pub kirikiri_export_chat: bool,
302    #[cfg(feature = "kirikiri")]
303    /// Kirikiri chat message key. For example, CIRCUS's key is "comumode". Yuzusoft's key is "phonechat".
304    /// If not specified, "comumode" will be used.
305    pub kirikiri_chat_key: Option<Vec<String>>,
306    #[cfg(feature = "kirikiri")]
307    /// Kirikiri chat message translation. The outter object's key is filename(`global` is a special key).
308    /// The inner object: key is original text, value is (translated text, original text count).
309    pub kirikiri_chat_json:
310        Option<std::sync::Arc<HashMap<String, HashMap<String, (String, usize)>>>>,
311    #[cfg(feature = "kirikiri")]
312    /// Kirikiri language list. First language code is code for language index 1.
313    pub kirikiri_languages: Option<std::sync::Arc<Vec<String>>>,
314    #[cfg(feature = "kirikiri")]
315    /// Remove empty lines in Kirikiri KS script.
316    pub kirikiri_remove_empty_lines: bool,
317    #[cfg(feature = "kirikiri")]
318    /// Kirikiri name commands, used to extract names from ks script.
319    pub kirikiri_name_commands: std::sync::Arc<std::collections::HashSet<String>>,
320    #[cfg(feature = "kirikiri")]
321    /// Kirikiri message commands, used to extract more message from ks script.
322    pub kirikiri_message_commands: std::sync::Arc<std::collections::HashSet<String>>,
323    #[cfg(feature = "bgi-arc")]
324    /// Whether to compress files in BGI archive when packing BGI archive.
325    pub bgi_compress_file: bool,
326    #[cfg(feature = "bgi-arc")]
327    #[default(3)]
328    /// Minimum length of match size for DSC compression. Possible values are 2-256.
329    pub bgi_compress_min_len: usize,
330    #[cfg(feature = "emote-img")]
331    /// Whether to overlay PIMG images. (By default, true if all layers are not group layers.)
332    pub emote_pimg_overlay: Option<bool>,
333    #[cfg(feature = "artemis-arc")]
334    /// Disable Artemis archive (.pfs) XOR encryption when packing.
335    pub artemis_arc_disable_xor: bool,
336    #[cfg(feature = "artemis")]
337    /// Artemis script indent size, used to format Artemis script.
338    /// Default is 4 spaces.
339    pub artemis_indent: Option<usize>,
340    #[cfg(feature = "artemis")]
341    /// Disable Artemis script indent, used to format Artemis script.
342    pub artemis_no_indent: bool,
343    #[cfg(feature = "artemis")]
344    #[default(100)]
345    /// Max line width in Artemis script, used to format Artemis script.
346    pub artemis_max_line_width: usize,
347    #[cfg(feature = "artemis")]
348    /// Specify the language of Artemis AST script.
349    /// If not specified, the first language will be used.
350    pub artemis_ast_lang: Option<String>,
351    #[cfg(feature = "cat-system")]
352    /// CatSystem2 CSTL script language, used to extract messages from CSTL script.
353    /// If not specified, the first language will be used.
354    pub cat_system_cstl_lang: Option<String>,
355    #[cfg(feature = "flate2")]
356    #[default(6)]
357    /// Zlib compression level. 0 means no compression, 9 means best compression.
358    pub zlib_compression_level: u32,
359    #[cfg(feature = "image")]
360    /// PNG compression level.
361    pub png_compression_level: PngCompressionLevel,
362    #[cfg(feature = "circus-img")]
363    /// Keep original BPP when importing Circus CRX images.
364    pub circus_crx_keep_original_bpp: bool,
365    #[cfg(feature = "circus-img")]
366    /// Use zstd compression for Circus CRX images. (CIRCUS Engine don't support this. Hook is required.)
367    pub circus_crx_zstd: bool,
368    #[cfg(feature = "zstd")]
369    #[default(3)]
370    /// Zstd compression level. 0 means default compression level (3), 22 means best compression.
371    pub zstd_compression_level: i32,
372    #[cfg(feature = "circus-img")]
373    /// Circus CRX image row type mode
374    pub circus_crx_mode: crate::scripts::circus::image::crx::CircusCrxMode,
375    #[cfg(feature = "ex-hibit")]
376    /// ExHibit xor key for rld script.
377    /// Use [ReExHIBIT](https://github.com/ZQF-ReVN/RxExHIBIT) to find the key.
378    pub ex_hibit_rld_xor_key: Option<u32>,
379    #[cfg(feature = "ex-hibit")]
380    /// ExHibit def.rld xor key.
381    pub ex_hibit_rld_def_xor_key: Option<u32>,
382    #[cfg(feature = "ex-hibit")]
383    /// ExHibit rld xor keys.
384    pub ex_hibit_rld_keys: Option<Box<[u32; 0x100]>>,
385    #[cfg(feature = "ex-hibit")]
386    /// ExHibit def.rld xor keys.
387    pub ex_hibit_rld_def_keys: Option<Box<[u32; 0x100]>>,
388    #[cfg(feature = "mozjpeg")]
389    #[default(80)]
390    /// JPEG quality for output images, 0-100. 100 means best quality.
391    pub jpeg_quality: u8,
392    #[cfg(feature = "webp")]
393    /// Use WebP lossless compression for output images.
394    pub webp_lossless: bool,
395    #[cfg(feature = "webp")]
396    #[default(80)]
397    /// WebP quality for output images, 0-100. 100 means best quality.
398    pub webp_quality: u8,
399    #[cfg(feature = "circus-img")]
400    /// Draw Circus CRX images on canvas (if canvas width and height are specified in file)
401    pub circus_crx_canvas: bool,
402    /// Try use YAML format instead of JSON when custom exporting.
403    pub custom_yaml: bool,
404    #[cfg(feature = "entis-gls")]
405    /// Entis GLS srcxml script language, used to extract messages from srcxml script.
406    /// If not specified, the first language will be used.
407    pub entis_gls_srcxml_lang: Option<String>,
408    #[cfg(feature = "will-plus")]
409    /// Disable disassembly for WillPlus ws2 script.
410    /// Use another parser to parse the script.
411    /// Should only be used when the default parser not works well.
412    pub will_plus_ws2_no_disasm: bool,
413    #[cfg(feature = "artemis-panmimisoft")]
414    /// Artemis Engine blacklist tag names for TXT script.
415    /// This is used to ignore these tags when finding names in Artemis TXT (ぱんみみそふと) script.
416    pub artemis_panmimisoft_txt_blacklist_names: std::sync::Arc<std::collections::HashSet<String>>,
417    #[cfg(feature = "artemis-panmimisoft")]
418    /// Specify the language of Artemis TXT (ぱんみみそふと) script.
419    /// If not specified, the first language will be used.
420    pub artemis_panmimisoft_txt_lang: Option<String>,
421    #[cfg(feature = "lossless-audio")]
422    /// Audio format for output lossless audio files.
423    pub lossless_audio_fmt: LosslessAudioFormat,
424    #[cfg(feature = "audio-flac")]
425    #[default(5)]
426    /// FLAC compression level for output FLAC audio files. 0 means fastest compression, 8 means best compression. Default level is 5.
427    pub flac_compression_level: u32,
428    #[cfg(feature = "artemis")]
429    #[default(true)]
430    /// Format lua code in Artemis ASB script(.asb/.iet) when exporting.
431    pub artemis_asb_format_lua: bool,
432    #[cfg(feature = "kirikiri")]
433    /// Whether to handle title in Kirikiri SCN script.
434    pub kirikiri_title: bool,
435    #[cfg(feature = "favorite")]
436    #[default(true)]
437    /// Whether to filter ascii strings in Favorite HCB script.
438    pub favorite_hcb_filter_ascii: bool,
439    #[cfg(feature = "bgi-img")]
440    #[default(get_default_threads())]
441    /// Workers count for decode BGI compressed images v2 in parallel. Default is half of CPU cores.
442    /// Set this to 1 to disable parallel decoding. 0 means same as 1.
443    pub bgi_img_workers: usize,
444    #[cfg(feature = "image-jxl")]
445    #[default(true)]
446    /// Use JXL lossless compression for output images. Enabled by default.
447    pub jxl_lossless: bool,
448    #[cfg(feature = "image-jxl")]
449    #[default(1.0)]
450    /// JXL distance for output images. 0 means mathematically lossless compression. 1.0 means visually lossless compression.
451    /// Allowed range is 0.0-25.0. Recommended range is 0.5-3.0. Default value is 1.0.
452    pub jxl_distance: f32,
453    #[cfg(feature = "image-jxl")]
454    #[default(1)]
455    /// Workers count for encode JXL images in parallel. Default is 1.
456    /// Set this to 1 to disable parallel encoding. 0 means same as 1
457    pub jxl_workers: usize,
458    #[cfg(feature = "emote-img")]
459    #[default(true)]
460    /// Process tlg images.
461    pub psb_process_tlg: bool,
462    #[cfg(feature = "softpal-img")]
463    #[default(true)]
464    /// Whether to use fake compression for Softpal Pgd images. Enabled by default.
465    /// WARN: Compress may cause image broken.
466    pub pgd_fake_compress: bool,
467    #[cfg(feature = "softpal")]
468    /// Whether to add message index to Softpal src script when exporting.
469    pub softpal_add_message_index: bool,
470    #[cfg(feature = "kirikiri")]
471    #[default(true)]
472    /// Enable multi-language support for Kirikiri chat messages. Default is true.
473    /// Note: This requires [Self::kirikiri_language_index] and [Self::kirikiri_languages] to be set correctly.
474    pub kirikiri_chat_multilang: bool,
475    #[cfg(feature = "kirikiri-arc")]
476    #[default(true)]
477    /// Decrypt SimpleCrypt files in Kirikiri XP3 archive when extracting. Default is true.
478    pub xp3_simple_crypt: bool,
479    #[cfg(feature = "kirikiri-arc")]
480    #[default(true)]
481    /// Decompress mdf files in Kirikiri XP3 archive when extracting. Default is true.
482    pub xp3_mdf_decompress: bool,
483    #[cfg(feature = "kirikiri-arc")]
484    /// Configuration for Kirikiri XP3 segmenter when creating XP3 archive.
485    pub xp3_segmenter: crate::scripts::kirikiri::archive::xp3::SegmenterConfig,
486    #[cfg(feature = "kirikiri-arc")]
487    #[default(true)]
488    /// Compress files in Kirikiri XP3 archive when creating. Default is true.
489    pub xp3_compress_files: bool,
490    #[cfg(feature = "kirikiri-arc")]
491    #[default(true)]
492    /// Compress index in Kirikiri XP3 archive when creating. Default is true.
493    pub xp3_compress_index: bool,
494    #[cfg(feature = "kirikiri-arc")]
495    #[default(num_cpus::get())]
496    /// Workers count for compress files in Kirikiri XP3 archive when creating in parallel. Default is CPU cores count.
497    pub xp3_compress_workers: usize,
498    #[cfg(feature = "kirikiri-arc")]
499    /// Use zstd compression for files in Kirikiri XP3 archive when creating. (Warning: Kirikiri engine don't support this. Hook is required.)
500    pub xp3_zstd: bool,
501    #[cfg(feature = "kirikiri-arc")]
502    #[default(1)]
503    /// Workers count for packing file in Kirikiri XP3 archive in parallel. Default is 1.
504    /// This not works when segment is disabled.
505    pub xp3_pack_workers: usize,
506    #[cfg(feature = "kirikiri-arc")]
507    /// Disable adler32 checksum for Kirikiri XP3 archive when creating.
508    /// This will keep compatibility with https://github.com/arcusmaximus/KirikiriTools tool.
509    pub xp3_no_adler: bool,
510    #[cfg(feature = "kirikiri")]
511    /// Insert new language at the specified index in Kirikiri SCN script. If index is out of bounds, this flags will be ignored.
512    pub kirikiri_language_insert: bool,
513    #[cfg(feature = "musica-arc")]
514    /// Musica game title for paz archive.
515    pub musica_game_title: Option<String>,
516    #[cfg(feature = "musica-arc")]
517    /// Musica xor key for paz archive.
518    pub musica_xor_key: Option<u8>,
519    #[cfg(feature = "musica-arc")]
520    /// Whether to compress files in Musica paz archive when packing paz archive.
521    pub musica_compress: bool,
522    #[cfg(feature = "bgi")]
523    /// Add an additional space at the end of message in BGI scripts when importing.
524    /// This may help BGI engine to display the message correctly in save/load screen for some games.
525    pub bgi_add_space: bool,
526}
527
528#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
529/// Script type
530pub enum ScriptType {
531    #[cfg(feature = "artemis")]
532    /// Artemis Engine AST script
533    Artemis,
534    #[cfg(feature = "artemis")]
535    /// Artemis Engine ASB script
536    ArtemisAsb,
537    #[cfg(feature = "artemis")]
538    /// Artemis Engine TXT (General) script
539    ArtemisTxt,
540    #[cfg(feature = "artemis-panmimisoft")]
541    /// Artemis Engine TXT (ぱんみみそふと) script
542    ArtemisPanmimisoftTxt,
543    #[cfg(feature = "artemis-arc")]
544    #[value(alias("pfs"))]
545    /// Artemis archive (pfs)
546    ArtemisArc,
547    #[cfg(feature = "artemis-arc")]
548    #[value(alias("pf2"))]
549    /// Artemis archive (pf2) (.pfs)
550    ArtemisPf2,
551    #[cfg(feature = "bgi")]
552    #[value(alias("ethornell"))]
553    /// Buriko General Interpreter/Ethornell Script
554    BGI,
555    #[cfg(feature = "bgi")]
556    #[value(alias("ethornell-bsi"))]
557    /// Buriko General Interpreter/Ethornell bsi script (._bsi)
558    BGIBsi,
559    #[cfg(feature = "bgi")]
560    #[value(alias("ethornell-bp"))]
561    /// Buriko General Interpreter/Ethornell bp script (._bp)
562    BGIBp,
563    #[cfg(feature = "bgi-arc")]
564    #[value(alias = "ethornell-arc-v1")]
565    /// Buriko General Interpreter/Ethornell archive v1
566    BGIArcV1,
567    #[cfg(feature = "bgi-arc")]
568    #[value(alias = "ethornell-arc-v2", alias = "bgi-arc", alias = "ethornell-arc")]
569    /// Buriko General Interpreter/Ethornell archive v2
570    BGIArcV2,
571    #[cfg(feature = "bgi-arc")]
572    #[value(alias("ethornell-dsc"))]
573    /// Buriko General Interpreter/Ethornell compressed file (DSC)
574    BGIDsc,
575    #[cfg(feature = "bgi-audio")]
576    #[value(alias("ethornell-audio"))]
577    /// Buriko General Interpreter/Ethornell audio file (Ogg/Vorbis)
578    BGIAudio,
579    #[cfg(feature = "bgi-img")]
580    #[value(alias("ethornell-img"))]
581    /// Buriko General Interpreter/Ethornell image (Image files in sysgrp.arc)
582    BGIImg,
583    #[cfg(feature = "bgi-img")]
584    #[value(alias("ethornell-cbg"))]
585    /// Buriko General Interpreter/Ethornell Compressed Background image (CBG)
586    BGICbg,
587    #[cfg(feature = "cat-system")]
588    /// CatSystem2 engine scene script
589    CatSystem,
590    #[cfg(feature = "cat-system")]
591    /// CatSystem2 engine CSTL script
592    CatSystemCstl,
593    #[cfg(feature = "cat-system-arc")]
594    /// CatSystem2 engine archive
595    CatSystemInt,
596    #[cfg(feature = "cat-system-img")]
597    /// CatSystem2 engine image
598    CatSystemHg3,
599    #[cfg(feature = "circus")]
600    /// Circus MES script
601    Circus,
602    #[cfg(feature = "circus-arc")]
603    /// Circus Image archive
604    CircusCrm,
605    #[cfg(feature = "circus-arc")]
606    /// Circus DAT archive
607    CircusDat,
608    #[cfg(feature = "circus-arc")]
609    /// Circus PCK archive
610    CircusPck,
611    #[cfg(feature = "circus-audio")]
612    /// Circus PCM audio
613    CircusPcm,
614    #[cfg(feature = "circus-img")]
615    /// Circus CRX Image
616    CircusCrx,
617    #[cfg(feature = "circus-img")]
618    /// Circus Differential Image
619    CircusCrxd,
620    #[cfg(feature = "emote-img")]
621    #[value(alias("psb"))]
622    /// Emote PSB (basic handle)
623    EmotePsb,
624    #[cfg(feature = "emote-img")]
625    #[value(alias("pimg"))]
626    /// Emote PIMG image
627    EmotePimg,
628    #[cfg(feature = "emote-img")]
629    #[value(alias("dref"))]
630    /// Emote DREF(DPAK-referenced) image
631    EmoteDref,
632    #[cfg(feature = "entis-gls")]
633    /// Entis GLS srcxml Script
634    EntisGls,
635    #[cfg(feature = "escude-arc")]
636    /// Escude bin archive
637    EscudeArc,
638    #[cfg(feature = "escude")]
639    /// Escude bin script
640    Escude,
641    #[cfg(feature = "escude")]
642    /// Escude list script
643    EscudeList,
644    #[cfg(feature = "ex-hibit")]
645    /// ExHibit rld script
646    ExHibit,
647    #[cfg(feature = "ex-hibit-arc")]
648    /// ExHibit GRP archive
649    ExHibitGrp,
650    #[cfg(feature = "favorite")]
651    /// Favorite hcb script
652    Favorite,
653    #[cfg(feature = "hexen-haus")]
654    /// HexenHaus bin script
655    HexenHaus,
656    #[cfg(feature = "hexen-haus-arc")]
657    /// HexenHaus Arcc archive
658    HexenHausArcc,
659    #[cfg(feature = "hexen-haus-arc")]
660    /// HexenHaus Audio archive
661    HexenHausOdio,
662    #[cfg(feature = "hexen-haus-arc")]
663    /// HexenHaus WAG archive
664    HexenHausWag,
665    #[cfg(feature = "hexen-haus-img")]
666    /// HexenHaus PNG image
667    HexenHausPng,
668    #[cfg(feature = "kirikiri")]
669    #[value(alias("kr-scn"))]
670    /// Kirikiri SCN script
671    KirikiriScn,
672    #[cfg(feature = "kirikiri")]
673    #[value(alias("kr-simple-crypt"))]
674    /// Kirikiri SimpleCrypt's text file
675    KirikiriSimpleCrypt,
676    #[cfg(feature = "kirikiri")]
677    #[value(alias = "kr", alias = "kr-ks", alias = "kirikiri-ks")]
678    /// Kirikiri script
679    Kirikiri,
680    #[cfg(feature = "kirikiri-arc")]
681    #[value(alias = "kr-xp3", alias = "xp3")]
682    /// Kirikiri XP3 archive
683    KirikiriXp3,
684    #[cfg(feature = "kirikiri-img")]
685    #[value(alias("kr-tlg"))]
686    /// Kirikiri TLG image
687    KirikiriTlg,
688    #[cfg(feature = "kirikiri")]
689    #[value(alias("kr-mdf"))]
690    /// Kirikiri MDF (zlib compressed) file
691    KirikiriMdf,
692    #[cfg(feature = "kirikiri")]
693    #[value(alias("kr-tjs2"))]
694    /// Kirikiri compiled TJS2 script
695    KirikiriTjs2,
696    #[cfg(feature = "kirikiri")]
697    #[value(alias("kr-tjs-ns0"))]
698    /// Kirikiri TJS NS0 binary encoded script
699    KirikiriTjsNs0,
700    #[cfg(feature = "musica")]
701    /// Musica Script (.sc)
702    Musica,
703    #[cfg(feature = "musica-arc")]
704    /// Musica Engine Resource Archive (.paz)
705    MusicaPaz,
706    #[cfg(feature = "silky")]
707    /// Silky Engine Mes script
708    Silky,
709    #[cfg(feature = "silky")]
710    /// Silky Engine Map script
711    SilkyMap,
712    #[cfg(feature = "softpal")]
713    /// Softpal src script
714    Softpal,
715    #[cfg(feature = "softpal-arc")]
716    /// Softpal Pac archive
717    SoftpalPac,
718    #[cfg(feature = "softpal-arc")]
719    /// Softpal Pac/AMUSE archive
720    SoftpalPacAmuse,
721    #[cfg(feature = "softpal-img")]
722    #[value(alias = "pgd-ge", alias = "pgd")]
723    /// Softpal Pgd Ge image
724    SoftpalPgdGe,
725    #[cfg(feature = "softpal-img")]
726    #[value(alias = "softpal-pgd2", alias = "pgd3", alias = "pgd2")]
727    /// Softpal Pgd Differential image
728    SoftpalPgd3,
729    #[cfg(feature = "will-plus")]
730    #[value(alias("adv-hd-ws2"))]
731    /// WillPlus ws2 script
732    WillPlusWs2,
733    #[cfg(feature = "will-plus-img")]
734    #[value(alias("adv-hd-wip"))]
735    /// WillPlus WIP Image
736    WillPlusWip,
737    #[cfg(feature = "yaneurao-itufuru")]
738    #[value(alias("itufuru"))]
739    /// Yaneurao Itufuru script
740    YaneuraoItufuru,
741    #[cfg(feature = "yaneurao-itufuru")]
742    #[value(alias("itufuru-arc"))]
743    /// Yaneurao Itufuru script archive
744    YaneuraoItufuruArc,
745}
746
747#[derive(Clone, Debug, Serialize, Deserialize)]
748/// Message structure for scripts
749pub struct Message {
750    #[serde(skip_serializing_if = "Option::is_none")]
751    /// Optional name for the message, used in some scripts.
752    pub name: Option<String>,
753    /// The actual message content.
754    pub message: String,
755}
756
757impl Message {
758    /// Creates a new `Message` instance.
759    pub fn new(message: String, name: Option<String>) -> Self {
760        Message { message, name }
761    }
762}
763
764/// Result of script operation.
765pub enum ScriptResult {
766    /// Operation completed successfully.
767    Ok,
768    /// Operation completed without any changes.
769    /// For example, no messages found in the script.
770    Ignored,
771    /// Operation not completed.
772    /// This will not count in statistics.
773    Uncount,
774}
775
776#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
777/// Format type (for CLI)
778pub enum FormatType {
779    /// Wrap line with fixed length
780    Fixed,
781    /// Do not wrap line
782    None,
783}
784
785#[derive(Clone)]
786/// Format options
787pub enum FormatOptions {
788    /// Wrap line with fixed length
789    Fixed {
790        /// Fixed length
791        length: usize,
792        /// Whether to keep original line breaks
793        keep_original: bool,
794        /// Whether to break words(ASCII only) at the end of the line
795        break_words: bool,
796        /// Whether to insert a full-width space after a line break when a sentence starts with a full-width quotation mark.
797        insert_fullwidth_space_at_line_start: bool,
798        /// If a line break occurs in the middle of some symbols, bring the sentence to next line
799        break_with_sentence: bool,
800        #[cfg(feature = "jieba")]
801        /// Whether to break Chinese words at the end of the line.
802        break_chinese_words: bool,
803        #[cfg(feature = "jieba")]
804        /// Path to custom jieba dictionary
805        jieba_dict: Option<String>,
806    },
807    /// Do not wrap line
808    None,
809}
810
811#[derive(Debug, Serialize, Deserialize)]
812/// Name table cell
813pub struct NameTableCell {
814    #[serde(rename = "JP_Name")]
815    /// Original name
816    pub jp_name: String,
817    #[serde(rename = "CN_Name")]
818    /// Translated name
819    pub cn_name: String,
820    #[serde(rename = "Count")]
821    /// Number of times this name appears in the script
822    pub count: usize,
823}
824
825#[derive(Debug, Serialize, Deserialize)]
826/// Replacement table for string replacements
827pub struct ReplacementTable {
828    #[serde(flatten)]
829    /// Map of original strings to their replacements
830    pub map: HashMap<String, String>,
831}
832
833#[cfg(feature = "image")]
834#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
835/// Image color type
836pub enum ImageColorType {
837    /// Grayscale image
838    Grayscale,
839    /// RGB image
840    Rgb,
841    /// RGBA image
842    Rgba,
843    /// BGR image
844    Bgr,
845    /// BGRA image
846    Bgra,
847}
848
849#[cfg(feature = "image")]
850impl ImageColorType {
851    /// Returns the number of bytes per pixel for the color type and depth.
852    pub fn bpp(&self, depth: u8) -> u16 {
853        match self {
854            ImageColorType::Grayscale => depth as u16,
855            ImageColorType::Rgb => depth as u16 * 3,
856            ImageColorType::Rgba => depth as u16 * 4,
857            ImageColorType::Bgr => depth as u16 * 3,
858            ImageColorType::Bgra => depth as u16 * 4,
859        }
860    }
861}
862
863#[cfg(feature = "image")]
864#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
865/// Image output type
866pub enum ImageOutputType {
867    /// PNG image
868    Png,
869    #[cfg(feature = "image-jpg")]
870    /// JPEG image
871    Jpg,
872    #[cfg(feature = "image-webp")]
873    /// WebP image
874    Webp,
875    #[cfg(feature = "image-jxl")]
876    /// JPEG XL image
877    Jxl,
878}
879
880#[cfg(feature = "image")]
881impl TryFrom<&str> for ImageOutputType {
882    type Error = anyhow::Error;
883
884    /// Try to convert a extension string to an `ImageOutputType`.
885    /// Extensions are case-insensitive.
886    fn try_from(value: &str) -> Result<Self, Self::Error> {
887        match value.to_ascii_lowercase().as_str() {
888            "png" => Ok(ImageOutputType::Png),
889            #[cfg(feature = "image-jpg")]
890            "jpg" => Ok(ImageOutputType::Jpg),
891            #[cfg(feature = "image-jpg")]
892            "jpeg" => Ok(ImageOutputType::Jpg),
893            #[cfg(feature = "image-webp")]
894            "webp" => Ok(ImageOutputType::Webp),
895            #[cfg(feature = "image-jxl")]
896            "jxl" => Ok(ImageOutputType::Jxl),
897            _ => Err(anyhow::anyhow!("Unsupported image output type: {}", value)),
898        }
899    }
900}
901
902#[cfg(feature = "image")]
903impl TryFrom<&std::path::Path> for ImageOutputType {
904    type Error = anyhow::Error;
905
906    fn try_from(value: &std::path::Path) -> Result<Self, Self::Error> {
907        if let Some(ext) = value.extension() {
908            Self::try_from(ext.to_string_lossy().as_ref())
909        } else {
910            Err(anyhow::anyhow!("No extension found in path"))
911        }
912    }
913}
914
915#[cfg(feature = "image")]
916impl AsRef<str> for ImageOutputType {
917    /// Returns the extension for the image output type.
918    fn as_ref(&self) -> &str {
919        match self {
920            ImageOutputType::Png => "png",
921            #[cfg(feature = "image-jpg")]
922            ImageOutputType::Jpg => "jpg",
923            #[cfg(feature = "image-webp")]
924            ImageOutputType::Webp => "webp",
925            #[cfg(feature = "image-jxl")]
926            ImageOutputType::Jxl => "jxl",
927        }
928    }
929}
930
931#[cfg(feature = "image")]
932#[derive(Clone, Debug)]
933/// Image data
934pub struct ImageData {
935    /// Image width in pixels
936    pub width: u32,
937    /// Image height in pixels
938    pub height: u32,
939    /// Image color type
940    pub color_type: ImageColorType,
941    /// Image depth in bits per channel
942    pub depth: u8,
943    /// Image data
944    pub data: Vec<u8>,
945}
946
947#[cfg(feature = "image")]
948#[derive(Clone, Debug)]
949/// Image data with name
950pub struct ImageDataWithName {
951    /// Image name
952    pub name: String,
953    /// Image data
954    pub data: ImageData,
955}
956
957#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
958/// BOM type
959pub enum BomType {
960    /// No BOM
961    None,
962    /// UTF-8 BOM
963    Utf8,
964    /// UTF-16 Little Endian BOM
965    Utf16LE,
966    /// UTF-16 Big Endian BOM
967    Utf16BE,
968}
969
970impl BomType {
971    /// Returns the byte sequence for the BOM type.
972    pub fn as_bytes(&self) -> &'static [u8] {
973        match self {
974            BomType::None => &[],
975            BomType::Utf8 => b"\xEF\xBB\xBF",
976            BomType::Utf16LE => b"\xFF\xFE",
977            BomType::Utf16BE => b"\xFE\xFF",
978        }
979    }
980}
981
982#[cfg(feature = "image")]
983#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
984/// PNG compression level
985pub enum PngCompressionLevel {
986    #[value(alias = "n")]
987    /// No compression whatsoever. Fastest, but results in large files.
988    NoCompression,
989    #[value(alias = "d")]
990    /// Default level (usually balanced)
991    Default,
992    /// Extremely fast but light compression.
993    ///
994    /// Note: When used in streaming mode, this compression level can actually result in files
995    /// *larger* than would be produced by `NoCompression` on incompressible data because
996    /// it doesn't do any buffering of the output stream to detect whether the data is being compressed or not.
997    Fastest,
998    #[value(alias = "f")]
999    /// Fast minimal compression
1000    Fast,
1001    #[value(alias = "b")]
1002    /// Higher compression level. Same as high
1003    Best,
1004    #[value(alias = "h")]
1005    /// Spend much more time to produce a slightly smaller file than with `Balanced`.
1006    High,
1007}
1008
1009#[cfg(feature = "image")]
1010impl Default for PngCompressionLevel {
1011    fn default() -> Self {
1012        PngCompressionLevel::Default
1013    }
1014}
1015
1016#[cfg(feature = "image")]
1017impl PngCompressionLevel {
1018    /// Converts the [PngCompressionLevel] to a [png::Compression] enum.
1019    pub fn to_compression(&self) -> png::Compression {
1020        match self {
1021            PngCompressionLevel::NoCompression => png::Compression::NoCompression,
1022            PngCompressionLevel::Fastest => png::Compression::Fastest,
1023            PngCompressionLevel::Default => png::Compression::Balanced,
1024            PngCompressionLevel::Fast => png::Compression::Fast,
1025            PngCompressionLevel::Best => png::Compression::High,
1026            PngCompressionLevel::High => png::Compression::High,
1027        }
1028    }
1029}
1030
1031#[cfg(feature = "lossless-audio")]
1032#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
1033/// Lossless audio format
1034pub enum LosslessAudioFormat {
1035    /// Wav
1036    Wav,
1037    #[cfg(feature = "audio-flac")]
1038    /// FLAC Format
1039    Flac,
1040}
1041
1042#[cfg(feature = "lossless-audio")]
1043impl Default for LosslessAudioFormat {
1044    fn default() -> Self {
1045        LosslessAudioFormat::Wav
1046    }
1047}
1048
1049#[cfg(feature = "lossless-audio")]
1050impl TryFrom<&str> for LosslessAudioFormat {
1051    type Error = anyhow::Error;
1052    /// Try to convert a extension string to an `LosslessAudioFormat`.
1053    /// Extensions are case-insensitive.
1054    fn try_from(value: &str) -> Result<Self, Self::Error> {
1055        match value.to_ascii_lowercase().as_str() {
1056            "wav" => Ok(LosslessAudioFormat::Wav),
1057            #[cfg(feature = "audio-flac")]
1058            "flac" => Ok(LosslessAudioFormat::Flac),
1059            _ => Err(anyhow::anyhow!(
1060                "Unsupported lossless audio format: {}",
1061                value
1062            )),
1063        }
1064    }
1065}
1066
1067#[cfg(feature = "lossless-audio")]
1068impl TryFrom<&std::path::Path> for LosslessAudioFormat {
1069    type Error = anyhow::Error;
1070
1071    fn try_from(value: &std::path::Path) -> Result<Self, Self::Error> {
1072        if let Some(ext) = value.extension() {
1073            Self::try_from(ext.to_string_lossy().as_ref())
1074        } else {
1075            Err(anyhow::anyhow!("No extension found in path"))
1076        }
1077    }
1078}
1079
1080#[cfg(feature = "lossless-audio")]
1081impl AsRef<str> for LosslessAudioFormat {
1082    /// Returns the extension for the lossless audio format.
1083    fn as_ref(&self) -> &str {
1084        match self {
1085            LosslessAudioFormat::Wav => "wav",
1086            #[cfg(feature = "audio-flac")]
1087            LosslessAudioFormat::Flac => "flac",
1088        }
1089    }
1090}
1091
1092#[allow(unused)]
1093pub(crate) fn get_default_threads() -> usize {
1094    num_cpus::get().max(2) / 2
1095}