pub mod args; pub mod ext; pub mod format; pub mod output_scripts; pub mod scripts; pub mod types; pub mod utils; use scripts::base::ArchiveContent; fn get_encoding( arg: &args::Arg, builder: &Box, ) -> types::Encoding { match &arg.encoding { Some(enc) => { return match enc { &types::TextEncoding::Default => builder.default_encoding(), &types::TextEncoding::Auto => types::Encoding::Auto, &types::TextEncoding::Cp932 => types::Encoding::Cp932, &types::TextEncoding::Utf8 => types::Encoding::Utf8, &types::TextEncoding::Gb2312 => types::Encoding::Gb2312, }; } None => {} } #[cfg(windows)] match &arg.code_page { Some(code_page) => { return types::Encoding::CodePage(*code_page); } None => {} } builder.default_encoding() } fn get_archived_encoding( arg: &args::Arg, builder: &Box, encoding: types::Encoding, ) -> types::Encoding { match &arg.archive_encoding { Some(enc) => { return match enc { &types::TextEncoding::Default => builder .default_archive_encoding() .unwrap_or_else(|| builder.default_encoding()), &types::TextEncoding::Auto => types::Encoding::Auto, &types::TextEncoding::Cp932 => types::Encoding::Cp932, &types::TextEncoding::Utf8 => types::Encoding::Utf8, &types::TextEncoding::Gb2312 => types::Encoding::Gb2312, }; } None => {} } #[cfg(windows)] match &arg.archive_code_page { Some(code_page) => { return types::Encoding::CodePage(*code_page); } None => {} } builder.default_archive_encoding().unwrap_or(encoding) } fn get_output_encoding(arg: &args::Arg) -> types::Encoding { match &arg.output_encoding { Some(enc) => { return match enc { &types::TextEncoding::Default => types::Encoding::Utf8, &types::TextEncoding::Auto => types::Encoding::Utf8, &types::TextEncoding::Cp932 => types::Encoding::Cp932, &types::TextEncoding::Utf8 => types::Encoding::Utf8, &types::TextEncoding::Gb2312 => types::Encoding::Gb2312, }; } None => {} } #[cfg(windows)] match &arg.output_code_page { Some(code_page) => { return types::Encoding::CodePage(*code_page); } None => {} } types::Encoding::Utf8 } fn get_patched_encoding( arg: &args::ImportArgs, builder: &Box, ) -> types::Encoding { match &arg.patched_encoding { Some(enc) => { return match enc { &types::TextEncoding::Default => builder.default_patched_encoding(), &types::TextEncoding::Auto => types::Encoding::Utf8, &types::TextEncoding::Cp932 => types::Encoding::Cp932, &types::TextEncoding::Utf8 => types::Encoding::Utf8, &types::TextEncoding::Gb2312 => types::Encoding::Gb2312, }; } None => {} } #[cfg(windows)] match &arg.patched_code_page { Some(code_page) => { return types::Encoding::CodePage(*code_page); } None => {} } builder.default_patched_encoding() } fn get_patched_archive_encoding( arg: &args::ImportArgs, builder: &Box, encoding: types::Encoding, ) -> types::Encoding { match &arg.patched_archive_encoding { Some(enc) => { return match enc { &types::TextEncoding::Default => { builder.default_archive_encoding().unwrap_or(encoding) } &types::TextEncoding::Auto => types::Encoding::Utf8, &types::TextEncoding::Cp932 => types::Encoding::Cp932, &types::TextEncoding::Utf8 => types::Encoding::Utf8, &types::TextEncoding::Gb2312 => types::Encoding::Gb2312, }; } None => {} } #[cfg(windows)] match &arg.patched_archive_code_page { Some(code_page) => { return types::Encoding::CodePage(*code_page); } None => {} } builder.default_archive_encoding().unwrap_or(encoding) } pub fn parse_script( filename: &str, arg: &args::Arg, config: &types::ExtraConfig, ) -> anyhow::Result<( Box, &'static Box, )> { match &arg.script_type { Some(typ) => { for builder in scripts::BUILDER.iter() { if typ == builder.script_type() { let encoding = get_encoding(arg, builder); let archive_encoding = get_archived_encoding(arg, builder, encoding); return Ok(( builder.build_script_from_file( filename, encoding, archive_encoding, config, )?, builder, )); } } } _ => {} } let mut exts_builder = Vec::new(); for builder in scripts::BUILDER.iter() { let exts = builder.extensions(); for ext in exts { if filename.to_lowercase().ends_with(ext) { exts_builder.push(builder); break; } } } let exts_builder = if exts_builder.is_empty() { scripts::BUILDER.iter().collect::>() } else { exts_builder }; if exts_builder.len() == 1 { let builder = exts_builder.first().unwrap(); let encoding = get_encoding(arg, builder); let archive_encoding = get_archived_encoding(arg, builder, encoding); return Ok(( builder.build_script_from_file(filename, encoding, archive_encoding, config)?, builder, )); } let mut buf = [0u8; 1024]; let mut size = 0; if filename != "-" { let mut f = std::fs::File::open(filename)?; size = std::io::Read::read(&mut f, &mut buf)?; } let mut scores = Vec::new(); for builder in exts_builder.iter() { if let Some(score) = builder.is_this_format(filename, &buf, size) { scores.push((score, builder)); } } if scores.is_empty() { return Err(anyhow::anyhow!("Unsupported script type")); } let max_score = scores.iter().map(|s| s.0).max().unwrap(); let mut best_builders = Vec::new(); for (score, builder) in scores.iter() { if *score == max_score { best_builders.push(builder); } } if best_builders.len() == 1 { let builder = best_builders.first().unwrap(); let encoding = get_encoding(arg, builder); let archive_encoding = get_archived_encoding(arg, builder, encoding); return Ok(( builder.build_script_from_file(filename, encoding, archive_encoding, config)?, builder, )); } if best_builders.len() > 1 { eprintln!( "Multiple script types found for {}: {:?}", filename, best_builders ); return Err(anyhow::anyhow!("Multiple script types found")); } Err(anyhow::anyhow!("Unsupported script type")) } pub fn parse_script_from_archive( file: &mut Box, arg: &args::Arg, config: &types::ExtraConfig, ) -> anyhow::Result<( Box, &'static Box, )> { match file.script_type() { Some(typ) => { for builder in scripts::BUILDER.iter() { if typ == builder.script_type() { let encoding = get_encoding(arg, builder); let archive_encoding = get_archived_encoding(arg, builder, encoding); return Ok(( builder.build_script( file.data()?, file.name(), encoding, archive_encoding, config, )?, builder, )); } } } _ => {} } let mut exts_builder = Vec::new(); for builder in scripts::BUILDER.iter() { let exts = builder.extensions(); for ext in exts { if file.name().to_lowercase().ends_with(ext) { exts_builder.push(builder); break; } } } let exts_builder = if exts_builder.is_empty() { scripts::BUILDER.iter().collect::>() } else { exts_builder }; if exts_builder.len() == 1 { let builder = exts_builder.first().unwrap(); let encoding = get_encoding(arg, builder); let archive_encoding = get_archived_encoding(arg, builder, encoding); return Ok(( builder.build_script( file.data()?, file.name(), encoding, archive_encoding, config, )?, builder, )); } let buf = file.data()?; let mut scores = Vec::new(); for builder in exts_builder.iter() { if let Some(score) = builder.is_this_format(file.name(), buf.as_slice(), buf.len()) { scores.push((score, builder)); } } if scores.is_empty() { return Err(anyhow::anyhow!("Unsupported script type")); } let max_score = scores.iter().map(|s| s.0).max().unwrap(); let mut best_builders = Vec::new(); for (score, builder) in scores.iter() { if *score == max_score { best_builders.push(builder); } } if best_builders.len() == 1 { let builder = best_builders.first().unwrap(); let encoding = get_encoding(arg, builder); let archive_encoding = get_archived_encoding(arg, builder, encoding); return Ok(( builder.build_script(buf, file.name(), encoding, archive_encoding, config)?, builder, )); } if best_builders.len() > 1 { eprintln!( "Multiple script types found for {}: {:?}", file.name(), best_builders ); return Err(anyhow::anyhow!("Multiple script types found")); } Err(anyhow::anyhow!("Unsupported script type")) } pub fn export_script( filename: &str, arg: &args::Arg, config: &types::ExtraConfig, output: &Option, is_dir: bool, ) -> anyhow::Result { eprintln!("Exporting {}", filename); let mut script = parse_script(filename, arg, config)?.0; if script.is_archive() { let odir = match output.as_ref() { Some(output) => { let mut pb = std::path::PathBuf::from(output); let filename = std::path::PathBuf::from(filename); if is_dir { if let Some(fname) = filename.file_name() { pb.push(fname); } } pb.to_string_lossy().into_owned() } None => { let mut pb = std::path::PathBuf::from(filename); pb.set_extension(""); pb.to_string_lossy().into_owned() } }; if !std::fs::exists(&odir)? { std::fs::create_dir_all(&odir)?; } for f in script.iter_archive_mut()? { let mut f = f?; if f.is_script() { let (script_file, _) = parse_script_from_archive(&mut f, arg, config)?; #[cfg(feature = "image")] if script_file.is_image() { let img_data = match script_file.export_image() { Ok(data) => data, Err(e) => { eprintln!("Error exporting image: {}", e); COUNTER.inc_error(); if arg.backtrace { eprintln!("Backtrace: {}", e.backtrace()); } continue; } }; let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png); let mut out_path = std::path::PathBuf::from(&odir).join(f.name()); out_path.set_extension(out_type.as_ref()); match utils::files::make_sure_dir_exists(&out_path) { Ok(_) => {} Err(e) => { eprintln!( "Error creating parent directory for {}: {}", out_path.display(), e ); COUNTER.inc_error(); continue; } } match utils::img::encode_img(img_data, out_type, &out_path.to_string_lossy()) { Ok(_) => {} Err(e) => { eprintln!("Error encoding image: {}", e); COUNTER.inc_error(); continue; } } COUNTER.inc(types::ScriptResult::Ok); continue; } let mut of = match &arg.output_type { Some(t) => t.clone(), None => script_file.default_output_script_type(), }; if !script_file.is_output_supported(of) { of = script_file.default_output_script_type(); } let mes = if of.is_custom() { Vec::new() } else { match script_file.extract_messages() { Ok(mes) => mes, Err(e) => { eprintln!("Error extracting messages from {}: {}", f.name(), e); COUNTER.inc_error(); if arg.backtrace { eprintln!("Backtrace: {}", e.backtrace()); } continue; } } }; if !of.is_custom() && mes.is_empty() { eprintln!("No messages found in {}", f.name()); COUNTER.inc(types::ScriptResult::Ignored); continue; } let mut out_path = std::path::PathBuf::from(&odir).join(f.name()); out_path.set_extension(if of.is_custom() { script_file.custom_output_extension() } else { of.as_ref() }); match utils::files::make_sure_dir_exists(&out_path) { Ok(_) => {} Err(e) => { eprintln!( "Error creating parent directory for {}: {}", out_path.display(), e ); COUNTER.inc_error(); continue; } } match of { types::OutputScriptType::Json => { let enc = get_output_encoding(arg); let s = match serde_json::to_string_pretty(&mes) { Ok(s) => s, Err(e) => { eprintln!("Error serializing messages to JSON: {}", e); COUNTER.inc_error(); continue; } }; let b = match utils::encoding::encode_string(enc, &s, false) { Ok(b) => b, Err(e) => { eprintln!("Error encoding string: {}", e); COUNTER.inc_error(); continue; } }; let mut f = match utils::files::write_file(&out_path) { Ok(f) => f, Err(e) => { eprintln!("Error writing file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } }; match f.write_all(&b) { Ok(_) => {} Err(e) => { eprintln!("Error writing to file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } } } types::OutputScriptType::M3t => { let enc = get_output_encoding(arg); let s = output_scripts::m3t::M3tDumper::dump(&mes); let b = match utils::encoding::encode_string(enc, &s, false) { Ok(b) => b, Err(e) => { eprintln!("Error encoding string: {}", e); COUNTER.inc_error(); continue; } }; let mut f = match utils::files::write_file(&out_path) { Ok(f) => f, Err(e) => { eprintln!("Error writing file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } }; match f.write_all(&b) { Ok(_) => {} Err(e) => { eprintln!("Error writing to file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } } } types::OutputScriptType::Custom => { let enc = get_output_encoding(arg); if let Err(e) = script_file.custom_export(&out_path, enc) { eprintln!("Error exporting custom script: {}", e); COUNTER.inc_error(); continue; } } } } else { let out_path = std::path::PathBuf::from(&odir).join(f.name()); match utils::files::make_sure_dir_exists(&out_path) { Ok(_) => {} Err(e) => { eprintln!( "Error creating parent directory for {}: {}", out_path.display(), e ); COUNTER.inc_error(); continue; } } match utils::files::write_file(&out_path) { Ok(mut fi) => match std::io::copy(&mut f, &mut fi) { Ok(_) => {} Err(e) => { eprintln!("Error writing to file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } }, Err(e) => { eprintln!("Error writing file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } } } COUNTER.inc(types::ScriptResult::Ok); } return Ok(types::ScriptResult::Ok); } #[cfg(feature = "image")] if script.is_image() { let img_data = script.export_image()?; let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png); let f = if filename == "-" { String::from("-") } else { match output.as_ref() { Some(output) => { if is_dir { let f = std::path::PathBuf::from(filename); let mut pb = std::path::PathBuf::from(output); if let Some(fname) = f.file_name() { pb.push(fname); } pb.set_extension(out_type.as_ref()); pb.to_string_lossy().into_owned() } else { output.clone() } } None => { let mut pb = std::path::PathBuf::from(filename); pb.set_extension(out_type.as_ref()); pb.to_string_lossy().into_owned() } } }; utils::img::encode_img(img_data, out_type, &f)?; return Ok(types::ScriptResult::Ok); } let mut of = match &arg.output_type { Some(t) => t.clone(), None => script.default_output_script_type(), }; if !script.is_output_supported(of) { of = script.default_output_script_type(); } let mes = if of.is_custom() { Vec::new() } else { script.extract_messages()? }; if !of.is_custom() && mes.is_empty() { eprintln!("No messages found"); return Ok(types::ScriptResult::Ignored); } let ext = if of.is_custom() { script.custom_output_extension() } else { of.as_ref() }; let f = if filename == "-" { String::from("-") } else { match output.as_ref() { Some(output) => { if is_dir { let f = std::path::PathBuf::from(filename); let mut pb = std::path::PathBuf::from(output); if let Some(fname) = f.file_name() { pb.push(fname); } pb.set_extension(ext); pb.to_string_lossy().into_owned() } else { output.clone() } } None => { let mut pb = std::path::PathBuf::from(filename); pb.set_extension(ext); pb.to_string_lossy().into_owned() } } }; match of { types::OutputScriptType::Json => { let enc = get_output_encoding(arg); let s = serde_json::to_string_pretty(&mes)?; let b = utils::encoding::encode_string(enc, &s, false)?; let mut f = utils::files::write_file(&f)?; f.write_all(&b)?; } types::OutputScriptType::M3t => { let enc = get_output_encoding(arg); let s = output_scripts::m3t::M3tDumper::dump(&mes); let b = utils::encoding::encode_string(enc, &s, false)?; let mut f = utils::files::write_file(&f)?; f.write_all(&b)?; } types::OutputScriptType::Custom => { let enc = get_output_encoding(arg); script.custom_export(f.as_ref(), enc)?; } } Ok(types::ScriptResult::Ok) } pub fn import_script( filename: &str, arg: &args::Arg, config: &types::ExtraConfig, imp_cfg: &args::ImportArgs, is_dir: bool, name_csv: Option<&std::collections::HashMap>, repl: Option<&types::ReplacementTable>, ) -> anyhow::Result { eprintln!("Importing {}", filename); let (mut script, builder) = parse_script(filename, arg, config)?; if script.is_archive() { let odir = { let mut pb = std::path::PathBuf::from(&imp_cfg.output); let filename = std::path::PathBuf::from(filename); if is_dir { if let Some(fname) = filename.file_name() { pb.push(fname); } } pb.to_string_lossy().into_owned() }; let files: Vec<_> = script.iter_archive()?.collect(); let files = files.into_iter().filter_map(|f| f.ok()).collect::>(); let patched_f = if is_dir { let f = std::path::PathBuf::from(filename); let mut pb = std::path::PathBuf::from(&imp_cfg.patched); if let Some(fname) = f.file_name() { pb.push(fname); } pb.set_extension(builder.extensions().first().unwrap_or(&"")); pb.to_string_lossy().into_owned() } else { imp_cfg.patched.clone() }; let files: Vec<_> = files.iter().map(|s| s.as_str()).collect(); let pencoding = get_patched_encoding(imp_cfg, builder); let enc = get_patched_archive_encoding(imp_cfg, builder, pencoding); let mut arch = builder.create_archive(&patched_f, &files, enc, config)?; for f in script.iter_archive_mut()? { let mut f = f?; let mut writer = arch.new_file(f.name())?; if f.is_script() { let (script_file, _) = parse_script_from_archive(&mut f, arg, config)?; let mut of = match &arg.output_type { Some(t) => t.clone(), None => script_file.default_output_script_type(), }; if !script_file.is_output_supported(of) { of = script_file.default_output_script_type(); } let mut out_path = std::path::PathBuf::from(&odir).join(f.name()); let ext = if of.is_custom() { script_file.custom_output_extension() } else { of.as_ref() }; out_path.set_extension(ext); if !out_path.exists() { out_path = std::path::PathBuf::from(&odir).join(f.name()); if !out_path.exists() { if imp_cfg.warn_when_output_file_not_found { eprintln!( "Warning: File {} does not exist, using file from original archive.", out_path.display() ); COUNTER.inc_warning(); } match std::io::copy(&mut f, &mut writer) { Ok(_) => {} Err(e) => { eprintln!("Error writing to file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } } COUNTER.inc(types::ScriptResult::Ok); continue; } else { let file = match std::fs::File::open(&out_path) { Ok(f) => f, Err(e) => { eprintln!("Error opening file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } }; let mut f = std::io::BufReader::new(file); match std::io::copy(&mut f, &mut writer) { Ok(_) => {} Err(e) => { eprintln!("Error writing to file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } } COUNTER.inc(types::ScriptResult::Ok); continue; } } let mut mes = match of { types::OutputScriptType::Json => { let enc = get_output_encoding(arg); let b = match utils::files::read_file(&out_path) { Ok(b) => b, Err(e) => { eprintln!("Error reading file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } }; let s = match utils::encoding::decode_to_string(enc, &b) { Ok(s) => s, Err(e) => { eprintln!("Error decoding string: {}", e); COUNTER.inc_error(); continue; } }; match serde_json::from_str::>(&s) { Ok(mes) => mes, Err(e) => { eprintln!("Error parsing JSON: {}", e); COUNTER.inc_error(); continue; } } } types::OutputScriptType::M3t => { let enc = get_output_encoding(arg); let b = match utils::files::read_file(&out_path) { Ok(b) => b, Err(e) => { eprintln!("Error reading file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } }; let s = match utils::encoding::decode_to_string(enc, &b) { Ok(s) => s, Err(e) => { eprintln!("Error decoding string: {}", e); COUNTER.inc_error(); continue; } }; let mut parser = output_scripts::m3t::M3tParser::new(&s); match parser.parse() { Ok(mes) => mes, Err(e) => { eprintln!("Error parsing M3T: {}", e); COUNTER.inc_error(); continue; } } } types::OutputScriptType::Custom => { Vec::new() // Custom scripts handle their own messages } }; if !of.is_custom() && mes.is_empty() { eprintln!("No messages found in {}", f.name()); COUNTER.inc(types::ScriptResult::Ignored); continue; } let encoding = get_patched_encoding(imp_cfg, builder); if of.is_custom() { let enc = get_output_encoding(arg); match script_file.custom_import( &out_path.to_string_lossy(), writer, encoding, enc, ) { Ok(_) => {} Err(e) => { eprintln!("Error importing custom script: {}", e); COUNTER.inc_error(); continue; } } COUNTER.inc(types::ScriptResult::Ok); continue; } let fmt = match imp_cfg.patched_format { Some(fmt) => match fmt { types::FormatType::Fixed => types::FormatOptions::Fixed { length: imp_cfg.patched_fixed_length.unwrap_or(32), keep_original: imp_cfg.patched_keep_original, }, types::FormatType::None => types::FormatOptions::None, }, None => script_file.default_format_type(), }; match name_csv { Some(name_table) => { utils::name_replacement::replace_message(&mut mes, name_table); } None => {} } format::fmt_message(&mut mes, fmt, *builder.script_type()); if let Err(e) = script_file.import_messages(mes, writer, encoding, repl) { eprintln!("Error importing messages: {}", e); COUNTER.inc_error(); continue; } } else { let out_path = std::path::PathBuf::from(&odir).join(f.name()); if out_path.is_file() { let f = match std::fs::File::open(&out_path) { Ok(f) => f, Err(e) => { eprintln!("Error opening file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } }; let mut f = std::io::BufReader::new(f); match std::io::copy(&mut f, &mut writer) { Ok(_) => {} Err(e) => { eprintln!("Error writing to file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } } } else { eprintln!( "Warning: File {} does not exist, use file from original archive.", out_path.display() ); COUNTER.inc_warning(); match std::io::copy(&mut f, &mut writer) { Ok(_) => {} Err(e) => { eprintln!("Error writing to file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } } } } COUNTER.inc(types::ScriptResult::Ok); } arch.write_header()?; return Ok(types::ScriptResult::Ok); } #[cfg(feature = "image")] if script.is_image() { let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png); let out_f = if is_dir { let f = std::path::PathBuf::from(filename); let mut pb = std::path::PathBuf::from(&imp_cfg.output); if let Some(fname) = f.file_name() { pb.push(fname); } pb.set_extension(out_type.as_ref()); pb.to_string_lossy().into_owned() } else { imp_cfg.output.clone() }; let data = utils::img::decode_img(out_type, &out_f)?; let patched_f = if is_dir { let f = std::path::PathBuf::from(filename); let mut pb = std::path::PathBuf::from(&imp_cfg.patched); if let Some(fname) = f.file_name() { pb.push(fname); } pb.set_extension(builder.extensions().first().unwrap_or(&"")); pb.to_string_lossy().into_owned() } else { imp_cfg.patched.clone() }; script.import_image_filename(data, &patched_f)?; return Ok(types::ScriptResult::Ok); } let mut of = match &arg.output_type { Some(t) => t.clone(), None => script.default_output_script_type(), }; if !script.is_output_supported(of) { of = script.default_output_script_type(); } let out_f = if is_dir { let f = std::path::PathBuf::from(filename); let mut pb = std::path::PathBuf::from(&imp_cfg.output); if let Some(fname) = f.file_name() { pb.push(fname); } pb.set_extension(of.as_ref()); pb.to_string_lossy().into_owned() } else { imp_cfg.output.clone() }; if !std::fs::exists(&out_f).unwrap_or(false) { eprintln!("Output file does not exist"); return Ok(types::ScriptResult::Ignored); } let mut mes = match of { types::OutputScriptType::Json => { let enc = get_output_encoding(arg); let b = utils::files::read_file(&out_f)?; let s = utils::encoding::decode_to_string(enc, &b)?; serde_json::from_str::>(&s)? } types::OutputScriptType::M3t => { let enc = get_output_encoding(arg); let b = utils::files::read_file(&out_f)?; let s = utils::encoding::decode_to_string(enc, &b)?; let mut parser = output_scripts::m3t::M3tParser::new(&s); parser.parse()? } types::OutputScriptType::Custom => { Vec::new() // Custom scripts handle their own messages } }; if !of.is_custom() && mes.is_empty() { eprintln!("No messages found"); return Ok(types::ScriptResult::Ignored); } let encoding = get_patched_encoding(imp_cfg, builder); let patched_f = if is_dir { let f = std::path::PathBuf::from(filename); let mut pb = std::path::PathBuf::from(&imp_cfg.patched); if let Some(fname) = f.file_name() { pb.push(fname); } pb.set_extension(builder.extensions().first().unwrap_or(&"")); pb.to_string_lossy().into_owned() } else { imp_cfg.patched.clone() }; if of.is_custom() { let enc = get_output_encoding(arg); script.custom_import_filename(&out_f, &patched_f, encoding, enc)?; return Ok(types::ScriptResult::Ok); } let fmt = match imp_cfg.patched_format { Some(fmt) => match fmt { types::FormatType::Fixed => types::FormatOptions::Fixed { length: imp_cfg.patched_fixed_length.unwrap_or(32), keep_original: imp_cfg.patched_keep_original, }, types::FormatType::None => types::FormatOptions::None, }, None => script.default_format_type(), }; match name_csv { Some(name_table) => { utils::name_replacement::replace_message(&mut mes, name_table); } None => {} } format::fmt_message(&mut mes, fmt, *builder.script_type()); script.import_messages_filename(mes, &patched_f, encoding, repl)?; Ok(types::ScriptResult::Ok) } pub fn pack_archive( input: &str, output: Option<&str>, arg: &args::Arg, config: &types::ExtraConfig, ) -> anyhow::Result<()> { let typ = match &arg.script_type { Some(t) => t, None => { return Err(anyhow::anyhow!("No script type specified")); } }; let (files, isdir) = utils::files::collect_files(input, arg.recursive, true) .map_err(|e| anyhow::anyhow!("Error collecting files: {}", e))?; if !isdir { return Err(anyhow::anyhow!("Input must be a directory for packing")); } let re_files: Vec = files .iter() .filter_map(|f| { std::path::PathBuf::from(f) .strip_prefix(input) .ok() .and_then(|p| { p.to_str() .map(|s| s.replace("\\", "/").trim_start_matches("/").to_owned()) }) }) .collect(); let reff = re_files.iter().map(|s| s.as_str()).collect::>(); let builder = scripts::BUILDER .iter() .find(|b| b.script_type() == typ) .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?; let output = match output { Some(output) => output.to_string(), None => { let mut pb = std::path::PathBuf::from(input); let ext = builder.extensions().first().unwrap_or(&"unk"); pb.set_extension(ext); if pb.to_string_lossy() == input { pb.set_extension(format!("{}.{}", ext, ext)); } pb.to_string_lossy().into_owned() } }; let mut archive = builder.create_archive(&output, &reff, get_output_encoding(arg), config)?; for (file, name) in files.iter().zip(reff) { let mut f = match std::fs::File::open(file) { Ok(f) => f, Err(e) => { eprintln!("Error opening file {}: {}", file, e); COUNTER.inc_error(); continue; } }; let mut wf = match archive.new_file(name) { Ok(f) => f, Err(e) => { eprintln!("Error creating file {} in archive: {}", name, e); COUNTER.inc_error(); continue; } }; match std::io::copy(&mut f, &mut wf) { Ok(_) => { COUNTER.inc(types::ScriptResult::Ok); } Err(e) => { eprintln!("Error writing to file {} in archive: {}", name, e); COUNTER.inc_error(); continue; } } } archive.write_header()?; Ok(()) } pub fn unpack_archive( filename: &str, arg: &args::Arg, config: &types::ExtraConfig, output: &Option, is_dir: bool, ) -> anyhow::Result { eprintln!("Unpacking {}", filename); let mut script = parse_script(filename, arg, config)?.0; if !script.is_archive() { return Ok(types::ScriptResult::Ignored); } let odir = match output.as_ref() { Some(output) => { let mut pb = std::path::PathBuf::from(output); let filename = std::path::PathBuf::from(filename); if is_dir { if let Some(fname) = filename.file_name() { pb.push(fname); } } pb.set_extension(""); pb.to_string_lossy().into_owned() } None => { let mut pb = std::path::PathBuf::from(filename); pb.set_extension(""); pb.to_string_lossy().into_owned() } }; if !std::fs::exists(&odir)? { std::fs::create_dir_all(&odir)?; } for f in script.iter_archive_mut()? { let mut f = f?; let out_path = std::path::PathBuf::from(&odir).join(f.name()); match utils::files::make_sure_dir_exists(&out_path) { Ok(_) => {} Err(e) => { eprintln!( "Error creating parent directory for {}: {}", out_path.display(), e ); COUNTER.inc_error(); continue; } } match utils::files::write_file(&out_path) { Ok(mut fi) => match std::io::copy(&mut f, &mut fi) { Ok(_) => {} Err(e) => { eprintln!("Error writing to file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } }, Err(e) => { eprintln!("Error writing file {}: {}", out_path.display(), e); COUNTER.inc_error(); continue; } } COUNTER.inc(types::ScriptResult::Ok); } Ok(types::ScriptResult::Ok) } pub fn create_file( input: &str, output: Option<&str>, arg: &args::Arg, _config: &types::ExtraConfig, ) -> anyhow::Result<()> { let typ = match &arg.script_type { Some(t) => t, None => { return Err(anyhow::anyhow!("No script type specified")); } }; let builder = scripts::BUILDER .iter() .find(|b| b.script_type() == typ) .ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?; #[cfg(feature = "image")] if builder.is_image() { if !builder.can_create_image_file() { return Err(anyhow::anyhow!( "Script type {:?} does not support image file creation", typ )); } let data = utils::img::decode_img(arg.image_type.unwrap_or(types::ImageOutputType::Png), input)?; let output = match output { Some(output) => output.to_string(), None => { let mut pb = std::path::PathBuf::from(input); let ext = builder.extensions().first().unwrap_or(&"unk"); pb.set_extension(ext); if pb.to_string_lossy() == input { pb.set_extension(format!("{}.{}", ext, ext)); } pb.to_string_lossy().into_owned() } }; builder.create_image_file_filename(data, &output, _config)?; return Ok(()); } if !builder.can_create_file() { return Err(anyhow::anyhow!( "Script type {:?} does not support file creation", typ )); } let output = match output { Some(output) => output.to_string(), None => { let mut pb = std::path::PathBuf::from(input); let ext = builder.extensions().first().unwrap_or(&"unk"); pb.set_extension(ext); if pb.to_string_lossy() == input { pb.set_extension(format!("{}.{}", ext, ext)); } pb.to_string_lossy().into_owned() } }; builder.create_file_filename( input, &output, get_encoding(arg, builder), get_output_encoding(arg), )?; Ok(()) } lazy_static::lazy_static! { static ref COUNTER: utils::counter::Counter = utils::counter::Counter::new(); } fn main() { let arg = args::parse_args(); if arg.backtrace { unsafe { std::env::set_var("RUST_LIB_BACKTRACE", "1") }; } let cfg = types::ExtraConfig { #[cfg(feature = "circus")] circus_mes_type: arg.circus_mes_type.clone(), #[cfg(feature = "escude-arc")] escude_fake_compress: arg.escude_fake_compress, #[cfg(feature = "escude")] escude_enum_scr: arg.escude_enum_scr.clone(), #[cfg(feature = "bgi")] bgi_import_duplicate: arg.bgi_import_duplicate, #[cfg(feature = "bgi")] bgi_disable_append: arg.bgi_disable_append, #[cfg(feature = "image")] image_type: arg.image_type.clone(), #[cfg(all(feature = "bgi-arc", feature = "bgi-img"))] bgi_is_sysgrp_arc: arg.bgi_is_sysgrp_arc.clone(), #[cfg(feature = "bgi-img")] bgi_img_scramble: arg.bgi_img_scramble.clone(), #[cfg(feature = "cat-system-arc")] cat_system_int_encrypt_password: arg.cat_system_int_encrypt_password.clone(), #[cfg(feature = "cat-system-img")] cat_system_image_canvas: arg.cat_system_image_canvas, }; match &arg.command { args::Command::Export { input, output } => { let (scripts, is_dir) = utils::files::collect_files(input, arg.recursive, false).unwrap(); if is_dir { match &output { Some(output) => { let op = std::path::Path::new(output); if op.exists() { if !op.is_dir() { eprintln!("Output path is not a directory"); return; } } else { std::fs::create_dir_all(op).unwrap(); } } None => {} } } for script in scripts.iter() { let re = export_script(&script, &arg, &cfg, output, is_dir); match re { Ok(s) => { COUNTER.inc(s); } Err(e) => { COUNTER.inc_error(); eprintln!("Error exporting {}: {}", script, e); if arg.backtrace { eprintln!("Backtrace: {}", e.backtrace()); } } } } } args::Command::Import(args) => { let name_csv = match &args.name_csv { Some(name_csv) => { let name_table = utils::name_replacement::read_csv(name_csv).unwrap(); Some(name_table) } None => None, }; let repl = match &args.replacement_json { Some(replacement_json) => { let b = utils::files::read_file(replacement_json).unwrap(); let s = String::from_utf8(b).unwrap(); let table = serde_json::from_str::(&s).unwrap(); Some(table) } None => None, }; let (scripts, is_dir) = utils::files::collect_files(&args.input, arg.recursive, false).unwrap(); if is_dir { let pb = std::path::Path::new(&args.patched); if pb.exists() { if !pb.is_dir() { eprintln!("Patched path is not a directory"); return; } } else { std::fs::create_dir_all(pb).unwrap(); } } for script in scripts.iter() { let re = import_script( &script, &arg, &cfg, args, is_dir, name_csv.as_ref(), repl.as_ref(), ); match re { Ok(s) => { COUNTER.inc(s); } Err(e) => { COUNTER.inc_error(); eprintln!("Error exporting {}: {}", script, e); if arg.backtrace { eprintln!("Backtrace: {}", e.backtrace()); } } } } } args::Command::Pack { input, output } => { let re = pack_archive(input, output.as_ref().map(|s| s.as_str()), &arg, &cfg); if let Err(e) = re { COUNTER.inc_error(); eprintln!("Error packing archive: {}", e); } } args::Command::Unpack { input, output } => { let (scripts, is_dir) = utils::files::collect_arc_files(input, arg.recursive).unwrap(); if is_dir { match &output { Some(output) => { let op = std::path::Path::new(output); if op.exists() { if !op.is_dir() { eprintln!("Output path is not a directory"); return; } } else { std::fs::create_dir_all(op).unwrap(); } } None => {} } } for script in scripts.iter() { let re = unpack_archive(&script, &arg, &cfg, output, is_dir); match re { Ok(s) => { COUNTER.inc(s); } Err(e) => { COUNTER.inc_error(); eprintln!("Error unpacking {}: {}", script, e); if arg.backtrace { eprintln!("Backtrace: {}", e.backtrace()); } } } } } args::Command::Create { input, output } => { let re = create_file(input, output.as_ref().map(|s| s.as_str()), &arg, &cfg); if let Err(e) = re { COUNTER.inc_error(); eprintln!("Error creating file: {}", e); if arg.backtrace { eprintln!("Backtrace: {}", e.backtrace()); } } } } eprintln!("{}", std::ops::Deref::deref(&COUNTER)); }