mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-07-08 01:31:53 +08:00
Add support for Entis GLS engine CSX Script (.csx)
This commit is contained in:
@@ -151,6 +151,7 @@ msg-tool create -t <script-type> <input> <output>
|
|||||||
| Script Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Custom Export | Custom Import | Create | Remarks |
|
| Script Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Custom Export | Custom Import | Create | Remarks |
|
||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
| `entis-gls` | `entis-gls` | Entis GLS engine XML Script (.srcxml) | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | |
|
| `entis-gls` | `entis-gls` | Entis GLS engine XML Script (.srcxml) | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | |
|
||||||
|
| `entis-gls-csx` | `entis-gls` | Entis GLS engine CSX Script (.csx) | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | |
|
||||||
### Escu:de
|
### Escu:de
|
||||||
| Script Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Custom Export | Custom Import | Create | Remarks |
|
| Script Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Custom Export | Custom Import | Create | Remarks |
|
||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use super::super::base::*;
|
|||||||
use super::disasm::*;
|
use super::disasm::*;
|
||||||
use super::types::*;
|
use super::types::*;
|
||||||
use crate::ext::io::*;
|
use crate::ext::io::*;
|
||||||
|
use crate::ext::vec::*;
|
||||||
use crate::scripts::base::*;
|
use crate::scripts::base::*;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
use crate::utils::struct_pack::*;
|
use crate::utils::struct_pack::*;
|
||||||
@@ -1176,20 +1177,789 @@ impl ECSImage for ECSExecutionImage {
|
|||||||
|
|
||||||
fn import<'a>(
|
fn import<'a>(
|
||||||
&self,
|
&self,
|
||||||
_messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
_file: Box<dyn WriteSeek + 'a>,
|
file: Box<dyn WriteSeek + 'a>,
|
||||||
_replacement: Option<&'a ReplacementTable>,
|
replacement: Option<&'a ReplacementTable>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
Err(anyhow::anyhow!("Import not implemented for CSX v2"))
|
let mut cloned = self.clone();
|
||||||
|
let mut mess = messages.iter();
|
||||||
|
let mut mes = mess.next();
|
||||||
|
let mut disasm = ECSExecutionImageDisassembler::new(
|
||||||
|
self.image.to_ref(),
|
||||||
|
&self.section_function,
|
||||||
|
&self.section_func_info,
|
||||||
|
&self.section_import_native_func,
|
||||||
|
&self.section_class_info,
|
||||||
|
&self.section_const_string,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
disasm.execute()?;
|
||||||
|
let mut constr_map: HashMap<String, u32> = cloned
|
||||||
|
.section_const_string
|
||||||
|
.strings
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| (s.string.0.clone(), i as u32))
|
||||||
|
.collect();
|
||||||
|
let mut assembly = disasm.assembly.clone();
|
||||||
|
let mut new_image = MemWriter::new();
|
||||||
|
let mut index = 0;
|
||||||
|
let mut dumped_index = 0;
|
||||||
|
let mut string_stack = Vec::new();
|
||||||
|
let mut stacks = Vec::new();
|
||||||
|
let len = assembly.len();
|
||||||
|
while index < len {
|
||||||
|
let cmd = assembly[index].clone();
|
||||||
|
if cmd.code == CsicLoad {
|
||||||
|
disasm.stream.pos = cmd.addr as usize + 1;
|
||||||
|
let csom = disasm.read_csom()?;
|
||||||
|
let csvt = disasm.read_csvt()?;
|
||||||
|
let is_string = csom == CsomImmediate && csvt == CsvtString;
|
||||||
|
if is_string {
|
||||||
|
let s = disasm.get_string_literal()?;
|
||||||
|
string_stack.push((Some(index), s));
|
||||||
|
}
|
||||||
|
stacks.push(is_string);
|
||||||
|
} else if matches!(
|
||||||
|
cmd.code,
|
||||||
|
CsicCall
|
||||||
|
| CsicCallMember
|
||||||
|
| CsicCallNativeFunction
|
||||||
|
| CsicEnter
|
||||||
|
| CsicElementIndirect
|
||||||
|
) {
|
||||||
|
string_stack.clear();
|
||||||
|
stacks.clear();
|
||||||
|
while dumped_index <= index {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
} else if cmd.code == CsicOperate {
|
||||||
|
disasm.stream.pos = cmd.addr as usize + 1;
|
||||||
|
let csot = disasm.read_csot()?;
|
||||||
|
if csot == CsotAdd {
|
||||||
|
if string_stack.len() >= 2
|
||||||
|
&& index >= 2
|
||||||
|
&& stacks.len() >= 2
|
||||||
|
&& stacks[stacks.len() - 1]
|
||||||
|
&& stacks[stacks.len() - 2]
|
||||||
|
{
|
||||||
|
let s2 = string_stack.pop().unwrap().1;
|
||||||
|
let s1 = string_stack.pop().unwrap().1;
|
||||||
|
let s = s1 + &s2;
|
||||||
|
string_stack.push((None, s));
|
||||||
|
stacks.pop();
|
||||||
|
// Remove the two previous load commands and replace with this one
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(is_str) = stacks.pop() {
|
||||||
|
if is_str && string_stack.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"String stack is empty when processing Operate at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if is_str {
|
||||||
|
string_stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if cmd.code == CsicExCall {
|
||||||
|
disasm.stream.pos = cmd.addr as usize + 1;
|
||||||
|
let arg_count = disasm.stream.read_i32()?;
|
||||||
|
let csom = disasm.read_csom()?;
|
||||||
|
let csvt = disasm.read_csvt()?;
|
||||||
|
if csom == CsomImmediate {
|
||||||
|
let func_name = if csvt == CsvtString {
|
||||||
|
disasm.get_string_literal()?
|
||||||
|
} else if csvt == CsvtInteger {
|
||||||
|
let func_address = disasm.stream.read_u32()?;
|
||||||
|
let func = disasm.func_map.get(&func_address).ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Function address 0x{:08X} not found in ExCall",
|
||||||
|
func_address
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
func.name.0.clone()
|
||||||
|
} else {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Unexpected CSVT for function name in ExCall"
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if func_name == "WitchWizard::OutMsg" && arg_count == 8 {
|
||||||
|
if string_stack.len() < 2 {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"String stack has less than 2 items when processing OutMsg at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if string_stack.len() > 2 {
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: String stack has more than 2 items when processing OutMsg at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
);
|
||||||
|
crate::COUNTER.inc_warning();
|
||||||
|
}
|
||||||
|
let name = string_stack[0].clone();
|
||||||
|
let message = string_stack[1].clone();
|
||||||
|
let message_idx = message.0.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Cannot replace constructed string for message at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if !name.1.is_empty() {
|
||||||
|
let name_idx = name.0.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Cannot replace constructed string for message name at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut name = match mes {
|
||||||
|
Some(m) => match &m.name {
|
||||||
|
Some(n) => n.clone(),
|
||||||
|
None => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"No name available for OutMsg at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"No more messages available for OutMsg at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(repl) = replacement {
|
||||||
|
for (k, v) in repl.map.iter() {
|
||||||
|
name = name.replace(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let constr_idx = if let Some(&idx) = constr_map.get(&name) {
|
||||||
|
idx
|
||||||
|
} else {
|
||||||
|
// Add new string to const string section
|
||||||
|
let idx = cloned.section_const_string.strings.len() as u32;
|
||||||
|
cloned.section_const_string.strings.push(ConstStringEntry {
|
||||||
|
string: WideString(name.clone()),
|
||||||
|
refs: DWordArray { data: Vec::new() },
|
||||||
|
});
|
||||||
|
constr_map.insert(name.clone(), idx);
|
||||||
|
idx
|
||||||
|
};
|
||||||
|
while dumped_index < name_idx {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(
|
||||||
|
&mut disasm.stream,
|
||||||
|
tcmd.addr as u64,
|
||||||
|
tcmd.size as u64,
|
||||||
|
)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
let name_cmd = &mut assembly[name_idx];
|
||||||
|
name_cmd.new_addr = new_image.pos as u32;
|
||||||
|
// Write new load command for name
|
||||||
|
new_image.write_u8(CsicLoad as u8)?;
|
||||||
|
new_image.write_u8(CsomImmediate as u8)?;
|
||||||
|
new_image.write_u8(CsvtString as u8)?;
|
||||||
|
new_image.write_u32(0x80000000)?;
|
||||||
|
new_image.write_u32(constr_idx)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
let mut message = match mes {
|
||||||
|
Some(m) => m.message.clone(),
|
||||||
|
None => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"No more messages available for OutMsg at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mes = mess.next();
|
||||||
|
if let Some(repl) = replacement {
|
||||||
|
for (k, v) in repl.map.iter() {
|
||||||
|
message = message.replace(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while dumped_index < message_idx {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(
|
||||||
|
&mut disasm.stream,
|
||||||
|
tcmd.addr as u64,
|
||||||
|
tcmd.size as u64,
|
||||||
|
)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
let message_cmd = &mut assembly[message_idx];
|
||||||
|
message_cmd.new_addr = new_image.pos as u32;
|
||||||
|
// Write new load command for message
|
||||||
|
new_image.write_u8(CsicLoad as u8)?;
|
||||||
|
new_image.write_u8(CsomImmediate as u8)?;
|
||||||
|
new_image.write_u8(CsvtString as u8)?;
|
||||||
|
new_image.write_u32(0x80000000)?;
|
||||||
|
let constr_idx = if let Some(&idx) = constr_map.get(&message) {
|
||||||
|
idx
|
||||||
|
} else {
|
||||||
|
// Add new string to const string section
|
||||||
|
let idx = cloned.section_const_string.strings.len() as u32;
|
||||||
|
cloned.section_const_string.strings.push(ConstStringEntry {
|
||||||
|
string: WideString(message.clone()),
|
||||||
|
refs: DWordArray { data: Vec::new() },
|
||||||
|
});
|
||||||
|
constr_map.insert(message.clone(), idx);
|
||||||
|
idx
|
||||||
|
};
|
||||||
|
new_image.write_u32(constr_idx)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string_stack.clear();
|
||||||
|
stacks.clear();
|
||||||
|
while dumped_index <= index {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
} else if cmd.code == CsicCallNativeMember {
|
||||||
|
disasm.stream.pos = cmd.addr as usize + 1;
|
||||||
|
let arg_count = disasm.stream.read_i32()?;
|
||||||
|
let class_index = disasm.stream.read_u32()?;
|
||||||
|
let class = self
|
||||||
|
.section_class_info
|
||||||
|
.infos
|
||||||
|
.get(class_index as usize)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Invalid class info index: {} (max {}) at {:08x}",
|
||||||
|
class_index,
|
||||||
|
self.section_class_info.infos.len(),
|
||||||
|
cmd.addr
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let func_index = disasm.stream.read_u32()?;
|
||||||
|
let func = class.method_info.get(func_index as usize).ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Invalid method info index: {} (max {}) at {:08x}",
|
||||||
|
func_index,
|
||||||
|
class.method_info.len(),
|
||||||
|
cmd.addr
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let func_name = func.prototype_info.global_name.0.as_str();
|
||||||
|
if func_name == "Window::CreateDisplay@2" && arg_count == 5 {
|
||||||
|
if string_stack.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"String stack is empty when processing Window::CreateDisplay@2"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if string_stack.len() > 1 {
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: String stack has more than 1 item when processing Window::CreateDisplay@2 at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
);
|
||||||
|
crate::COUNTER.inc_warning();
|
||||||
|
}
|
||||||
|
let message = string_stack[0].clone();
|
||||||
|
let message_idx = message.0.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Cannot replace constructed string for message at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut message = match mes {
|
||||||
|
Some(m) => m.message.clone(),
|
||||||
|
None => {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"No more messages available for Window::CreateDisplay@2 at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mes = mess.next();
|
||||||
|
if let Some(repl) = replacement {
|
||||||
|
for (k, v) in repl.map.iter() {
|
||||||
|
message = message.replace(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while dumped_index < message_idx {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(
|
||||||
|
&mut disasm.stream,
|
||||||
|
tcmd.addr as u64,
|
||||||
|
tcmd.size as u64,
|
||||||
|
)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
let message_cmd = &mut assembly[message_idx];
|
||||||
|
message_cmd.new_addr = new_image.pos as u32;
|
||||||
|
// Write new load command for message
|
||||||
|
new_image.write_u8(CsicLoad as u8)?;
|
||||||
|
new_image.write_u8(CsomImmediate as u8)?;
|
||||||
|
new_image.write_u8(CsvtString as u8)?;
|
||||||
|
new_image.write_u32(0x80000000)?;
|
||||||
|
let constr_idx = if let Some(&idx) = constr_map.get(&message) {
|
||||||
|
idx
|
||||||
|
} else {
|
||||||
|
// Add new string to const string section
|
||||||
|
let idx = cloned.section_const_string.strings.len() as u32;
|
||||||
|
cloned.section_const_string.strings.push(ConstStringEntry {
|
||||||
|
string: WideString(message.clone()),
|
||||||
|
refs: DWordArray { data: Vec::new() },
|
||||||
|
});
|
||||||
|
constr_map.insert(message.clone(), idx);
|
||||||
|
idx
|
||||||
|
};
|
||||||
|
new_image.write_u32(constr_idx)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
string_stack.clear();
|
||||||
|
stacks.clear();
|
||||||
|
while dumped_index <= index {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
while dumped_index < len {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
if mes.is_some() || mess.next().is_some() {
|
||||||
|
return Err(anyhow::anyhow!("Not all messages were used."));
|
||||||
|
}
|
||||||
|
let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
|
||||||
|
assembly.iter().map(|c| (c.addr, c)).collect();
|
||||||
|
Self::fix_image(&assembly, &mut disasm, &mut new_image, &commands)?;
|
||||||
|
cloned.image = MemReader::new(new_image.into_inner());
|
||||||
|
cloned.fix_references(&commands)?;
|
||||||
|
cloned.save(file)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_multi<'a>(
|
fn import_multi<'a>(
|
||||||
&self,
|
&self,
|
||||||
_messages: HashMap<String, Vec<Message>>,
|
mut messages: HashMap<String, Vec<Message>>,
|
||||||
_file: Box<dyn WriteSeek + 'a>,
|
file: Box<dyn WriteSeek + 'a>,
|
||||||
_replacement: Option<&'a ReplacementTable>,
|
replacement: Option<&'a ReplacementTable>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
Err(anyhow::anyhow!("Import multi not implemented for CSX v2"))
|
let mut cloned = self.clone();
|
||||||
|
let mut key = String::from("global");
|
||||||
|
let mut disasm = ECSExecutionImageDisassembler::new(
|
||||||
|
self.image.to_ref(),
|
||||||
|
&self.section_function,
|
||||||
|
&self.section_func_info,
|
||||||
|
&self.section_import_native_func,
|
||||||
|
&self.section_class_info,
|
||||||
|
&self.section_const_string,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
disasm.execute()?;
|
||||||
|
let mut constr_map: HashMap<String, u32> = cloned
|
||||||
|
.section_const_string
|
||||||
|
.strings
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| (s.string.0.clone(), i as u32))
|
||||||
|
.collect();
|
||||||
|
let mut assembly = disasm.assembly.clone();
|
||||||
|
let mut new_image = MemWriter::new();
|
||||||
|
let mut index = 0;
|
||||||
|
let mut dumped_index = 0;
|
||||||
|
let mut string_stack = Vec::new();
|
||||||
|
let mut stacks = Vec::new();
|
||||||
|
let len = assembly.len();
|
||||||
|
while index < len {
|
||||||
|
let cmd = assembly[index].clone();
|
||||||
|
if cmd.code == CsicLoad {
|
||||||
|
disasm.stream.pos = cmd.addr as usize + 1;
|
||||||
|
let csom = disasm.read_csom()?;
|
||||||
|
let csvt = disasm.read_csvt()?;
|
||||||
|
let is_string = csom == CsomImmediate && csvt == CsvtString;
|
||||||
|
if is_string {
|
||||||
|
let s = disasm.get_string_literal()?;
|
||||||
|
string_stack.push((Some(index), s));
|
||||||
|
}
|
||||||
|
stacks.push(is_string);
|
||||||
|
} else if matches!(
|
||||||
|
cmd.code,
|
||||||
|
CsicCall
|
||||||
|
| CsicCallMember
|
||||||
|
| CsicCallNativeFunction
|
||||||
|
| CsicEnter
|
||||||
|
| CsicElementIndirect
|
||||||
|
) {
|
||||||
|
string_stack.clear();
|
||||||
|
stacks.clear();
|
||||||
|
while dumped_index <= index {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
} else if cmd.code == CsicOperate {
|
||||||
|
disasm.stream.pos = cmd.addr as usize + 1;
|
||||||
|
let csot = disasm.read_csot()?;
|
||||||
|
if csot == CsotAdd {
|
||||||
|
if string_stack.len() >= 2
|
||||||
|
&& index >= 2
|
||||||
|
&& stacks.len() >= 2
|
||||||
|
&& stacks[stacks.len() - 1]
|
||||||
|
&& stacks[stacks.len() - 2]
|
||||||
|
{
|
||||||
|
let s2 = string_stack.pop().unwrap().1;
|
||||||
|
let s1 = string_stack.pop().unwrap().1;
|
||||||
|
let s = s1 + &s2;
|
||||||
|
string_stack.push((None, s));
|
||||||
|
stacks.pop();
|
||||||
|
// Remove the two previous load commands and replace with this one
|
||||||
|
index += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(is_str) = stacks.pop() {
|
||||||
|
if is_str && string_stack.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"String stack is empty when processing Operate at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if is_str {
|
||||||
|
string_stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if cmd.code == CsicExCall {
|
||||||
|
disasm.stream.pos = cmd.addr as usize + 1;
|
||||||
|
let arg_count = disasm.stream.read_i32()?;
|
||||||
|
let csom = disasm.read_csom()?;
|
||||||
|
let csvt = disasm.read_csvt()?;
|
||||||
|
if csom == CsomImmediate {
|
||||||
|
let func_name = if csvt == CsvtString {
|
||||||
|
disasm.get_string_literal()?
|
||||||
|
} else if csvt == CsvtInteger {
|
||||||
|
let func_address = disasm.stream.read_u32()?;
|
||||||
|
let func = disasm.func_map.get(&func_address).ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Function address 0x{:08X} not found in ExCall",
|
||||||
|
func_address
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
func.name.0.clone()
|
||||||
|
} else {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Unexpected CSVT for function name in ExCall"
|
||||||
|
));
|
||||||
|
};
|
||||||
|
if func_name == "WitchWizard::SetPastLabel"
|
||||||
|
&& arg_count == 2
|
||||||
|
&& !self.no_part_label
|
||||||
|
{
|
||||||
|
if string_stack.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"String stack is empty when processing SetPastLabel"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if string_stack.len() > 1 {
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: String stack has more than 1 item when processing SetPastLabel at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
);
|
||||||
|
crate::COUNTER.inc_warning();
|
||||||
|
}
|
||||||
|
key = string_stack[0].1.clone();
|
||||||
|
} else if func_name == "WitchWizard::OutMsg" && arg_count == 8 {
|
||||||
|
if string_stack.len() < 2 {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"String stack has less than 2 items when processing OutMsg at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if string_stack.len() > 2 {
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: String stack has more than 2 items when processing OutMsg at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
);
|
||||||
|
crate::COUNTER.inc_warning();
|
||||||
|
}
|
||||||
|
let name = string_stack[0].clone();
|
||||||
|
let message = string_stack[1].clone();
|
||||||
|
let message_idx = message.0.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Cannot replace constructed string for message at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
if !name.1.is_empty() {
|
||||||
|
let name_idx = name.0.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Cannot replace constructed string for message name at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut name = messages
|
||||||
|
.get_mut(&key)
|
||||||
|
.and_then(|messages| messages.first_mut().map(|m| m.name.take()))
|
||||||
|
.flatten()
|
||||||
|
.ok_or(anyhow::anyhow!(
|
||||||
|
"No available name message at {:08X}.",
|
||||||
|
cmd.addr
|
||||||
|
))?;
|
||||||
|
if let Some(repl) = replacement {
|
||||||
|
for (k, v) in repl.map.iter() {
|
||||||
|
name = name.replace(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let constr_idx = if let Some(&idx) = constr_map.get(&name) {
|
||||||
|
idx
|
||||||
|
} else {
|
||||||
|
// Add new string to const string section
|
||||||
|
let idx = cloned.section_const_string.strings.len() as u32;
|
||||||
|
cloned.section_const_string.strings.push(ConstStringEntry {
|
||||||
|
string: WideString(name.clone()),
|
||||||
|
refs: DWordArray { data: Vec::new() },
|
||||||
|
});
|
||||||
|
constr_map.insert(name.clone(), idx);
|
||||||
|
idx
|
||||||
|
};
|
||||||
|
while dumped_index < name_idx {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(
|
||||||
|
&mut disasm.stream,
|
||||||
|
tcmd.addr as u64,
|
||||||
|
tcmd.size as u64,
|
||||||
|
)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
let name_cmd = &mut assembly[name_idx];
|
||||||
|
name_cmd.new_addr = new_image.pos as u32;
|
||||||
|
// Write new load command for name
|
||||||
|
new_image.write_u8(CsicLoad as u8)?;
|
||||||
|
new_image.write_u8(CsomImmediate as u8)?;
|
||||||
|
new_image.write_u8(CsvtString as u8)?;
|
||||||
|
new_image.write_u32(0x80000000)?;
|
||||||
|
new_image.write_u32(constr_idx)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
let mut message = messages
|
||||||
|
.get_mut(&key)
|
||||||
|
.and_then(|messages| messages.pop_first())
|
||||||
|
.ok_or(anyhow::anyhow!(
|
||||||
|
"No available message for AddSelect at {:08X}.",
|
||||||
|
cmd.addr
|
||||||
|
))?
|
||||||
|
.message;
|
||||||
|
if let Some(repl) = replacement {
|
||||||
|
for (k, v) in repl.map.iter() {
|
||||||
|
message = message.replace(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while dumped_index < message_idx {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(
|
||||||
|
&mut disasm.stream,
|
||||||
|
tcmd.addr as u64,
|
||||||
|
tcmd.size as u64,
|
||||||
|
)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
let message_cmd = &mut assembly[message_idx];
|
||||||
|
message_cmd.new_addr = new_image.pos as u32;
|
||||||
|
// Write new load command for message
|
||||||
|
new_image.write_u8(CsicLoad as u8)?;
|
||||||
|
new_image.write_u8(CsomImmediate as u8)?;
|
||||||
|
new_image.write_u8(CsvtString as u8)?;
|
||||||
|
new_image.write_u32(0x80000000)?;
|
||||||
|
let constr_idx = if let Some(&idx) = constr_map.get(&message) {
|
||||||
|
idx
|
||||||
|
} else {
|
||||||
|
// Add new string to const string section
|
||||||
|
let idx = cloned.section_const_string.strings.len() as u32;
|
||||||
|
cloned.section_const_string.strings.push(ConstStringEntry {
|
||||||
|
string: WideString(message.clone()),
|
||||||
|
refs: DWordArray { data: Vec::new() },
|
||||||
|
});
|
||||||
|
constr_map.insert(message.clone(), idx);
|
||||||
|
idx
|
||||||
|
};
|
||||||
|
new_image.write_u32(constr_idx)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
} else if func_name == "WitchWizard::SetCurrentScriptName" && arg_count == 2 {
|
||||||
|
if string_stack.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"String stack is empty when processing SetCurrentScriptName"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if string_stack.len() > 1 {
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: String stack has more than 1 item when processing SetCurrentScriptName at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
);
|
||||||
|
crate::COUNTER.inc_warning();
|
||||||
|
}
|
||||||
|
key = string_stack[0].1.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string_stack.clear();
|
||||||
|
stacks.clear();
|
||||||
|
while dumped_index <= index {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
} else if cmd.code == CsicCallNativeMember {
|
||||||
|
disasm.stream.pos = cmd.addr as usize + 1;
|
||||||
|
let arg_count = disasm.stream.read_i32()?;
|
||||||
|
let class_index = disasm.stream.read_u32()?;
|
||||||
|
let class = self
|
||||||
|
.section_class_info
|
||||||
|
.infos
|
||||||
|
.get(class_index as usize)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Invalid class info index: {} (max {}) at {:08x}",
|
||||||
|
class_index,
|
||||||
|
self.section_class_info.infos.len(),
|
||||||
|
cmd.addr
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let func_index = disasm.stream.read_u32()?;
|
||||||
|
let func = class.method_info.get(func_index as usize).ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Invalid method info index: {} (max {}) at {:08x}",
|
||||||
|
func_index,
|
||||||
|
class.method_info.len(),
|
||||||
|
cmd.addr
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let func_name = func.prototype_info.global_name.0.as_str();
|
||||||
|
if func_name == "Window::CreateDisplay@2" && arg_count == 5 {
|
||||||
|
if string_stack.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"String stack is empty when processing Window::CreateDisplay@2"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if string_stack.len() > 1 {
|
||||||
|
eprintln!(
|
||||||
|
"WARNING: String stack has more than 1 item when processing Window::CreateDisplay@2 at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
);
|
||||||
|
crate::COUNTER.inc_warning();
|
||||||
|
}
|
||||||
|
let message = string_stack[0].clone();
|
||||||
|
let message_idx = message.0.ok_or_else(|| {
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"Cannot replace constructed string for message at {:08X}",
|
||||||
|
cmd.addr,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut message = messages
|
||||||
|
.get_mut(&key)
|
||||||
|
.and_then(|messages| messages.pop_first())
|
||||||
|
.ok_or(anyhow::anyhow!(
|
||||||
|
"No available message for CreateDisplay at {:08X}.",
|
||||||
|
cmd.addr,
|
||||||
|
))?
|
||||||
|
.message;
|
||||||
|
if let Some(repl) = replacement {
|
||||||
|
for (k, v) in repl.map.iter() {
|
||||||
|
message = message.replace(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while dumped_index < message_idx {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(
|
||||||
|
&mut disasm.stream,
|
||||||
|
tcmd.addr as u64,
|
||||||
|
tcmd.size as u64,
|
||||||
|
)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
let message_cmd = &mut assembly[message_idx];
|
||||||
|
message_cmd.new_addr = new_image.pos as u32;
|
||||||
|
// Write new load command for message
|
||||||
|
new_image.write_u8(CsicLoad as u8)?;
|
||||||
|
new_image.write_u8(CsomImmediate as u8)?;
|
||||||
|
new_image.write_u8(CsvtString as u8)?;
|
||||||
|
new_image.write_u32(0x80000000)?;
|
||||||
|
let constr_idx = if let Some(&idx) = constr_map.get(&message) {
|
||||||
|
idx
|
||||||
|
} else {
|
||||||
|
// Add new string to const string section
|
||||||
|
let idx = cloned.section_const_string.strings.len() as u32;
|
||||||
|
cloned.section_const_string.strings.push(ConstStringEntry {
|
||||||
|
string: WideString(message.clone()),
|
||||||
|
refs: DWordArray { data: Vec::new() },
|
||||||
|
});
|
||||||
|
constr_map.insert(message.clone(), idx);
|
||||||
|
idx
|
||||||
|
};
|
||||||
|
new_image.write_u32(constr_idx)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
string_stack.clear();
|
||||||
|
stacks.clear();
|
||||||
|
while dumped_index <= index {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
while dumped_index < len {
|
||||||
|
let tcmd = &mut assembly[dumped_index];
|
||||||
|
tcmd.new_addr = new_image.pos as u32;
|
||||||
|
// Copy original command
|
||||||
|
new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
|
||||||
|
dumped_index += 1;
|
||||||
|
}
|
||||||
|
for (s, mes) in messages {
|
||||||
|
if !mes.is_empty() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Not all messages were used for key '{}', {} remaining.",
|
||||||
|
s,
|
||||||
|
mes.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
|
||||||
|
assembly.iter().map(|c| (c.addr, c)).collect();
|
||||||
|
Self::fix_image(&assembly, &mut disasm, &mut new_image, &commands)?;
|
||||||
|
cloned.image = MemReader::new(new_image.into_inner());
|
||||||
|
cloned.fix_references(&commands)?;
|
||||||
|
cloned.save(file)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_all<'a>(&self, messages: Vec<String>, file: Box<dyn WriteSeek + 'a>) -> Result<()> {
|
fn import_all<'a>(&self, messages: Vec<String>, file: Box<dyn WriteSeek + 'a>) -> Result<()> {
|
||||||
|
|||||||
Reference in New Issue
Block a user