diff --git a/.gitignore b/.gitignore index d517caf..7bacca3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.log .vscode/ /vendor +/tmp diff --git a/Cargo.toml b/Cargo.toml index 91b26b6..58f5d9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ circus-arc = ["circus"] circus-audio = ["circus", "flate2", "int-enum", "lossless-audio"] circus-img = ["circus", "image", "flate2", "zstd"] emote-img = ["base64", "emote-psb", "image", "json", "libtlg-rs", "url"] -entis-gls = ["xml5ever", "markup5ever", "markup5ever_rcdom"] +entis-gls = ["xml5ever", "markup5ever", "markup5ever_rcdom", "int-enum"] escude = ["int-enum"] escude-arc = ["escude", "rand", "utils-bit-stream"] ex-hibit = [] diff --git a/src/scripts/entis_gls/csx/mod.rs b/src/scripts/entis_gls/csx/mod.rs new file mode 100644 index 0000000..23ce58f --- /dev/null +++ b/src/scripts/entis_gls/csx/mod.rs @@ -0,0 +1,3 @@ +//! Ported from Crsky/EntisGLS_Tools C# project +//! Original license: GPL-3.0 +pub mod v1; diff --git a/src/scripts/entis_gls/csx/v1/disasm.rs b/src/scripts/entis_gls/csx/v1/disasm.rs new file mode 100644 index 0000000..ae0a37a --- /dev/null +++ b/src/scripts/entis_gls/csx/v1/disasm.rs @@ -0,0 +1,560 @@ +use super::types::*; +use crate::ext::io::*; +use crate::types::*; +use crate::utils::struct_pack::*; +use anyhow::Result; +use std::io::{Seek, Write}; + +use CSCompareType::*; +use CSInstructionCode::*; +use CSObjectMode::*; +use CSOperatorType::*; +use CSUnaryOperatorType::*; +use CSVariableType::*; + +fn escape_string(s: &str) -> String { + s.replace("\r", "\\r") + .replace("\n", "\\n") + .replace("\t", "\\t") +} + +pub struct ECSExecutionImageDisassembler<'a> { + stream: MemReaderRef<'a>, + conststr: Option<&'a TaggedRefAddressList>, + assembly: ECSExecutionImageAssembly, + writer: Option>, + addr: u32, + code: CSInstructionCode, +} + +impl<'a> ECSExecutionImageDisassembler<'a> { + pub fn new( + stream: MemReaderRef<'a>, + conststr: Option<&'a TaggedRefAddressList>, + writer: Option>, + ) -> Self { + Self { + stream, + conststr, + assembly: ECSExecutionImageAssembly { + command_list: Vec::new(), + }, + writer, + addr: 0, + code: CsicNew, + } + } + + pub fn execute(&mut self) -> Result<()> { + let len = self.stream.data.len(); + while self.stream.pos < len { + self.addr = self.stream.pos as u32; + let code_value = self.stream.read_u8()?; + self.code = CSInstructionCode::try_from(code_value).map_err(|_| { + anyhow::anyhow!( + "Invalid CSInstructionCode value: {} at {:08x}", + code_value, + self.addr + ) + })?; + match self.code { + CsicNew => { + self.command_new()?; + } + CsicFree => { + self.command_free()?; + } + CsicLoad => { + self.command_load()?; + } + CsicStore => { + self.command_store()?; + } + CsicEnter => { + self.command_enter()?; + } + CsicLeave => { + self.command_leave()?; + } + CsicJump => { + self.command_jump()?; + } + CsicCJump => { + self.command_cjump()?; + } + CsicCall => { + self.command_call()?; + } + CsicReturn => { + self.command_return()?; + } + CsicElement => { + self.command_element()?; + } + CsicElementIndirect => { + self.command_element_indirect()?; + } + CsicOperate => { + self.command_operate()?; + } + CsicUniOperate => { + self.command_uni_operate()?; + } + CsicCompare => { + self.command_compare()?; + } + } + let size = self.stream.pos as u32 - self.addr; + self.assembly + .command_list + .push(ECSExecutionImageCommandRecord { + code: self.code, + addr: self.addr, + size, + new_addr: self.addr, + }); + } + Ok(()) + } + + fn line + ?Sized>(&mut self, line: &S) -> anyhow::Result<()> { + if let Some(writer) = &mut self.writer { + writeln!(writer, "{:08x} {}", self.addr, line.as_ref())?; + } + Ok(()) + } + + fn get_string_literal2(&mut self) -> Result<(Option, String)> { + let length = self.stream.read_u32()?; + if length != 0x80000000 { + self.stream.seek_relative(-4)?; + let s = WideString::unpack(&mut self.stream, false, Encoding::Utf16LE)?.0; + Ok((None, s)) + } else if let Some(conststr) = &self.conststr { + let index = self.stream.read_u32()? as usize; + match conststr.get(index) { + Some(s) => Ok((Some(index), s.tag.0.clone())), + None => Err(anyhow::anyhow!( + "Invalid string literal index: {} (max {})", + index, + conststr.len() + )), + } + } else { + Err(anyhow::anyhow!( + "No constant string table for string literal index" + )) + } + } + + fn get_string_literal(&mut self) -> Result { + let (_, s) = self.get_string_literal2()?; + Ok(s) + } + + fn read_csct(&mut self) -> Result { + let value = self.stream.read_u8()?; + CSCompareType::try_from(value).map_err(|_| { + anyhow::anyhow!( + "Invalid CSCompareType value: {} at {:08x}", + value, + self.addr + ) + }) + } + + fn read_csom(&mut self) -> Result { + let value = self.stream.read_u8()?; + CSObjectMode::try_from(value).map_err(|_| { + anyhow::anyhow!("Invalid CSObjectMode value: {} at {:08x}", value, self.addr) + }) + } + + fn read_csot(&mut self) -> Result { + let value = self.stream.read_u8()?; + CSOperatorType::try_from(value).map_err(|_| { + anyhow::anyhow!( + "Invalid CSOperatorType value: {} at {:08x}", + value, + self.addr + ) + }) + } + + fn read_csuot(&mut self) -> Result { + let value = self.stream.read_u8()?; + CSUnaryOperatorType::try_from(value).map_err(|_| { + anyhow::anyhow!( + "Invalid CSUnaryOperatorType value: {} at {:08x}", + value, + self.addr + ) + }) + } + + fn read_csvt(&mut self) -> Result { + let value = self.stream.read_u8()?; + CSVariableType::try_from(value).map_err(|_| { + anyhow::anyhow!( + "Invalid CSVariableType value: {} at {:08x}", + value, + self.addr + ) + }) + } + + fn command_new(&mut self) -> Result<()> { + let csom = self.read_csom()?; + let typ = self.read_csvt()?; + let class_name = if typ == CsvtObject { + Some(self.get_string_literal()?) + } else { + None + }; + let name = self.get_string_literal()?; + let pobj = match csom { + CsomStack => "stack", + CsomThis => "this", + _ => { + return Err(anyhow::anyhow!( + "Invalid CSObjectMode for 'new' instruction: {:?} at {:08x}", + csom, + self.addr + )); + } + }; + match &class_name { + Some(class_name) => { + self.line(&format!("New {pobj} \"{class_name}\" \"{name}\""))?; + } + None => { + self.line(&format!("New {pobj} \"{name}\""))?; + } + } + Ok(()) + } + + fn command_free(&mut self) -> Result<()> { + self.line("Free")?; + Ok(()) + } + + fn command_load(&mut self) -> Result<()> { + let csom = self.read_csom()?; + let csvt = self.read_csvt()?; + if csom == CsomImmediate { + match csvt { + CsvtObject => { + let class_name = self.get_string_literal()?; + self.line(&format!("Load * {class_name}"))?; + } + CsvtReference => { + self.line("Load * ECSReference")?; + } + CsvtArray => { + self.line("Load * ECSArray")?; + } + CsvtHash => { + self.line("Load * ECSHash")?; + } + CsvtInteger => { + let val = self.stream.read_u32()?; + self.line(&format!("Load Integer {}", val))?; + } + CsvtReal => { + let val = self.stream.read_f64()?; + self.line(&format!("Load Real {}", val))?; + } + CsvtString => { + let t = self.get_string_literal2()?; + let escaped = escape_string(&t.1); + if let Some(index) = t.0 { + self.line(&format!("Load Const String {index} \"{escaped}\""))?; + } else { + self.line(&format!("Load String \"{escaped}\""))?; + } + } + CsvtInteger64 => { + let val = self.stream.read_u64()?; + self.line(&format!("Load Integer64 {}", val))?; + } + CsvtPointer => { + let value = self.stream.read_u32()?; + self.line(&format!("Load Pointer {}", value))?; + } + } + } else { + let pobj = match csom { + CsomStack => "stack", + CsomThis => "this", + CsomGlobal => "global", + CsomData => "data", + CsomAuto => "auto", + _ => { + return Err(anyhow::anyhow!( + "Invalid CSObjectMode for 'load' instruction: {:?} at {:08x}", + csom, + self.addr + )); + } + }; + match csvt { + CsvtReference => { + self.line(&format!("Load {pobj}"))?; + } + CsvtInteger => { + let index = self.stream.read_i32()?; + self.line(&format!("Load {pobj} [{index}]"))?; + } + CsvtString => { + let name = self.get_string_literal()?; + self.line(&format!("Load {pobj} [\"{name}\"]"))?; + } + _ => { + return Err(anyhow::anyhow!( + "Invalid CSVariableType for 'load' instruction: {:?} at {:08x}", + csvt, + self.addr + )); + } + } + } + Ok(()) + } + + fn command_store(&mut self) -> Result<()> { + let csot = self.read_csot()?; + match csot { + CsotNop => { + self.line("Store")?; + } + CsotAdd => { + self.line("Store Add")?; + } + CsotSub => { + self.line("Store Sub")?; + } + CsotMul => { + self.line("Store Mul")?; + } + CsotDiv => { + self.line("Store Div")?; + } + CsotMod => { + self.line("Store Mod")?; + } + CsotAnd => { + self.line("Store And")?; + } + CsotOr => { + self.line("Store Or")?; + } + CsotXor => { + self.line("Store Xor")?; + } + CsotLogicalAnd => { + self.line("Store LAnd")?; + } + CsotLogicalOr => { + self.line("Store LOr")?; + } + } + Ok(()) + } + + fn command_enter(&mut self) -> Result<()> { + let name = self.get_string_literal()?; + let num_args = self.stream.read_i32()?; + if num_args != -1 { + let mut sb = String::new(); + sb.push('('); + for i in 0..num_args { + let csvt = self.read_csvt()?; + let class_name = if csvt == CsvtObject { + Some(self.get_string_literal()?) + } else { + None + }; + let var_name = self.get_string_literal()?; + if let Some(cname) = class_name { + sb.push_str(&format!("{{{}:{}}}", cname, var_name)); + } else { + sb.push_str(&var_name); + } + if i < num_args - 1 { + sb.push_str(", "); + } + } + sb.push(')'); + self.line(&format!("Enter \"{}\" {}", name, sb))?; + } else { + let flag = self.stream.read_u8()?; + if flag != 0 { + return Err(anyhow::anyhow!( + "Invalid flag for variable argument 'enter' instruction: {} at {:08x}", + flag, + self.addr + )); + } + let catch_addr = self.stream.read_u32()? + self.stream.pos as u32; + self.line(&format!("Enter \"{}\" Try-Catch {:08x}", name, catch_addr))?; + } + Ok(()) + } + + fn command_leave(&mut self) -> Result<()> { + self.line("Leave")?; + Ok(()) + } + + fn command_jump(&mut self) -> Result<()> { + let target_addr = self.stream.read_u32()? as u64 + self.stream.pos as u64; + self.line(&format!("Jump {:08x}", target_addr))?; + Ok(()) + } + + fn command_cjump(&mut self) -> Result<()> { + let cond = self.stream.read_u8()?; + let target_addr = self.stream.read_u32()? as u64 + self.stream.pos as u64; + self.line(&format!("CJump {} {:08x}", cond, target_addr))?; + Ok(()) + } + + fn command_call(&mut self) -> Result<()> { + let csom = self.read_csom()?; + let num_args = self.stream.read_i32()?; + let func_name = self.get_string_literal()?; + let pobj = match csom { + CsomImmediate if func_name == "@CATCH" => "", + CsomThis => "this", + CsomGlobal => "global", + CsomAuto => "auto", + _ => { + return Err(anyhow::anyhow!( + "Invalid CSObjectMode for 'call' instruction: {:?} at {:08x}", + csom, + self.addr + )); + } + }; + self.line(&format!("Call {} \"{}\" <{}>", pobj, func_name, num_args))?; + Ok(()) + } + + fn command_return(&mut self) -> Result<()> { + let free_stack = self.stream.read_u8()?; + self.line(&format!("Return {}", free_stack))?; + Ok(()) + } + + fn command_element(&mut self) -> Result<()> { + let csvt = self.read_csvt()?; + match csvt { + CsvtInteger => { + let index = self.stream.read_i32()?; + self.line(&format!("Element {}", index))?; + } + CsvtString => { + let name = self.get_string_literal()?; + self.line(&format!("Element \"{}\"", name))?; + } + _ => { + return Err(anyhow::anyhow!( + "Invalid CSVariableType for 'element' instruction: {:?} at {:08x}", + csvt, + self.addr + )); + } + } + Ok(()) + } + + fn command_element_indirect(&mut self) -> Result<()> { + self.line("ElementIndirect")?; + Ok(()) + } + + fn command_operate(&mut self) -> Result<()> { + let csot = self.read_csot()?; + match csot { + CsotNop => { + self.line("Operate Nop")?; + } + CsotAdd => { + self.line("Operate Add")?; + } + CsotSub => { + self.line("Operate Sub")?; + } + CsotMul => { + self.line("Operate Mul")?; + } + CsotDiv => { + self.line("Operate Div")?; + } + CsotMod => { + self.line("Operate Mod")?; + } + CsotAnd => { + self.line("Operate And")?; + } + CsotOr => { + self.line("Operate Or")?; + } + CsotXor => { + self.line("Operate Xor")?; + } + CsotLogicalAnd => { + self.line("Operate LAnd")?; + } + CsotLogicalOr => { + self.line("Operate LOr")?; + } + } + Ok(()) + } + + fn command_uni_operate(&mut self) -> Result<()> { + let csuot = self.read_csuot()?; + match csuot { + CsuotPlus => { + self.line("UnaryOperate Plus")?; + } + CsuotNegate => { + self.line("UnaryOperate Negate")?; + } + CsuotBitnot => { + self.line("UnaryOperate BitNot")?; + } + CsuotLogicalNot => { + self.line("UnaryOperate LNot")?; + } + } + Ok(()) + } + + fn command_compare(&mut self) -> Result<()> { + let csct = self.read_csct()?; + match csct { + CsctEqual => { + self.line("Compare Equal")?; + } + CsctNotEqual => { + self.line("Compare NotEqual")?; + } + CsctLessThan => { + self.line("Compare LessThan")?; + } + CsctLessEqual => { + self.line("Compare LessEqual")?; + } + CsctGreaterThan => { + self.line("Compare GreaterThan")?; + } + CsctGreaterEqual => { + self.line("Compare GreaterEqual")?; + } + } + Ok(()) + } +} diff --git a/src/scripts/entis_gls/csx/v1/img.rs b/src/scripts/entis_gls/csx/v1/img.rs new file mode 100644 index 0000000..0d73c35 --- /dev/null +++ b/src/scripts/entis_gls/csx/v1/img.rs @@ -0,0 +1,186 @@ +use super::disasm::*; +use super::types::*; +use crate::ext::io::*; +use crate::types::*; +use crate::utils::struct_pack::*; +use anyhow::Result; +use std::io::Write; + +#[derive(Clone, Debug)] +#[allow(dead_code)] +pub struct ECSExecutionImage { + file_header: EMCFileHeader, + exi_header: Option>, + header: Option, + image: MemReader, + pif_prologue: DWordArray, + pif_epilogue: DWordArray, + function_list: FunctionNameList, + csg_global: ECSGlobal, + csg_data: ECSGlobal, + ext_const_str: Option, + ext_global_ref: DWordArray, + ext_data_ref: DWordArray, + imp_global_ref: TaggedRefAddressList, + imp_data_ref: TaggedRefAddressList, +} + +impl ECSExecutionImage { + pub fn new(mut reader: MemReader) -> Result { + let file_header = EMCFileHeader::unpack(&mut reader, false, Encoding::Utf8)?; + // if file_header.signagure != *b"Entis\x1a\0\0" { + // return Err(anyhow::anyhow!("Invalid EMC file signature")); + // } + let len = reader.data.len(); + let mut exi_header = None; + let mut header = None; + let mut image = None; + let mut pif_prologue = None; + let mut pif_epilogue = None; + let mut function_list = None; + let mut csg_global = None; + let mut int64 = false; + let mut csg_data = None; + let mut ext_const_str = None; + let mut ext_global_ref = DWordArray::default(); + let mut ext_data_ref = DWordArray::default(); + let mut imp_global_ref = TaggedRefAddressList::default(); + let mut imp_data_ref = TaggedRefAddressList::default(); + while reader.pos < len { + if len - reader.pos < 16 { + break; + } + let id = reader.read_u64()?; + if id == 0 { + break; + } + let size = reader.read_u64()?; + match id { + // header + 0x2020726564616568 => { + let buf = reader.read_exact_vec(size as usize)?; + { + let mut sread = MemReaderRef::new(&buf); + header = Some(EXIHeader::unpack(&mut sread, false, Encoding::Utf8)?); + } + exi_header = Some(buf); + if let Some(hdr) = &header { + if hdr.int_base == 64 { + int64 = true; + } + } + } + // image + 0x2020206567616D69 => { + image = Some(MemReader::new(reader.read_exact_vec(size as usize)?)); + } + // function + 0x6E6F6974636E7566 => { + pif_prologue = Some(DWordArray::unpack(&mut reader, false, Encoding::Utf8)?); + pif_epilogue = Some(DWordArray::unpack(&mut reader, false, Encoding::Utf8)?); + function_list = Some(FunctionNameList::unpack( + &mut reader, + false, + Encoding::Utf8, + )?); + } + // global + 0x20206C61626F6C67 => { + let count = reader.read_u32()?; + let mut items = Vec::with_capacity(count as usize); + for _ in 0..count { + let name = WideString::unpack(&mut reader, false, Encoding::Utf16LE)?.0; + let obj = ECSObject::read_from(&mut reader, int64)?; + items.push(ECSObjectItem { name, obj }); + } + csg_global = Some(ECSGlobal(items)); + } + // data + 0x2020202061746164 => { + let count = reader.read_u32()?; + let mut items = Vec::with_capacity(count as usize); + for _ in 0..count { + let name = WideString::unpack(&mut reader, false, Encoding::Utf16LE)?.0; + let length = reader.read_i32()?; + let obj = if length >= 0 { + let mut datas = Vec::with_capacity(length as usize); + for _ in 0..length { + let name = + WideString::unpack(&mut reader, false, Encoding::Utf16LE)?.0; + let obj = ECSObject::read_from(&mut reader, int64)?; + datas.push(ECSObjectItem { name, obj }); + } + ECSObject::Global(ECSGlobal(datas)) + } else { + ECSObject::read_from(&mut reader, int64)? + }; + items.push(ECSObjectItem { name, obj }); + } + csg_data = Some(ECSGlobal(items)); + } + // conststr + 0x72747374736E6F63 => { + ext_const_str = Some(TaggedRefAddressList::unpack( + &mut reader, + false, + Encoding::Utf8, + )?); + } + // linkinf + 0x20666E696B6E696C => { + ext_global_ref = DWordArray::unpack(&mut reader, false, Encoding::Utf8)?; + ext_data_ref = DWordArray::unpack(&mut reader, false, Encoding::Utf8)?; + imp_global_ref = + TaggedRefAddressList::unpack(&mut reader, false, Encoding::Utf8)?; + imp_data_ref = + TaggedRefAddressList::unpack(&mut reader, false, Encoding::Utf8)?; + if !ext_global_ref.is_empty() + || !ext_data_ref.is_empty() + || !imp_global_ref.is_empty() + || !imp_data_ref.is_empty() + { + eprintln!( + "Warning: External/global references(linker data) are not supported and will be ignored. This may cause script rebuild errors." + ); + crate::COUNTER.inc_warning(); + } + } + 0 => { + break; + } + _ => { + return Err(anyhow::anyhow!( + "Unknown ECSExecutionImage section ID: 0x{:016X}", + id + )); + } + } + } + Ok(Self { + file_header, + exi_header, + header, + image: image.ok_or_else(|| anyhow::anyhow!("Missing image data"))?, + pif_prologue: pif_prologue.ok_or_else(|| anyhow::anyhow!("Missing PIF prologue"))?, + pif_epilogue: pif_epilogue.ok_or_else(|| anyhow::anyhow!("Missing PIF epilogue"))?, + function_list: function_list.ok_or_else(|| anyhow::anyhow!("Missing function list"))?, + csg_global: csg_global.ok_or_else(|| anyhow::anyhow!("Missing CSG global"))?, + csg_data: csg_data.ok_or_else(|| anyhow::anyhow!("Missing CSG data"))?, + ext_const_str, + ext_global_ref, + ext_data_ref, + imp_global_ref, + imp_data_ref, + }) + } + + pub fn disasm<'a>(&self, writer: Box) -> Result<()> { + let mut disasm = ECSExecutionImageDisassembler::new( + self.image.to_ref(), + self.ext_const_str.as_ref(), + Some(writer), + ); + disasm.execute()?; + Ok(()) + } +} diff --git a/src/scripts/entis_gls/csx/v1/mod.rs b/src/scripts/entis_gls/csx/v1/mod.rs new file mode 100644 index 0000000..cb30fc7 --- /dev/null +++ b/src/scripts/entis_gls/csx/v1/mod.rs @@ -0,0 +1,91 @@ +//! Ported from CSXTools C# project +//! See parent module documentation for more details. +mod disasm; +mod img; +mod types; + +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use anyhow::Result; +use img::ECSExecutionImage; + +#[derive(Debug)] +pub struct CSXScriptBuilder {} + +impl CSXScriptBuilder { + pub fn new() -> Self { + Self {} + } +} + +impl ScriptBuilder for CSXScriptBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Utf16LE + } + + fn build_script( + &self, + buf: Vec, + _filename: &str, + _encoding: Encoding, + _archive_encoding: Encoding, + config: &ExtraConfig, + _archive: Option<&Box>, + ) -> Result> { + Ok(Box::new(CSXScript::new(buf, config)?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &["csx"] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::EntisGlsCsx1 + } + + fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + if buf_len >= 8 && &buf[0..8] == b"Entis\x1a\0\0" { + Some(30) + } else { + None + } + } +} + +#[derive(Debug)] +pub struct CSXScript { + img: ECSExecutionImage, +} + +impl CSXScript { + pub fn new(buf: Vec, _config: &ExtraConfig) -> Result { + let reader = MemReader::new(buf); + let img = ECSExecutionImage::new(reader)?; + Ok(Self { img }) + } +} + +impl Script for CSXScript { + 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<'a>(&'a self) -> &'a str { + "s" + } + + fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> { + let file = crate::utils::files::write_file(filename)?; + self.img.disasm(Box::new(file))?; + Ok(()) + } +} diff --git a/src/scripts/entis_gls/csx/v1/types.rs b/src/scripts/entis_gls/csx/v1/types.rs new file mode 100644 index 0000000..0cd3353 --- /dev/null +++ b/src/scripts/entis_gls/csx/v1/types.rs @@ -0,0 +1,320 @@ +use crate::ext::io::*; +use crate::types::*; +use crate::utils::encoding::*; +use crate::utils::struct_pack::*; +use anyhow::Result; +use int_enum::IntEnum; +use msg_tool_macro::{StructPack, StructUnpack}; +use std::io::{Read, Seek, Write}; +use std::ops::{Deref, DerefMut}; + +#[repr(u8)] +#[derive(Debug, IntEnum, PartialEq, Eq, Clone, Copy)] +pub enum CSCompareType { + CsctNotEqual, + CsctEqual, + CsctLessThan, + CsctLessEqual, + CsctGreaterThan, + CsctGreaterEqual, +} + +#[repr(u8)] +#[derive(Debug, IntEnum, PartialEq, Eq, Clone, Copy)] +pub enum CSInstructionCode { + CsicNew, + CsicFree, + CsicLoad, + CsicStore, + CsicEnter, + CsicLeave, + CsicJump, + CsicCJump, + CsicCall, + CsicReturn, + CsicElement, + CsicElementIndirect, + CsicOperate, + CsicUniOperate, + CsicCompare, +} + +#[repr(u8)] +#[derive(Debug, IntEnum, PartialEq, Eq, Clone, Copy)] +pub enum CSObjectMode { + CsomImmediate, + CsomStack, + CsomThis, + CsomGlobal, + CsomData, + CsomAuto, +} + +#[repr(u8)] +#[derive(Debug, IntEnum, PartialEq, Eq, Clone, Copy)] +pub enum CSOperatorType { + CsotNop = 0xFF, + CsotAdd = 0, + CsotSub, + CsotMul, + CsotDiv, + CsotMod, + CsotAnd, + CsotOr, + CsotXor, + CsotLogicalAnd, + CsotLogicalOr, +} + +#[repr(u8)] +#[derive(Debug, IntEnum, PartialEq, Eq, Clone, Copy)] +pub enum CSUnaryOperatorType { + CsuotPlus, + CsuotNegate, + CsuotBitnot, + CsuotLogicalNot, +} + +#[repr(u8)] +#[derive(Debug, IntEnum, PartialEq, Eq, Clone, Copy)] +pub enum CSVariableType { + CsvtObject, + CsvtReference, + CsvtArray, + CsvtHash, + CsvtInteger, + CsvtReal, + CsvtString, + CsvtInteger64, + CsvtPointer, +} + +#[derive(Clone, Debug, StructPack, StructUnpack)] +pub struct EMCFileHeader { + pub signagure: [u8; 8], + pub file_id: u32, + pub _reserved: u32, + pub format_desc: [u8; 48], +} + +#[derive(Clone, Debug, StructPack, StructUnpack)] +pub struct EXIHeader { + #[skip_unpack_if(reader.stream_length()? < 4)] + pub version: u32, + #[skip_unpack_if(reader.stream_length()? < 8)] + pub int_base: u32, +} + +#[derive(Clone, Debug, Default, StructPack, StructUnpack)] +pub struct DWordArray { + #[pvec(u32)] + pub data: Vec, +} + +impl Deref for DWordArray { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for DWordArray { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +#[derive(Clone, Debug)] +pub struct WideString(pub String); + +impl StructUnpack for WideString { + fn unpack(reader: &mut R, big: bool, encoding: Encoding) -> Result { + let length = u32::unpack(reader, big, encoding)? as usize; + let to_read = length * 2; + let buf = reader.read_exact_vec(to_read)?; + let enc = if big { + Encoding::Utf16BE + } else { + Encoding::Utf16LE + }; + let s = decode_to_string(enc, &buf, true)?; + Ok(Self(s)) + } +} + +impl StructPack for WideString { + fn pack(&self, writer: &mut W, big: bool, encoding: Encoding) -> Result<()> { + let enc = if big { + Encoding::Utf16BE + } else { + Encoding::Utf16LE + }; + let encoded = encode_string(enc, &self.0, false)?; + let length = (encoded.len() / 2) as u32; + length.pack(writer, big, encoding)?; + writer.write_all(&encoded)?; + Ok(()) + } +} + +#[derive(Clone, Debug, StructPack, StructUnpack)] +pub struct FunctionNameItem { + pub addr: u32, + pub name: WideString, +} + +#[derive(Clone, Debug, StructPack, StructUnpack)] +pub struct FunctionNameList { + #[pvec(u32)] + pub items: Vec, +} + +#[derive(Clone, Debug)] +#[allow(dead_code)] +pub enum ECSObject { + Integer(i64), + Real(f64), + String(String), + Array(Vec), + ClassInfoObject(String), + Hash, + Reference, + Global(ECSGlobal), +} + +impl ECSObject { + pub fn read_from(reader: &mut R, int64: bool) -> Result { + let typ = reader.read_u32()?; + let obj_typ = CSVariableType::try_from(typ as u8) + .map_err(|typ| anyhow::anyhow!("Invalid CSVariableType: {}", typ))?; + match obj_typ { + CSVariableType::CsvtObject => { + let class_name = WideString::unpack(reader, false, Encoding::Utf8)?.0; + return Ok(ECSObject::ClassInfoObject(class_name)); + } + CSVariableType::CsvtReference => { + return Ok(ECSObject::Reference); + } + CSVariableType::CsvtArray => { + let count = reader.read_u32()? as usize; + let mut items = Vec::with_capacity(count); + for _ in 0..count { + let item = ECSObject::read_from(reader, int64)?; + items.push(item); + } + return Ok(ECSObject::Array(items)); + } + CSVariableType::CsvtHash => { + return Ok(ECSObject::Hash); + } + CSVariableType::CsvtInteger => { + if int64 { + let val = reader.read_i64()?; + return Ok(ECSObject::Integer(val)); + } else { + let val = reader.read_i32()? as i64; + return Ok(ECSObject::Integer(val)); + } + } + CSVariableType::CsvtReal => { + let val = reader.read_f64()?; + return Ok(ECSObject::Real(val)); + } + CSVariableType::CsvtString => { + let s = WideString::unpack(reader, false, Encoding::Utf8)?.0; + return Ok(ECSObject::String(s)); + } + _ => { + return Err(anyhow::anyhow!("Unsupported CSVariableType: {:?}", obj_typ)); + } + } + } +} + +#[derive(Clone, Debug)] +#[allow(dead_code)] +pub struct ECSObjectItem { + pub name: String, + pub obj: ECSObject, +} + +#[derive(Clone, Debug)] +pub struct ECSGlobal(pub Vec); + +impl Deref for ECSGlobal { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ECSGlobal { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Clone, Debug, StructPack, StructUnpack)] +pub struct TaggedRefAddress { + pub tag: WideString, + pub refs: DWordArray, +} + +#[derive(Clone, Debug, Default, StructPack, StructUnpack)] +pub struct TaggedRefAddressList { + #[pvec(u32)] + pub items: Vec, +} + +impl Deref for TaggedRefAddressList { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.items + } +} + +impl DerefMut for TaggedRefAddressList { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.items + } +} + +#[derive(Clone, Debug)] +#[allow(dead_code)] +pub struct ECSExecutionImageCommandRecord { + pub code: CSInstructionCode, + pub addr: u32, + pub size: u32, + pub new_addr: u32, +} + +#[derive(Clone, Debug)] +pub struct ECSExecutionImageAssembly { + pub command_list: Vec, +} + +impl Deref for ECSExecutionImageAssembly { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.command_list + } +} + +impl DerefMut for ECSExecutionImageAssembly { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.command_list + } +} + +#[test] +fn test_exi_header_unpack() { + let data = b"\x01\x00\x00\x00"; + let mut cursor = MemReaderRef::new(data); + let header = EXIHeader::unpack(&mut cursor, false, Encoding::Utf8).unwrap(); + assert_eq!(header.version, 1); + assert_eq!(header.int_base, 0); +} diff --git a/src/scripts/entis_gls/mod.rs b/src/scripts/entis_gls/mod.rs index 9d7212f..fc8d93a 100644 --- a/src/scripts/entis_gls/mod.rs +++ b/src/scripts/entis_gls/mod.rs @@ -1,2 +1,3 @@ //! Entis GLS engine Script +pub mod csx; pub mod srcxml; diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index 48bd477..72dd0e1 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -162,6 +162,8 @@ lazy_static::lazy_static! { Box::new(musica::sc::MusicaBuilder::new()), #[cfg(feature = "musica-arc")] Box::new(musica::archive::paz::PazArcBuilder::new()), + #[cfg(feature = "entis-gls")] + Box::new(entis_gls::csx::v1::CSXScriptBuilder::new()), ]; /// A list of all script extensions. pub static ref ALL_EXTS: Vec = diff --git a/src/types.rs b/src/types.rs index 12bf6c4..284d658 100644 --- a/src/types.rs +++ b/src/types.rs @@ -17,6 +17,8 @@ pub enum Encoding { Gb2312, /// UTF-16 Little Endian encoding Utf16LE, + /// UTF-16 Big Endian encoding + Utf16BE, /// Code page encoding (Windows only) #[cfg(windows)] CodePage(u32), @@ -67,6 +69,7 @@ impl Encoding { Self::Cp932 => Some("shift_jis"), Self::Gb2312 => Some("gbk"), Self::Utf16LE => Some("utf-16le"), + Self::Utf16BE => Some("utf-16be"), #[cfg(windows)] Self::CodePage(code_page) => match *code_page { 932 => Some("shift_jis"), @@ -670,6 +673,9 @@ pub enum ScriptType { #[cfg(feature = "entis-gls")] /// Entis GLS srcxml Script EntisGls, + #[cfg(feature = "entis-gls")] + /// Entis GLS csx script (version 1) + EntisGlsCsx1, #[cfg(feature = "escude-arc")] /// Escude bin archive EscudeArc, diff --git a/src/utils/encoding.rs b/src/utils/encoding.rs index 5c069ce..404f6ee 100644 --- a/src/utils/encoding.rs +++ b/src/utils/encoding.rs @@ -154,6 +154,16 @@ pub fn decode_to_string( }, ) .map_err(|_| anyhow::anyhow!("Failed to decode UTF-16LE"))?), + Encoding::Utf16BE => Ok(encoding::codec::utf_16::UTF_16BE_ENCODING + .decode( + data, + if check { + DecoderTrap::Strict + } else { + DecoderTrap::Replace + }, + ) + .map_err(|_| anyhow::anyhow!("Failed to decode UTF-16BE"))?), #[cfg(windows)] Encoding::CodePage(code_page) => Ok(super::encoding_win::decode_to_string( code_page, data, check, @@ -260,6 +270,10 @@ pub fn encode_string( let re = utf16string::WString::::from(data); Ok(re.as_bytes().to_vec()) } + Encoding::Utf16BE => { + let re = utf16string::WString::::from(data); + Ok(re.as_bytes().to_vec()) + } #[cfg(windows)] Encoding::CodePage(code_page) => { Ok(super::encoding_win::encode_string(code_page, data, check)?)