Allow to import strings in custom mode

This commit is contained in:
2026-01-18 16:13:48 +08:00
parent 763ace6149
commit c075d4593f
4 changed files with 364 additions and 0 deletions

View File

@@ -1,6 +1,7 @@
use super::disasm::*;
use super::types::*;
use crate::ext::io::*;
use crate::scripts::base::*;
use crate::types::*;
use crate::utils::struct_pack::*;
use anyhow::Result;
@@ -179,6 +180,186 @@ impl ECSExecutionImage {
})
}
fn fix_image<'a, 'b>(
assembly: &ECSExecutionImageAssembly,
mut reader: MemReaderRef<'a>,
writer: &mut MemWriter,
commands: &HashMap<u32, &'b ECSExecutionImageCommandRecord>,
) -> Result<()> {
for cmd in assembly.iter() {
// Fix Enter Try Catch address offsets
if cmd.code == CsicEnter {
reader.pos = cmd.addr as usize + 1;
let name_length = reader.read_u32()?;
if name_length != 0x80000000 {
reader.pos += name_length as usize * 2;
} else {
reader.pos += 4;
}
let num_args = reader.read_i32()?;
if num_args == -1 {
let _flag = reader.read_u8()?;
let offset = reader.pos as i64 - cmd.addr as i64;
let original_addr = reader.read_i32()? as i64 + reader.pos as i64;
let target_cmd = commands.get(&(original_addr as u32)).ok_or_else(|| anyhow::anyhow!(
"Cannot find target command at address {:08X} for Enter instruction fixup at {:08X}",
original_addr as u32,
cmd.addr
))?;
let new_addr = target_cmd.new_addr as i64 - cmd.new_addr as i64 - offset - 4;
writer.write_i32_at(cmd.new_addr as u64 + offset as u64, new_addr as i32)?;
}
} else if cmd.code == CsicJump {
// Fix Jump address offsets
reader.pos = cmd.addr as usize + 1;
let offset = reader.pos as i64 - cmd.addr as i64;
let original_addr = reader.read_i32()? as i64 + reader.pos as i64;
let target_cmd = commands.get(&(original_addr as u32)).ok_or_else(|| anyhow::anyhow!(
"Cannot find target command at address {:08X} for Jump instruction fixup at {:08X}",
original_addr as u32,
cmd.addr
))?;
let new_addr = target_cmd.new_addr as i64 - cmd.new_addr as i64 - offset - 4;
writer.write_i32_at(cmd.new_addr as u64 + offset as u64, new_addr as i32)?;
} else if cmd.code == CsicCJump {
// Fix CJump address offsets
reader.pos = cmd.addr as usize + 2;
let offset = reader.pos as i64 - cmd.addr as i64;
let original_addr = reader.read_i32()? as i64 + reader.pos as i64;
let target_cmd = commands.get(&(original_addr as u32)).ok_or_else(|| anyhow::anyhow!(
"Cannot find target command at address {:08X} for CJump instruction fixup at {:08X}",
original_addr as u32,
cmd.addr
))?;
let new_addr = target_cmd.new_addr as i64 - cmd.new_addr as i64 - offset - 4;
writer.write_i32_at(cmd.new_addr as u64 + offset as u64, new_addr as i32)?;
}
}
Ok(())
}
fn fix_references(
&mut self,
commands: &HashMap<u32, &ECSExecutionImageCommandRecord>,
) -> Result<()> {
for cmd in self.pif_prologue.iter_mut() {
let ocmd = *cmd;
*cmd = commands
.get(&ocmd)
.ok_or_else(|| {
anyhow::anyhow!(
"Cannot find target command at address {:08X} for PIF prologue fixup",
ocmd
)
})?
.new_addr;
}
for cmd in self.pif_epilogue.iter_mut() {
let ocmd = *cmd;
*cmd = commands
.get(&ocmd)
.ok_or_else(|| {
anyhow::anyhow!(
"Cannot find target command at address {:08X} for PIF epilogue fixup",
ocmd
)
})?
.new_addr;
}
for func in self.function_list.iter_mut() {
let ocmd = func.addr;
func.addr = commands
.get(&ocmd)
.ok_or_else(|| {
anyhow::anyhow!(
"Cannot find target command at address {:08X} for function list fixup",
ocmd
)
})?
.new_addr;
}
Ok(())
}
fn save<'a>(&self, mut writer: Box<dyn Write + 'a>) -> Result<()> {
self.file_header.pack(&mut writer, false, Encoding::Utf8)?;
if let Some(exi_header) = &self.exi_header {
writer.write_u64(0x2020726564616568)?; // header
writer.write_u64(exi_header.len() as u64)?;
writer.write_all(&exi_header)?;
}
writer.write_u64(0x2020206567616D69)?; // image
writer.write_u64(self.image.data.len() as u64)?;
writer.write_all(&self.image.data)?;
writer.write_u64(0x6E6F6974636E7566)?; // function
let mut mem = MemWriter::new();
self.pif_prologue.pack(&mut mem, false, Encoding::Utf8)?;
self.pif_epilogue.pack(&mut mem, false, Encoding::Utf8)?;
self.function_list.pack(&mut mem, false, Encoding::Utf8)?;
let data = mem.into_inner();
writer.write_u64(data.len() as u64)?;
writer.write_all(&data)?;
writer.write_u64(0x20206C61626F6C67)?; // global
let mut mem = MemWriter::new();
let int64 = if let Some(hdr) = &self.header {
hdr.int_base == 64
} else {
false
};
mem.write_u32(self.csg_global.len() as u32)?;
for item in self.csg_global.iter() {
WideString(item.name.clone()).pack(&mut mem, false, Encoding::Utf16LE)?;
item.obj.write_to(&mut mem, int64)?;
}
let data = mem.into_inner();
writer.write_u64(data.len() as u64)?;
writer.write_all(&data)?;
writer.write_u64(0x2020202061746164)?; // data
let mut mem = MemWriter::new();
mem.write_u32(self.csg_data.len() as u32)?;
for item in self.csg_data.iter() {
WideString(item.name.clone()).pack(&mut mem, false, Encoding::Utf16LE)?;
match &item.obj {
ECSObject::Global(g) => {
mem.write_i32(g.len() as i32)?;
for data_item in g.iter() {
WideString(data_item.name.clone()).pack(
&mut mem,
false,
Encoding::Utf16LE,
)?;
data_item.obj.write_to(&mut mem, int64)?;
}
}
_ => {
mem.write_u32(0x80000000)?;
item.obj.write_to(&mut mem, int64)?;
}
}
}
let data = mem.into_inner();
writer.write_u64(data.len() as u64)?;
writer.write_all(&data)?;
if let Some(ext_const_str) = &self.ext_const_str {
writer.write_u64(0x72747374736E6F63)?; // conststr
let mut mem = MemWriter::new();
ext_const_str.pack(&mut mem, false, Encoding::Utf8)?;
let data = mem.into_inner();
writer.write_u64(data.len() as u64)?;
writer.write_all(&data)?;
}
writer.write_u64(0x20666E696B6E696C)?; // linkinf
let mut mem = MemWriter::new();
self.ext_global_ref.pack(&mut mem, false, Encoding::Utf8)?;
self.ext_data_ref.pack(&mut mem, false, Encoding::Utf8)?;
self.imp_global_ref.pack(&mut mem, false, Encoding::Utf8)?;
self.imp_data_ref.pack(&mut mem, false, Encoding::Utf8)?;
let data = mem.into_inner();
writer.write_u64(data.len() as u64)?;
writer.write_all(&data)?;
Ok(())
}
pub fn disasm<'a>(&self, writer: Box<dyn Write + 'a>) -> Result<()> {
let mut disasm = ECSExecutionImageDisassembler::new(
self.image.to_ref(),
@@ -414,4 +595,59 @@ impl ECSExecutionImage {
}
Ok(messages)
}
pub fn import_all<'a>(
&self,
messages: Vec<String>,
file: Box<dyn WriteSeek + 'a>,
) -> Result<()> {
let mut cloned = self.clone();
let mut mess = messages.into_iter();
let mut mes = mess.next();
let mut disasm = ECSExecutionImageDisassembler::new(
self.image.to_ref(),
self.ext_const_str.as_ref(),
None,
);
disasm.execute()?;
let mut assembly = disasm.assembly.clone();
let mut new_image = MemWriter::new();
for cmd in assembly.iter_mut() {
cmd.new_addr = new_image.pos as u32;
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 code: u8 = CsicLoad.into();
let csom: u8 = csom.into();
let csvt: u8 = csvt.into();
let s = match mes.take() {
Some(v) => WideString(v),
None => {
return Err(anyhow::anyhow!(
"Not enough messages to import, ran out at instruction address {:08X}",
cmd.addr
));
}
};
mes = mess.next();
new_image.write_u8(code)?;
new_image.write_u8(csom)?;
new_image.write_u8(csvt)?;
s.pack(&mut new_image, false, Encoding::Utf8)?;
continue;
}
}
// Copy original instruction
new_image.write_from(&mut disasm.stream, cmd.addr as u64, cmd.size as u64)?;
}
let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
assembly.iter().map(|c| (c.addr, c)).collect();
Self::fix_image(&assembly, disasm.stream.clone(), &mut new_image, &commands)?;
cloned.image = MemReader::new(new_image.into_inner());
cloned.fix_references(&commands)?;
cloned.save(file)?;
Ok(())
}
}

