mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-07 05:18:44 +08:00
Add export support for entis gls csx script (v1)
This commit is contained in:
@@ -19,9 +19,9 @@ fn escape_string(s: &str) -> String {
|
||||
}
|
||||
|
||||
pub struct ECSExecutionImageDisassembler<'a> {
|
||||
stream: MemReaderRef<'a>,
|
||||
pub stream: MemReaderRef<'a>,
|
||||
conststr: Option<&'a TaggedRefAddressList>,
|
||||
assembly: ECSExecutionImageAssembly,
|
||||
pub assembly: ECSExecutionImageAssembly,
|
||||
writer: Option<Box<dyn Write + 'a>>,
|
||||
addr: u32,
|
||||
code: CSInstructionCode,
|
||||
@@ -147,7 +147,7 @@ impl<'a> ECSExecutionImageDisassembler<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_string_literal(&mut self) -> Result<String> {
|
||||
pub fn get_string_literal(&mut self) -> Result<String> {
|
||||
let (_, s) = self.get_string_literal2()?;
|
||||
Ok(s)
|
||||
}
|
||||
@@ -163,7 +163,7 @@ impl<'a> ECSExecutionImageDisassembler<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn read_csom(&mut self) -> Result<CSObjectMode> {
|
||||
pub 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)
|
||||
@@ -192,7 +192,7 @@ impl<'a> ECSExecutionImageDisassembler<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn read_csvt(&mut self) -> Result<CSVariableType> {
|
||||
pub fn read_csvt(&mut self) -> Result<CSVariableType> {
|
||||
let value = self.stream.read_u8()?;
|
||||
CSVariableType::try_from(value).map_err(|_| {
|
||||
anyhow::anyhow!(
|
||||
|
||||
@@ -4,8 +4,13 @@ use crate::ext::io::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::struct_pack::*;
|
||||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
|
||||
use CSInstructionCode::*;
|
||||
use CSObjectMode::*;
|
||||
use CSVariableType::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct ECSExecutionImage {
|
||||
@@ -183,4 +188,219 @@ impl ECSExecutionImage {
|
||||
disasm.execute()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn export(&self) -> Result<Vec<Message>> {
|
||||
let mut disasm = ECSExecutionImageDisassembler::new(
|
||||
self.image.to_ref(),
|
||||
self.ext_const_str.as_ref(),
|
||||
None,
|
||||
);
|
||||
disasm.execute()?;
|
||||
let mut messages = Vec::new();
|
||||
let assembly = disasm.assembly.clone();
|
||||
let mut name = None;
|
||||
let mut pre_is_mess = false;
|
||||
let mut string_stack = Vec::new();
|
||||
let mut message = String::new();
|
||||
for cmd in assembly.iter() {
|
||||
if cmd.code == CsicLoad {
|
||||
disasm.stream.pos = cmd.addr as usize + 1;
|
||||
let csom = disasm.read_csom()?;
|
||||
let csvt = disasm.read_csvt()?;
|
||||
if csom == CsomImmediate && csvt == CsvtString {
|
||||
let text = disasm.get_string_literal()?;
|
||||
string_stack.insert(0, text);
|
||||
if string_stack.len() > 8 {
|
||||
string_stack.pop();
|
||||
}
|
||||
}
|
||||
} else if cmd.code == CsicCall {
|
||||
disasm.stream.pos = cmd.addr as usize + 1;
|
||||
let _csom = disasm.read_csom()?;
|
||||
let num_args = disasm.stream.read_i32()?;
|
||||
let func_name = WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE)?.0;
|
||||
let mut is_mess = false;
|
||||
if num_args == 1 {
|
||||
if func_name == "SceneTitle" {
|
||||
if string_stack.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"SceneTitle call without string argument at {:08X}",
|
||||
cmd.addr
|
||||
));
|
||||
}
|
||||
messages.push(Message::new(string_stack[0].clone(), None));
|
||||
} else if func_name == "Mess" {
|
||||
is_mess = true;
|
||||
if string_stack.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Mess call without string argument at {:08X}",
|
||||
cmd.addr
|
||||
));
|
||||
}
|
||||
message.push_str(string_stack[0].as_str());
|
||||
}
|
||||
} else if num_args == 2 {
|
||||
if func_name == "Talk" {
|
||||
if string_stack.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Talk call without string argument at {:08X}",
|
||||
cmd.addr
|
||||
));
|
||||
}
|
||||
name = Some(string_stack[0].clone());
|
||||
} else if func_name == "AddSelect" {
|
||||
if string_stack.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"AddSelect call without string argument at {:08X}",
|
||||
cmd.addr
|
||||
));
|
||||
}
|
||||
messages.push(Message::new(string_stack[0].clone(), None));
|
||||
}
|
||||
}
|
||||
if pre_is_mess && !is_mess {
|
||||
messages.push(Message::new(message.clone(), name.take()));
|
||||
message.clear();
|
||||
}
|
||||
pre_is_mess = is_mess;
|
||||
}
|
||||
}
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
pub fn export_multi(&self) -> Result<HashMap<String, Vec<Message>>> {
|
||||
let mut key = String::from("global");
|
||||
let mut messages = HashMap::new();
|
||||
let mut disasm = ECSExecutionImageDisassembler::new(
|
||||
self.image.to_ref(),
|
||||
self.ext_const_str.as_ref(),
|
||||
None,
|
||||
);
|
||||
disasm.execute()?;
|
||||
let assembly = disasm.assembly.clone();
|
||||
let mut name = None;
|
||||
let mut pre_is_mess = false;
|
||||
let mut pre_is_enter = false;
|
||||
let mut string_stack = Vec::new();
|
||||
let mut message = String::new();
|
||||
let mut pre_enter_name = String::new();
|
||||
for cmd in assembly.iter() {
|
||||
let is_enter = cmd.code == CsicEnter;
|
||||
if cmd.code == CsicLoad {
|
||||
disasm.stream.pos = cmd.addr as usize + 1;
|
||||
let csom = disasm.read_csom()?;
|
||||
let csvt = disasm.read_csvt()?;
|
||||
if csom == CsomImmediate && csvt == CsvtString {
|
||||
let text = disasm.get_string_literal()?;
|
||||
string_stack.insert(0, text);
|
||||
if string_stack.len() > 8 {
|
||||
string_stack.pop();
|
||||
}
|
||||
}
|
||||
} else if cmd.code == CsicCall {
|
||||
disasm.stream.pos = cmd.addr as usize + 1;
|
||||
let csom = disasm.read_csom()?;
|
||||
let num_args = disasm.stream.read_i32()?;
|
||||
let func_name = WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE)?.0;
|
||||
let mut is_mess = false;
|
||||
if num_args == 1 {
|
||||
if func_name == "SceneTitle" {
|
||||
if string_stack.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"SceneTitle call without string argument at {:08X}",
|
||||
cmd.addr
|
||||
));
|
||||
}
|
||||
messages
|
||||
.entry(key.clone())
|
||||
.or_insert_with(|| Vec::new())
|
||||
.push(Message::new(string_stack[0].clone(), None));
|
||||
} else if func_name == "Mess" {
|
||||
is_mess = true;
|
||||
if string_stack.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Mess call without string argument at {:08X}",
|
||||
cmd.addr
|
||||
));
|
||||
}
|
||||
if string_stack[0].starts_with("@") {
|
||||
println!(
|
||||
"Skipping message with special tag at {:08X}: {}",
|
||||
cmd.addr, string_stack[0]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
message.push_str(string_stack[0].as_str());
|
||||
}
|
||||
} else if num_args == 2 {
|
||||
if func_name == "Talk" {
|
||||
if string_stack.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Talk call without string argument at {:08X}",
|
||||
cmd.addr
|
||||
));
|
||||
}
|
||||
name = Some(string_stack[0].clone());
|
||||
} else if func_name == "AddSelect" {
|
||||
if string_stack.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"AddSelect call without string argument at {:08X}",
|
||||
cmd.addr
|
||||
));
|
||||
}
|
||||
messages
|
||||
.entry(key.clone())
|
||||
.or_insert_with(|| Vec::new())
|
||||
.push(Message::new(string_stack[0].clone(), None));
|
||||
}
|
||||
} else if num_args == 0 && csom == CsomAuto && func_name == "ScenarioEnter" {
|
||||
if pre_is_enter {
|
||||
key = pre_enter_name.clone();
|
||||
} else {
|
||||
key = "global".to_string();
|
||||
}
|
||||
}
|
||||
if pre_is_mess && !is_mess {
|
||||
messages
|
||||
.entry(key.clone())
|
||||
.or_insert_with(|| Vec::new())
|
||||
.push(Message::new(message.clone(), name.take()));
|
||||
message.clear();
|
||||
}
|
||||
pre_is_mess = is_mess;
|
||||
} else if is_enter {
|
||||
disasm.stream.pos = cmd.addr as usize + 1;
|
||||
let name = WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE)?.0;
|
||||
let num_args = disasm.stream.read_i32()?;
|
||||
if num_args == 0 {
|
||||
pre_enter_name = name.clone();
|
||||
}
|
||||
}
|
||||
pre_is_enter = is_enter;
|
||||
}
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
pub fn export_all(&self) -> Result<Vec<String>> {
|
||||
let mut disasm = ECSExecutionImageDisassembler::new(
|
||||
self.image.to_ref(),
|
||||
self.ext_const_str.as_ref(),
|
||||
None,
|
||||
);
|
||||
disasm.execute()?;
|
||||
let mut messages = Vec::new();
|
||||
let assembly = disasm.assembly.clone();
|
||||
for cmd in assembly.iter() {
|
||||
if cmd.code == CsicLoad {
|
||||
disasm.stream.pos = cmd.addr as usize + 1;
|
||||
let csom = disasm.read_csom()?;
|
||||
let csvt = disasm.read_csvt()?;
|
||||
if csom == CsomImmediate && csvt == CsvtString {
|
||||
let text = disasm.get_string_literal()?;
|
||||
messages.push(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(messages)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ mod types;
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::encoding::*;
|
||||
use anyhow::Result;
|
||||
use img::ECSExecutionImage;
|
||||
|
||||
@@ -56,36 +57,72 @@ impl ScriptBuilder for CSXScriptBuilder {
|
||||
#[derive(Debug)]
|
||||
pub struct CSXScript {
|
||||
img: ECSExecutionImage,
|
||||
disasm: bool,
|
||||
custom_yaml: bool,
|
||||
}
|
||||
|
||||
impl CSXScript {
|
||||
pub fn new(buf: Vec<u8>, _config: &ExtraConfig) -> Result<Self> {
|
||||
pub fn new(buf: Vec<u8>, config: &ExtraConfig) -> Result<Self> {
|
||||
let reader = MemReader::new(buf);
|
||||
let img = ECSExecutionImage::new(reader)?;
|
||||
Ok(Self { img })
|
||||
Ok(Self {
|
||||
img,
|
||||
disasm: config.entis_gls_csx_diasm,
|
||||
custom_yaml: config.custom_yaml,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Script for CSXScript {
|
||||
fn default_output_script_type(&self) -> OutputScriptType {
|
||||
OutputScriptType::Custom
|
||||
OutputScriptType::Json
|
||||
}
|
||||
|
||||
fn is_output_supported(&self, output: OutputScriptType) -> bool {
|
||||
matches!(output, OutputScriptType::Custom)
|
||||
fn is_output_supported(&self, _output: OutputScriptType) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_format_type(&self) -> FormatOptions {
|
||||
FormatOptions::None
|
||||
}
|
||||
|
||||
fn custom_output_extension<'a>(&'a self) -> &'a str {
|
||||
"s"
|
||||
fn extract_messages(&self) -> Result<Vec<Message>> {
|
||||
self.img.export()
|
||||
}
|
||||
|
||||
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))?;
|
||||
fn multiple_message_files(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn extract_multiple_messages(&self) -> Result<std::collections::HashMap<String, Vec<Message>>> {
|
||||
self.img.export_multi()
|
||||
}
|
||||
|
||||
fn custom_output_extension<'a>(&'a self) -> &'a str {
|
||||
if self.disasm {
|
||||
"d.txt"
|
||||
} else if self.custom_yaml {
|
||||
"yaml"
|
||||
} else {
|
||||
"json"
|
||||
}
|
||||
}
|
||||
|
||||
fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
|
||||
if self.disasm {
|
||||
let file = crate::utils::files::write_file(filename)?;
|
||||
self.img.disasm(Box::new(file))?;
|
||||
} else {
|
||||
let messages = self.img.export_all()?;
|
||||
let s = if self.custom_yaml {
|
||||
serde_yaml_ng::to_string(&messages)?
|
||||
} else {
|
||||
serde_json::to_string_pretty(&messages)?
|
||||
};
|
||||
let s = encode_string(encoding, &s, false)?;
|
||||
let mut file = crate::utils::files::write_file(filename)?;
|
||||
file.write_all(&s)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user