mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-07 05:18:44 +08:00
add entis gls csx v1 disasm (ported from Crsky/EntisGLS_Tools)
This commit is contained in:
3
src/scripts/entis_gls/csx/mod.rs
Normal file
3
src/scripts/entis_gls/csx/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
//! Ported from Crsky/EntisGLS_Tools C# project
|
||||
//! Original license: GPL-3.0
|
||||
pub mod v1;
|
||||
560
src/scripts/entis_gls/csx/v1/disasm.rs
Normal file
560
src/scripts/entis_gls/csx/v1/disasm.rs
Normal file
@@ -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<Box<dyn Write + 'a>>,
|
||||
addr: u32,
|
||||
code: CSInstructionCode,
|
||||
}
|
||||
|
||||
impl<'a> ECSExecutionImageDisassembler<'a> {
|
||||
pub fn new(
|
||||
stream: MemReaderRef<'a>,
|
||||
conststr: Option<&'a TaggedRefAddressList>,
|
||||
writer: Option<Box<dyn Write + 'a>>,
|
||||
) -> 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<S: AsRef<str> + ?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<usize>, 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<String> {
|
||||
let (_, s) = self.get_string_literal2()?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn read_csct(&mut self) -> Result<CSCompareType> {
|
||||
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<CSObjectMode> {
|
||||
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<CSOperatorType> {
|
||||
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<CSUnaryOperatorType> {
|
||||
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<CSVariableType> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
186
src/scripts/entis_gls/csx/v1/img.rs
Normal file
186
src/scripts/entis_gls/csx/v1/img.rs
Normal file
@@ -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<Vec<u8>>,
|
||||
header: Option<EXIHeader>,
|
||||
image: MemReader,
|
||||
pif_prologue: DWordArray,
|
||||
pif_epilogue: DWordArray,
|
||||
function_list: FunctionNameList,
|
||||
csg_global: ECSGlobal,
|
||||
csg_data: ECSGlobal,
|
||||
ext_const_str: Option<TaggedRefAddressList>,
|
||||
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<Self> {
|
||||
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<dyn Write + 'a>) -> Result<()> {
|
||||
let mut disasm = ECSExecutionImageDisassembler::new(
|
||||
self.image.to_ref(),
|
||||
self.ext_const_str.as_ref(),
|
||||
Some(writer),
|
||||
);
|
||||
disasm.execute()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
91
src/scripts/entis_gls/csx/v1/mod.rs
Normal file
91
src/scripts/entis_gls/csx/v1/mod.rs
Normal file
@@ -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<u8>,
|
||||
_filename: &str,
|
||||
_encoding: Encoding,
|
||||
_archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
_archive: Option<&Box<dyn Script>>,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
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<u8> {
|
||||
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<u8>, _config: &ExtraConfig) -> Result<Self> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
320
src/scripts/entis_gls/csx/v1/types.rs
Normal file
320
src/scripts/entis_gls/csx/v1/types.rs
Normal file
@@ -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<u32>,
|
||||
}
|
||||
|
||||
impl Deref for DWordArray {
|
||||
type Target = Vec<u32>;
|
||||
|
||||
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<R: Read + Seek>(reader: &mut R, big: bool, encoding: Encoding) -> Result<Self> {
|
||||
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<W: Write>(&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<FunctionNameItem>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum ECSObject {
|
||||
Integer(i64),
|
||||
Real(f64),
|
||||
String(String),
|
||||
Array(Vec<ECSObject>),
|
||||
ClassInfoObject(String),
|
||||
Hash,
|
||||
Reference,
|
||||
Global(ECSGlobal),
|
||||
}
|
||||
|
||||
impl ECSObject {
|
||||
pub fn read_from<R: Read + Seek>(reader: &mut R, int64: bool) -> Result<Self> {
|
||||
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<ECSObjectItem>);
|
||||
|
||||
impl Deref for ECSGlobal {
|
||||
type Target = Vec<ECSObjectItem>;
|
||||
|
||||
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<TaggedRefAddress>,
|
||||
}
|
||||
|
||||
impl Deref for TaggedRefAddressList {
|
||||
type Target = Vec<TaggedRefAddress>;
|
||||
|
||||
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<ECSExecutionImageCommandRecord>,
|
||||
}
|
||||
|
||||
impl Deref for ECSExecutionImageAssembly {
|
||||
type Target = Vec<ECSExecutionImageCommandRecord>;
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
//! Entis GLS engine Script
|
||||
pub mod csx;
|
||||
pub mod srcxml;
|
||||
|
||||
@@ -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<String> =
|
||||
|
||||
Reference in New Issue
Block a user