View File

@@ -126,4 +126,28 @@ impl Script for CSXScript {
}
Ok(())
}
fn custom_import<'a>(
&'a self,
custom_filename: &'a str,
file: Box<dyn WriteSeek + 'a>,
_encoding: Encoding,
output_encoding: Encoding,
) -> Result<()> {
if self.disasm {
Err(anyhow::anyhow!(
"Importing from disassembly is not supported."
))
} else {
let data = crate::utils::files::read_file(custom_filename)?;
let s = decode_to_string(output_encoding, &data, false)?;
let messages: Vec<String> = if self.custom_yaml {
serde_yaml_ng::from_str(&s)?
} else {
serde_json::from_str(&s)?
};
self.img.import_all(messages, file)?;
Ok(())
}
}
}

View File

@@ -170,6 +170,20 @@ pub struct FunctionNameList {
pub items: Vec<FunctionNameItem>,
}
impl Deref for FunctionNameList {
type Target = Vec<FunctionNameItem>;
fn deref(&self) -> &Self::Target {
&self.items
}
}
impl DerefMut for FunctionNameList {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.items
}
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub enum ECSObject {
@@ -230,6 +244,58 @@ impl ECSObject {
}
}
}
pub fn write_to<W: Write>(&self, writer: &mut W, int64: bool) -> Result<()> {
match self {
ECSObject::ClassInfoObject(name) => {
let obj_type: u8 = CSVariableType::CsvtObject.into();
writer.write_i32(obj_type as i32)?;
WideString(name.clone()).pack(writer, false, Encoding::Utf8)?;
}
ECSObject::Reference => {
let obj_type: u8 = CSVariableType::CsvtReference.into();
writer.write_i32(obj_type as i32)?;
}
ECSObject::Array(items) => {
let obj_type: u8 = CSVariableType::CsvtArray.into();
writer.write_i32(obj_type as i32)?;
writer.write_u32(items.len() as u32)?;
for item in items {
item.write_to(writer, int64)?;
}
}
ECSObject::Hash => {
let obj_type: u8 = CSVariableType::CsvtHash.into();
writer.write_i32(obj_type as i32)?;
}
ECSObject::Integer(val) => {
let obj_type: u8 = CSVariableType::CsvtInteger.into();
writer.write_i32(obj_type as i32)?;
if int64 {
writer.write_i64(*val)?;
} else {
writer.write_i32(*val as i32)?;
}
}
ECSObject::Real(val) => {
let obj_type: u8 = CSVariableType::CsvtReal.into();
writer.write_i32(obj_type as i32)?;
writer.write_f64(*val)?;
}
ECSObject::String(s) => {
let obj_type: u8 = CSVariableType::CsvtString.into();
writer.write_i32(obj_type as i32)?;
WideString(s.clone()).pack(writer, false, Encoding::Utf8)?;
}
_ => {
return Err(anyhow::anyhow!(
"Unsupported ECSObject type for writing: {:?}",
self
));
}
}
Ok(())
}
}
#[derive(Clone, Debug)]