From ce5aef41219a24fd8b8240ab8bf996d9407b5794 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Thu, 22 Jan 2026 15:23:16 +0800 Subject: [PATCH] Add support to import all texts --- src/scripts/entis_gls/csx/v2/disasm.rs | 26 ++ src/scripts/entis_gls/csx/v2/img.rs | 456 ++++++++++++++++++++++++- src/scripts/entis_gls/csx/v2/types.rs | 2 + 3 files changed, 481 insertions(+), 3 deletions(-) diff --git a/src/scripts/entis_gls/csx/v2/disasm.rs b/src/scripts/entis_gls/csx/v2/disasm.rs index af58ee8..5d4c5eb 100644 --- a/src/scripts/entis_gls/csx/v2/disasm.rs +++ b/src/scripts/entis_gls/csx/v2/disasm.rs @@ -69,9 +69,14 @@ impl<'a> ECSExecutionImageDisassembler<'a> { let mut ordered = self.func_info.functions.clone(); ordered.sort_by_key(|f| f.header.address); let img_len = self.stream.data.len() as u32; + let mut pre_end = 0; for e in ordered { // ignore inline functions if e.header.flags == 0x11 { + eprintln!( + "Skipping inline function at {:#x}, {}", + e.header.address, e.name.0 + ); continue; } let start = e.header.address; @@ -88,7 +93,26 @@ impl<'a> ECSExecutionImageDisassembler<'a> { crate::COUNTER.inc_warning(); continue; } + if pre_end != 0 && pre_end < start { + self.assembly.push(ECSExecutionImageCommandRecord { + code: CodeSystemReserved, + addr: pre_end, + size: start - pre_end, + new_addr: pre_end, + internal: true, + }); + } self.execute_range(start, end)?; + pre_end = end; + } + if pre_end != 0 && pre_end < img_len { + self.assembly.push(ECSExecutionImageCommandRecord { + code: CodeSystemReserved, + addr: pre_end, + size: img_len - pre_end, + new_addr: pre_end, + internal: true, + }); } if self.func_info.functions.is_empty() { // older format without function info @@ -109,6 +133,7 @@ impl<'a> ECSExecutionImageDisassembler<'a> { fn execute_range(&mut self, start: u32, end: u32) -> Result<()> { self.stream.pos = start as usize; let end = end as usize; + // println!("Disassembling range {:#08x} - {:#08x}", start, end); while self.stream.pos < end { self.addr = self.stream.pos as u32; let code = self.stream.read_u8()?; @@ -239,6 +264,7 @@ impl<'a> ECSExecutionImageDisassembler<'a> { addr: self.addr, size, new_addr: self.addr, + internal: false, }); } Ok(()) diff --git a/src/scripts/entis_gls/csx/v2/img.rs b/src/scripts/entis_gls/csx/v2/img.rs index 47b5af1..9c6c221 100644 --- a/src/scripts/entis_gls/csx/v2/img.rs +++ b/src/scripts/entis_gls/csx/v2/img.rs @@ -8,7 +8,7 @@ use crate::types::*; use crate::utils::struct_pack::*; use anyhow::Result; use std::collections::HashMap; -use std::io::Seek; +use std::io::{Seek, Write}; const ID_HEADER: u64 = 0x2020726564616568; // header const ID_IMAGE: u64 = 0x2020206567616D69; @@ -376,6 +376,385 @@ impl ECSExecutionImage { no_part_label: config.entis_gls_csx_no_part_label, }) } + + fn fix_image<'a, 'b>( + assembly: &ECSExecutionImageAssembly, + disasm: &mut ECSExecutionImageDisassembler<'a>, + writer: &mut MemWriter, + commands: &HashMap, + ) -> Result<()> { + for cmd in assembly.iter() { + if cmd.code == CsicEnter { + disasm.stream.pos = cmd.addr as usize + 1; + let name_length = disasm.stream.read_u32()?; + if name_length != 0x80000000 { + disasm.stream.pos += name_length as usize * 2; + } else { + disasm.stream.pos += 4; + } + let num_args = disasm.stream.read_i32()?; + if num_args == -1 { + let _flag = disasm.stream.read_u8()?; + let offset = disasm.stream.pos as i64 - cmd.addr as i64; + let original_addr = disasm.stream.read_i32()? as i64 + disasm.stream.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 matches!(cmd.code, CsicJump | CodeJumpOffset32) { + disasm.stream.pos = cmd.addr as usize + 1; + let offset = disasm.stream.pos as i64 - cmd.addr as i64; + let original_addr = disasm.stream.read_i32()? as i64 + disasm.stream.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 {:?} instruction fixup at {:08X}", + original_addr as u32, + cmd.code, + 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 matches!(cmd.code, CsicCJump | CodeCJumpOffset32 | CodeCNJumpOffset32) { + disasm.stream.pos = cmd.addr as usize + 2; + let offset = disasm.stream.pos as i64 - cmd.addr as i64; + let original_addr = disasm.stream.read_i32()? as i64 + disasm.stream.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 {:?} instruction fixup at {:08X}", + original_addr as u32, + cmd.code, + 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 == 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 && csvt == CsvtInteger { + let offset = disasm.stream.pos as i64 - cmd.addr as i64; + let addr = disasm.stream.read_u32()?; + let target_cmd = commands.get(&addr).ok_or_else(|| anyhow::anyhow!( + "Cannot find target command at address {:08X} for ExCall instruction fixup at {:08X}", + addr, + cmd.addr + ))?; + let new_addr = target_cmd.new_addr; + writer.write_u32_at(cmd.new_addr as u64 + offset as u64, new_addr)?; + } + } else if cmd.code == CodeCallImm32 { + disasm.stream.pos = cmd.addr as usize + 1; + let offset = disasm.stream.pos as i64 - cmd.addr as i64; + let addr = disasm.stream.read_u32()?; + let target_cmd = commands.get(&addr).ok_or_else(|| anyhow::anyhow!( + "Cannot find target command at address {:08X} for CallImm32 instruction fixup at {:08X}", + addr, + cmd.addr + ))?; + let new_addr = target_cmd.new_addr; + writer.write_u32_at(cmd.new_addr as u64 + offset as u64, new_addr)?; + } + } + Ok(()) + } + + fn fix_references( + &mut self, + commands: &HashMap, + ) -> Result<()> { + let mut list: Vec = commands.iter().map(|(&k, _)| k).collect(); + list.sort(); + for cmd in self.section_function.prologue.iter_mut() { + let ocmd = *cmd; + if let Some(tcmd) = commands.get(&ocmd) { + *cmd = tcmd.new_addr; + } else { + let pre_one_idx = match list.binary_search(&ocmd) { + Ok(idx) => idx, + Err(idx) => { + if idx == 0 { + idx + } else { + idx - 1 + } + } + }; + let tcmd = &commands[&list[pre_one_idx]]; + if !tcmd.internal || tcmd.size + tcmd.addr < ocmd { + return Err(anyhow::anyhow!( + "Cannot find target command at address {:08X} for PIF prologue fixup", + ocmd + )); + } + let offset = tcmd.new_addr as i64 - tcmd.addr as i64; + *cmd = (ocmd as i64 + offset) as u32; + } + } + for cmd in self.section_function.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.section_function.func_names.iter_mut() { + let ocmd = func.address; + func.address = commands + .get(&ocmd) + .ok_or_else(|| { + anyhow::anyhow!( + "Cannot find target command at address {:08X} for function names list fixup", + ocmd + ) + })? + .new_addr; + } + for cmd in self.section_init_naked_func.naked_prologue.iter_mut() { + let ocmd = *cmd; + *cmd = commands + .get(&ocmd) + .ok_or_else(|| { + anyhow::anyhow!( + "Cannot find target command at address {:08X} for NNF prologue fixup", + ocmd + ) + })? + .new_addr; + } + for cmd in self.section_init_naked_func.naked_epilogue.iter_mut() { + let ocmd = *cmd; + *cmd = commands + .get(&ocmd) + .ok_or_else(|| { + anyhow::anyhow!( + "Cannot find target command at address {:08X} for NNF epilogue fixup", + ocmd + ) + })? + .new_addr; + } + for func in self.section_func_info.functions.iter_mut() { + let ocmd = func.header.address; + func.header.address = commands + .get(&ocmd) + .ok_or_else(|| { + anyhow::anyhow!( + "Cannot find target command at address {:08X} for function info list fixup", + ocmd + ) + })? + .new_addr; + if func.header.bytes != u32::MAX { + let end_ocmd = ocmd + func.header.bytes; + let end_tcmd = commands + .get(&end_ocmd) + .ok_or_else(|| { + anyhow::anyhow!( + "Cannot find target command at address {:08X} for function info list fixup", + end_ocmd + ) + })?.new_addr; + func.header.bytes = end_tcmd - func.header.address; + } + } + Ok(()) + } + + fn save<'a>(&self, mut writer: Box) -> Result<()> { + self.file_header + .pack(&mut writer, false, Encoding::Utf8, &None)?; + if self.section_header.header_size > 0 { + let mut mem = MemWriter::new(); + self.section_header + .pack(&mut mem, false, Encoding::Utf8, &None)?; + writer.write_u64(ID_HEADER)?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + } + writer.write_u64(ID_IMAGE)?; + writer.write_u64(self.image.data.len() as u64)?; + writer.write_all(&self.image.data)?; + if let Some(img_global) = &self.image_global { + writer.write_u64(ID_IMAGE_GLOBAL)?; + writer.write_u64(img_global.data.len() as u64)?; + writer.write_all(&img_global.data)?; + } + if let Some(img_const) = &self.image_const { + writer.write_u64(ID_IMAGE_CONST)?; + writer.write_u64(img_const.data.len() as u64)?; + writer.write_all(&img_const.data)?; + } + if let Some(img_shared) = &self.image_shared { + writer.write_u64(ID_IMAGE_SHARED)?; + writer.write_u64(img_shared.data.len() as u64)?; + writer.write_all(&img_shared.data)?; + } + writer.write_u64(ID_CLASS_INFO)?; + let mut mem = MemWriter::new(); + self.section_class_info.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + writer.write_u64(ID_FUNCTION)?; + let mut mem = MemWriter::new(); + self.section_function.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + writer.write_u64(ID_INIT_NAKED_FUNC)?; + let mut mem = MemWriter::new(); + self.section_init_naked_func.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + writer.write_u64(ID_FUNC_INFO)?; + let mut mem = MemWriter::new(); + self.section_func_info.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + if let Some(section_symbol_info) = &self.section_symbol_info { + writer.write_u64(ID_SYMBOL_INFO)?; + let mut mem = MemWriter::new(); + section_symbol_info.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + } + if let Some(section_global) = &self.section_global { + writer.write_u64(ID_GLOBAL)?; + let mut mem = MemWriter::new(); + section_global.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + } + if let Some(section_data) = &self.section_data { + writer.write_u64(ID_DATA)?; + let mut mem = MemWriter::new(); + section_data.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + } + writer.write_u64(ID_CONST_STRING)?; + let mut mem = MemWriter::new(); + self.section_const_string.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + if let Some(section_link_info) = &self.section_link_info { + writer.write_u64(ID_LINK_INFO)?; + let mut mem = MemWriter::new(); + section_link_info.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + } + if let Some(section_link_info_ex) = &self.section_link_info_ex { + writer.write_u64(ID_LINK_INFO_EX)?; + let mut mem = MemWriter::new(); + section_link_info_ex.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + } + if let Some(section_ref_func) = &self.section_ref_func { + writer.write_u64(ID_REF_FUNC)?; + let mut mem = MemWriter::new(); + section_ref_func.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + } + if let Some(section_ref_code) = &self.section_ref_code { + writer.write_u64(ID_REF_CODE)?; + let mut mem = MemWriter::new(); + section_ref_code.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + } + if let Some(section_ref_class) = &self.section_ref_class { + writer.write_u64(ID_REF_CLASS)?; + let mut mem = MemWriter::new(); + section_ref_class.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + } + writer.write_u64(ID_IMPORT_NATIVE_FUNC)?; + let mut mem = MemWriter::new(); + self.section_import_native_func.pack( + &mut mem, + false, + Encoding::Utf8, + &Some(Box::new(self.section_header.clone())), + )?; + writer.write_u64(mem.data.len() as u64)?; + writer.write_all(&mem.into_inner())?; + Ok(()) + } } impl ECSImage for ECSExecutionImage { @@ -718,7 +1097,78 @@ impl ECSImage for ECSExecutionImage { Err(anyhow::anyhow!("Import multi not implemented for CSX v2")) } - fn import_all<'a>(&self, _messages: Vec, _file: Box) -> Result<()> { - Err(anyhow::anyhow!("Import all not implemented for CSX v2")) + fn import_all<'a>(&self, messages: Vec, file: Box) -> 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.section_function, + &self.section_func_info, + &self.section_import_native_func, + &self.section_class_info, + &self.section_const_string, + None, + ); + disasm.execute()?; + let mut conststr_map: HashMap = 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(); + 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 s = match mes { + Some(s) => s, + None => { + return Err(anyhow::anyhow!( + "Not enough messages for import_all at {:08X}", + cmd.addr, + )); + } + }; + mes = mess.next(); + let constr_idx = if let Some(&idx) = conststr_map.get(&s) { + 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(s.clone()), + refs: DWordArray { data: Vec::new() }, + }); + conststr_map.insert(s.clone(), idx); + idx + }; + 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)?; + continue; + } + } + // Copy original command + new_image.write_from(&mut disasm.stream, cmd.addr as u64, cmd.size as u64)?; + } + if mes.is_some() || mess.next().is_some() { + return Err(anyhow::anyhow!("Too many messages for import_all")); + } + let commands: HashMap = + 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(()) } } diff --git a/src/scripts/entis_gls/csx/v2/types.rs b/src/scripts/entis_gls/csx/v2/types.rs index a30d617..db01d7e 100644 --- a/src/scripts/entis_gls/csx/v2/types.rs +++ b/src/scripts/entis_gls/csx/v2/types.rs @@ -1218,6 +1218,8 @@ pub struct ECSExecutionImageCommandRecord { pub addr: u32, pub size: u32, pub new_addr: u32, + /// true if this command is internal (not from original code) + pub internal: bool, } #[derive(Clone, Debug)]