mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-12 07:58:48 +08:00
Add escude LIST file export
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
use crate::utils::encoding::decode_to_string;
|
||||
use crate::{types::Encoding, utils::struct_pack::StructUnpack};
|
||||
use std::{ffi::CString, io::*};
|
||||
|
||||
pub trait Peek {
|
||||
@@ -202,6 +204,20 @@ pub trait Peek {
|
||||
|
||||
fn peek_cstring(&mut self) -> Result<CString>;
|
||||
fn peek_cstring_at(&mut self, offset: usize) -> Result<CString>;
|
||||
|
||||
fn read_struct<T: StructUnpack>(&mut self, big: bool, encoding: Encoding) -> Result<T>;
|
||||
fn read_struct_vec<T: StructUnpack>(
|
||||
&mut self,
|
||||
count: usize,
|
||||
big: bool,
|
||||
encoding: Encoding,
|
||||
) -> Result<Vec<T>> {
|
||||
let mut vec = Vec::with_capacity(count);
|
||||
for _ in 0..count {
|
||||
vec.push(self.read_struct(big, encoding)?);
|
||||
}
|
||||
Ok(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Read + Seek> Peek for T {
|
||||
@@ -265,6 +281,11 @@ impl<T: Read + Seek> Peek for T {
|
||||
self.seek(SeekFrom::Start(current_pos))?;
|
||||
CString::new(buf).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
|
||||
}
|
||||
|
||||
fn read_struct<S: StructUnpack>(&mut self, big: bool, encoding: Encoding) -> Result<S> {
|
||||
S::unpack(self, big, encoding)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadExt {
|
||||
@@ -288,6 +309,9 @@ pub trait ReadExt {
|
||||
fn read_i128_be(&mut self) -> Result<i128>;
|
||||
|
||||
fn read_cstring(&mut self) -> Result<CString>;
|
||||
fn read_fstring(&mut self, len: usize, encoding: Encoding, trim: bool) -> Result<String>;
|
||||
|
||||
fn read_exact_vec(&mut self, len: usize) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
impl<T: Read> ReadExt for T {
|
||||
@@ -394,6 +418,25 @@ impl<T: Read> ReadExt for T {
|
||||
}
|
||||
CString::new(buf).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
|
||||
}
|
||||
fn read_fstring(&mut self, len: usize, encoding: Encoding, trim: bool) -> Result<String> {
|
||||
let mut buf = vec![0u8; len];
|
||||
self.read_exact(&mut buf)?;
|
||||
if trim {
|
||||
let first_zero = buf.iter().position(|&b| b == 0);
|
||||
if let Some(pos) = first_zero {
|
||||
buf.truncate(pos);
|
||||
}
|
||||
}
|
||||
let s = decode_to_string(encoding, &buf)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn read_exact_vec(&mut self, len: usize) -> Result<Vec<u8>> {
|
||||
let mut buf = vec![0u8; len];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WriteExt {
|
||||
|
||||
142
src/main.rs
142
src/main.rs
@@ -233,7 +233,13 @@ pub fn parse_script_from_archive(
|
||||
let encoding = get_encoding(arg, builder);
|
||||
let archive_encoding = get_archived_encoding(arg, builder, encoding);
|
||||
return Ok((
|
||||
builder.build_script(file.data().to_vec(), encoding, archive_encoding, config)?,
|
||||
builder.build_script(
|
||||
file.data().to_vec(),
|
||||
file.name(),
|
||||
encoding,
|
||||
archive_encoding,
|
||||
config,
|
||||
)?,
|
||||
builder,
|
||||
));
|
||||
}
|
||||
@@ -258,7 +264,13 @@ pub fn parse_script_from_archive(
|
||||
let encoding = get_encoding(arg, builder);
|
||||
let archive_encoding = get_archived_encoding(arg, builder, encoding);
|
||||
return Ok((
|
||||
builder.build_script(file.data().to_vec(), encoding, archive_encoding, config)?,
|
||||
builder.build_script(
|
||||
file.data().to_vec(),
|
||||
file.name(),
|
||||
encoding,
|
||||
archive_encoding,
|
||||
config,
|
||||
)?,
|
||||
builder,
|
||||
));
|
||||
}
|
||||
@@ -285,18 +297,12 @@ pub fn export_script(
|
||||
if script.is_archive() {
|
||||
let odir = match output.as_ref() {
|
||||
Some(output) => {
|
||||
if is_dir {
|
||||
let mut pb = std::path::PathBuf::from(output);
|
||||
let filename = std::path::PathBuf::from(filename);
|
||||
if let Some(fname) = filename.file_name() {
|
||||
pb.push(fname);
|
||||
}
|
||||
pb.to_string_lossy().into_owned()
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"A directory is required for archive export"
|
||||
));
|
||||
let mut pb = std::path::PathBuf::from(output);
|
||||
let filename = std::path::PathBuf::from(filename);
|
||||
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);
|
||||
@@ -311,28 +317,51 @@ pub fn export_script(
|
||||
let f = f?;
|
||||
if f.is_script() {
|
||||
let (script_file, _) = parse_script_from_archive(&f, arg, config)?;
|
||||
let mes = 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());
|
||||
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;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if mes.is_empty() {
|
||||
if !of.is_custom() && mes.is_empty() {
|
||||
eprintln!("No messages found in {}", f.name());
|
||||
COUNTER.inc(types::ScriptResult::Ignored);
|
||||
continue;
|
||||
}
|
||||
let of = match &arg.output_type {
|
||||
Some(t) => t.clone(),
|
||||
None => script_file.default_output_script_type(),
|
||||
};
|
||||
let mut out_path = std::path::PathBuf::from(&odir).join(f.name());
|
||||
out_path.set_extension(of.as_ref());
|
||||
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);
|
||||
@@ -397,9 +426,29 @@ pub fn export_script(
|
||||
}
|
||||
}
|
||||
}
|
||||
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 fi.write_all(f.data()) {
|
||||
Ok(_) => {}
|
||||
@@ -420,18 +469,26 @@ pub fn export_script(
|
||||
}
|
||||
return Ok(types::ScriptResult::Ok);
|
||||
}
|
||||
// println!("{:?}", script);
|
||||
let mes = script.extract_messages()?;
|
||||
// for m in mes.iter() {
|
||||
// println!("{:?}", m);
|
||||
// }
|
||||
if mes.is_empty() {
|
||||
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 of = match &arg.output_type {
|
||||
Some(t) => t.clone(),
|
||||
None => script.default_output_script_type(),
|
||||
let ext = if of.is_custom() {
|
||||
script.custom_output_extension()
|
||||
} else {
|
||||
of.as_ref()
|
||||
};
|
||||
let f = if filename == "-" {
|
||||
String::from("-")
|
||||
@@ -444,7 +501,7 @@ pub fn export_script(
|
||||
if let Some(fname) = f.file_name() {
|
||||
pb.push(fname);
|
||||
}
|
||||
pb.set_extension(of.as_ref());
|
||||
pb.set_extension(ext);
|
||||
pb.to_string_lossy().into_owned()
|
||||
} else {
|
||||
output.clone()
|
||||
@@ -452,7 +509,7 @@ pub fn export_script(
|
||||
}
|
||||
None => {
|
||||
let mut pb = std::path::PathBuf::from(filename);
|
||||
pb.set_extension(of.as_ref());
|
||||
pb.set_extension(ext);
|
||||
pb.to_string_lossy().into_owned()
|
||||
}
|
||||
}
|
||||
@@ -472,6 +529,11 @@ pub fn export_script(
|
||||
let mut f = utils::files::write_file(&f)?;
|
||||
f.write_all(&b)?;
|
||||
}
|
||||
types::OutputScriptType::Custom => {
|
||||
let enc = get_output_encoding(arg);
|
||||
println!("f: {}", f);
|
||||
script.custom_export(f.as_ref(), enc)?;
|
||||
}
|
||||
}
|
||||
Ok(types::ScriptResult::Ok)
|
||||
}
|
||||
@@ -520,6 +582,10 @@ pub fn import_script(
|
||||
let mut parser = output_scripts::m3t::M3tParser::new(&s);
|
||||
parser.parse()?
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Unsupported output script type for import: {:?}", of);
|
||||
return Ok(types::ScriptResult::Ignored);
|
||||
}
|
||||
};
|
||||
if mes.is_empty() {
|
||||
eprintln!("No messages found");
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use crate::types::*;
|
||||
use anyhow::Result;
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
pub trait ReadSeek: Read + Seek + std::fmt::Debug {}
|
||||
|
||||
impl<T: Read + Seek + std::fmt::Debug> ReadSeek for T {}
|
||||
|
||||
pub trait ScriptBuilder: std::fmt::Debug {
|
||||
fn default_encoding(&self) -> Encoding;
|
||||
@@ -15,6 +20,7 @@ pub trait ScriptBuilder: std::fmt::Debug {
|
||||
fn build_script(
|
||||
&self,
|
||||
buf: Vec<u8>,
|
||||
filename: &str,
|
||||
encoding: Encoding,
|
||||
archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
@@ -28,7 +34,22 @@ pub trait ScriptBuilder: std::fmt::Debug {
|
||||
config: &ExtraConfig,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
let data = crate::utils::files::read_file(filename)?;
|
||||
self.build_script(data, encoding, archive_encoding, config)
|
||||
self.build_script(data, filename, encoding, archive_encoding, config)
|
||||
}
|
||||
|
||||
fn build_script_from_reader(
|
||||
&self,
|
||||
mut reader: Box<dyn ReadSeek>,
|
||||
filename: &str,
|
||||
encoding: Encoding,
|
||||
archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
let mut data = Vec::new();
|
||||
reader
|
||||
.read_to_end(&mut data)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to read from reader: {}", e))?;
|
||||
self.build_script(data, filename, encoding, archive_encoding, config)
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &'static [&'static str];
|
||||
@@ -53,6 +74,14 @@ pub trait ArchiveContent {
|
||||
pub trait Script: std::fmt::Debug {
|
||||
fn default_output_script_type(&self) -> OutputScriptType;
|
||||
|
||||
fn is_output_supported(&self, output: OutputScriptType) -> bool {
|
||||
!matches!(output, OutputScriptType::Custom)
|
||||
}
|
||||
|
||||
fn custom_output_extension(&self) -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
fn default_format_type(&self) -> FormatOptions;
|
||||
|
||||
fn extract_messages(&self) -> Result<Vec<Message>> {
|
||||
@@ -79,6 +108,12 @@ pub trait Script: std::fmt::Debug {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn custom_export(&self, _filename: &std::path::Path, _encoding: Encoding) -> Result<()> {
|
||||
Err(anyhow::anyhow!(
|
||||
"This script type does not support custom export."
|
||||
))
|
||||
}
|
||||
|
||||
fn is_archive(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ impl ScriptBuilder for BGIScriptBuilder {
|
||||
fn build_script(
|
||||
&self,
|
||||
buf: Vec<u8>,
|
||||
_filename: &str,
|
||||
encoding: Encoding,
|
||||
_archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
|
||||
@@ -21,6 +21,7 @@ impl ScriptBuilder for CircusMesScriptBuilder {
|
||||
fn build_script(
|
||||
&self,
|
||||
buf: Vec<u8>,
|
||||
_filename: &str,
|
||||
encoding: Encoding,
|
||||
_archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
|
||||
@@ -27,6 +27,7 @@ impl ScriptBuilder for EscudeBinArchiveBuilder {
|
||||
fn build_script(
|
||||
&self,
|
||||
data: Vec<u8>,
|
||||
_filename: &str,
|
||||
_encoding: Encoding,
|
||||
archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
@@ -63,6 +64,21 @@ impl ScriptBuilder for EscudeBinArchiveBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_script_from_reader(
|
||||
&self,
|
||||
reader: Box<dyn ReadSeek>,
|
||||
_filename: &str,
|
||||
_encoding: Encoding,
|
||||
archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
Ok(Box::new(EscudeBinArchive::new(
|
||||
reader,
|
||||
archive_encoding,
|
||||
config,
|
||||
)?))
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["bin"]
|
||||
}
|
||||
@@ -105,7 +121,7 @@ impl ArchiveContent for Entry {
|
||||
}
|
||||
|
||||
fn is_script(&self) -> bool {
|
||||
self.data.starts_with(b"ESCR1_00")
|
||||
self.data.starts_with(b"ESCR1_00") || self.data.starts_with(b"LIST")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
252
src/scripts/escude/list.rs
Normal file
252
src/scripts/escude/list.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::encoding::encode_string;
|
||||
use crate::utils::struct_pack::*;
|
||||
use anyhow::Result;
|
||||
use msg_tool_macro::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EscudeBinListBuilder {}
|
||||
|
||||
impl EscudeBinListBuilder {
|
||||
pub const fn new() -> Self {
|
||||
EscudeBinListBuilder {}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptBuilder for EscudeBinListBuilder {
|
||||
fn default_encoding(&self) -> Encoding {
|
||||
Encoding::Cp932
|
||||
}
|
||||
|
||||
fn build_script(
|
||||
&self,
|
||||
data: Vec<u8>,
|
||||
filename: &str,
|
||||
encoding: Encoding,
|
||||
_archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
Ok(Box::new(EscudeBinList::new(
|
||||
data, filename, encoding, config,
|
||||
)?))
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["bin"]
|
||||
}
|
||||
|
||||
fn script_type(&self) -> &'static ScriptType {
|
||||
&ScriptType::EscudeList
|
||||
}
|
||||
|
||||
fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
|
||||
if buf_len > 4 && buf.starts_with(b"LIST") {
|
||||
return Some(255);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EscudeBinList {
|
||||
entries: Vec<ListEntry>,
|
||||
}
|
||||
|
||||
impl EscudeBinList {
|
||||
pub fn new(
|
||||
data: Vec<u8>,
|
||||
filename: &str,
|
||||
encoding: Encoding,
|
||||
_config: &ExtraConfig,
|
||||
) -> Result<Self> {
|
||||
let mut reader = MemReader::new(data);
|
||||
let mut magic = [0; 4];
|
||||
reader.read_exact(&mut magic)?;
|
||||
if &magic != b"LIST" {
|
||||
return Err(anyhow::anyhow!("Invalid Escude list file format"));
|
||||
}
|
||||
let wsize = reader.read_u32()?;
|
||||
let mut entries = Vec::new();
|
||||
loop {
|
||||
let current = reader.stream_position()?;
|
||||
if current as usize >= wsize as usize + 8 {
|
||||
break;
|
||||
}
|
||||
let id = reader.read_u32()?;
|
||||
let size = reader.read_u32()?;
|
||||
let data = reader.read_exact_vec(size as usize)?;
|
||||
entries.push(ListEntry {
|
||||
id: id,
|
||||
data: ListData::Unknown(data),
|
||||
});
|
||||
}
|
||||
let mut s = EscudeBinList { entries };
|
||||
match s.try_decode(filename, encoding) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
eprintln!("WARN: Failed to decode Escude list: {}", e);
|
||||
crate::COUNTER.inc_warning();
|
||||
}
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn try_decode(&mut self, filename: &str, encoding: Encoding) -> Result<()> {
|
||||
let filename = std::path::Path::new(filename);
|
||||
if let Some(filename) = filename.file_name() {
|
||||
let filename = filename.to_ascii_lowercase();
|
||||
if filename == "enum_scr.bin" {
|
||||
for ent in self.entries.iter_mut() {
|
||||
let id = ent.id;
|
||||
if let ListData::Unknown(unk) = &ent.data {
|
||||
let mut reader = MemReader::new(unk.clone());
|
||||
let element_size = if id == 0 {
|
||||
132
|
||||
} else if id == 1 {
|
||||
100
|
||||
} else if id == 2 {
|
||||
36
|
||||
} else if id == 3 {
|
||||
104
|
||||
} else if id == 9999 {
|
||||
1
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("Unknown enum source ID: {}", id));
|
||||
};
|
||||
let len = unk.len();
|
||||
if len % element_size != 0 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Invalid enum source length: {} for ID: {}",
|
||||
len,
|
||||
id
|
||||
));
|
||||
}
|
||||
let count = len / element_size;
|
||||
let data_entry = match id {
|
||||
0 => ListData::Scr(EnumScr::Scripts(
|
||||
reader.read_struct_vec::<ScriptT>(count, false, encoding)?,
|
||||
)),
|
||||
1 => ListData::Scr(EnumScr::Names(
|
||||
reader.read_struct_vec::<NameT>(count, false, encoding)?,
|
||||
)),
|
||||
2 => ListData::Scr(EnumScr::Vars(
|
||||
reader.read_struct_vec::<VarT>(count, false, encoding)?,
|
||||
)),
|
||||
3 => ListData::Scr(EnumScr::Scenes(
|
||||
reader.read_struct_vec::<SceneT>(count, false, encoding)?,
|
||||
)),
|
||||
9999 => {
|
||||
// Special case for unknown enum source ID
|
||||
ListData::Unknown(unk.clone())
|
||||
}
|
||||
_ => return Err(anyhow::anyhow!("Unknown enum source ID: {}", id)),
|
||||
};
|
||||
ent.data = data_entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Script for EscudeBinList {
|
||||
fn default_output_script_type(&self) -> OutputScriptType {
|
||||
OutputScriptType::Custom
|
||||
}
|
||||
|
||||
fn is_output_supported(&self, output: OutputScriptType) -> bool {
|
||||
matches!(output, OutputScriptType::Custom)
|
||||
}
|
||||
|
||||
fn default_format_type(&self) -> FormatOptions {
|
||||
FormatOptions::None
|
||||
}
|
||||
|
||||
fn custom_output_extension(&self) -> &'static str {
|
||||
"json"
|
||||
}
|
||||
|
||||
fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
|
||||
let s = serde_json::to_string_pretty(&self.entries)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to write Escude list to JSON: {}", e))?;
|
||||
let mut writer = crate::utils::files::write_file(filename)?;
|
||||
let s = encode_string(encoding, &s, false)?;
|
||||
writer.write_all(&s)?;
|
||||
writer.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
|
||||
struct ScriptT {
|
||||
#[fstring = 64]
|
||||
/// File name
|
||||
pub file: String,
|
||||
pub source: u32,
|
||||
#[fstring = 64]
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
|
||||
struct NameT {
|
||||
#[fstring = 64]
|
||||
/// Name of the character
|
||||
pub text: String,
|
||||
/// Text color
|
||||
pub color: u32,
|
||||
#[fstring = 32]
|
||||
/// Face image file name
|
||||
pub face: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
|
||||
struct VarT {
|
||||
/// Variable name
|
||||
#[fstring = 32]
|
||||
pub name: String,
|
||||
/// Variable value
|
||||
pub value: u16,
|
||||
/// Variable flag
|
||||
pub flag: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
|
||||
struct SceneT {
|
||||
/// The scene script ID
|
||||
pub script: u32,
|
||||
/// The scene name
|
||||
#[fstring = 64]
|
||||
pub name: String,
|
||||
/// The scene thumbail image file name
|
||||
#[fstring = 32]
|
||||
pub thumbnail: String,
|
||||
/// The scene order in the scene (Extra)
|
||||
pub order: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, StructPack)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
enum EnumScr {
|
||||
Scripts(Vec<ScriptT>),
|
||||
Names(Vec<NameT>),
|
||||
Vars(Vec<VarT>),
|
||||
Scenes(Vec<SceneT>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, StructPack)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
enum ListData {
|
||||
Scr(EnumScr),
|
||||
Unknown(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct ListEntry {
|
||||
id: u32,
|
||||
data: ListData,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod archive;
|
||||
mod crypto;
|
||||
pub mod list;
|
||||
mod lzw;
|
||||
pub mod script;
|
||||
|
||||
@@ -24,6 +24,7 @@ impl ScriptBuilder for EscudeBinScriptBuilder {
|
||||
fn build_script(
|
||||
&self,
|
||||
data: Vec<u8>,
|
||||
_filename: &str,
|
||||
encoding: Encoding,
|
||||
_archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
|
||||
@@ -18,6 +18,8 @@ lazy_static::lazy_static! {
|
||||
Box::new(escude::archive::EscudeBinArchiveBuilder::new()),
|
||||
#[cfg(feature = "escude")]
|
||||
Box::new(escude::script::EscudeBinScriptBuilder::new()),
|
||||
#[cfg(feature = "escude")]
|
||||
Box::new(escude::list::EscudeBinListBuilder::new()),
|
||||
];
|
||||
pub static ref ALL_EXTS: Vec<String> =
|
||||
BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect();
|
||||
|
||||
12
src/types.rs
12
src/types.rs
@@ -60,6 +60,14 @@ pub enum OutputScriptType {
|
||||
M3t,
|
||||
/// JSON which can be used for GalTransl
|
||||
Json,
|
||||
/// Custom output
|
||||
Custom,
|
||||
}
|
||||
|
||||
impl OutputScriptType {
|
||||
pub fn is_custom(&self) -> bool {
|
||||
matches!(self, OutputScriptType::Custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for OutputScriptType {
|
||||
@@ -67,6 +75,7 @@ impl AsRef<str> for OutputScriptType {
|
||||
match self {
|
||||
OutputScriptType::M3t => "m3t",
|
||||
OutputScriptType::Json => "json",
|
||||
OutputScriptType::Custom => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,6 +204,9 @@ pub enum ScriptType {
|
||||
#[cfg(feature = "escude")]
|
||||
/// Escude bin script
|
||||
Escude,
|
||||
#[cfg(feature = "escude")]
|
||||
/// Escude list script
|
||||
EscudeList,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
||||
@@ -65,3 +65,13 @@ pub fn write_file<F: AsRef<Path> + ?Sized>(f: &F) -> io::Result<Box<dyn Write>>
|
||||
Box::new(fs::File::create(f)?)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn make_sure_dir_exists<F: AsRef<Path> + ?Sized>(f: &F) -> io::Result<()> {
|
||||
let path = f.as_ref();
|
||||
if let Some(parent) = path.parent() {
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod encoding;
|
||||
mod encoding_win;
|
||||
pub mod files;
|
||||
pub mod name_replacement;
|
||||
pub mod struct_pack;
|
||||
|
||||
34
src/utils/struct_pack.rs
Normal file
34
src/utils/struct_pack.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use crate::types::Encoding;
|
||||
use anyhow::Result;
|
||||
use msg_tool_macro::struct_unpack_impl_for_num;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
pub trait StructUnpack: Sized {
|
||||
fn unpack<R: Read + Seek>(reader: R, big: bool, encoding: Encoding) -> Result<Self>;
|
||||
}
|
||||
|
||||
pub trait StructPack: Sized {
|
||||
fn pack<W: Write>(&self, writer: &mut W, big: bool, encoding: Encoding) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<T: StructPack> StructPack for Vec<T> {
|
||||
fn pack<W: Write>(&self, writer: &mut W, big: bool, encoding: Encoding) -> Result<()> {
|
||||
for item in self {
|
||||
item.pack(writer, big, encoding)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct_unpack_impl_for_num!(u8);
|
||||
struct_unpack_impl_for_num!(u16);
|
||||
struct_unpack_impl_for_num!(u32);
|
||||
struct_unpack_impl_for_num!(u64);
|
||||
struct_unpack_impl_for_num!(u128);
|
||||
struct_unpack_impl_for_num!(i8);
|
||||
struct_unpack_impl_for_num!(i16);
|
||||
struct_unpack_impl_for_num!(i32);
|
||||
struct_unpack_impl_for_num!(i64);
|
||||
struct_unpack_impl_for_num!(i128);
|
||||
struct_unpack_impl_for_num!(f32);
|
||||
struct_unpack_impl_for_num!(f64);
|
||||
Reference in New Issue
Block a user