From c075d4593f94ec02d3df9ff2cced4a109eb1bca3 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sun, 18 Jan 2026 16:13:48 +0800 Subject: [PATCH] Allow to import strings in custom mode --- src/ext/io.rs | 38 +++++ src/scripts/entis_gls/csx/v1/img.rs | 236 ++++++++++++++++++++++++++ src/scripts/entis_gls/csx/v1/mod.rs | 24 +++ src/scripts/entis_gls/csx/v1/types.rs | 66 +++++++ 4 files changed, 364 insertions(+) diff --git a/src/ext/io.rs b/src/ext/io.rs index cf7a949..62d2769 100644 --- a/src/ext/io.rs +++ b/src/ext/io.rs @@ -1121,6 +1121,14 @@ pub trait WriteExt { fn write_i128(&mut self, value: i128) -> Result<()>; /// Writes an [i128] to the writer in big-endian order. fn write_i128_be(&mut self, value: i128) -> Result<()>; + /// Writes a [f32] to the writer in little-endian order. + fn write_f32(&mut self, value: f32) -> Result<()>; + /// Writes a [f32] to the writer in big-endian order. + fn write_f32_be(&mut self, value: f32) -> Result<()>; + /// Writes a [f64] to the writer in little-endian order. + fn write_f64(&mut self, value: f64) -> Result<()>; + /// Writes a [f64] to the writer in big-endian order. + fn write_f64_be(&mut self, value: f64) -> Result<()>; /// Writes a C-style string (null-terminated) to the writer. fn write_cstring(&mut self, value: &CString) -> Result<()>; @@ -1131,6 +1139,8 @@ pub trait WriteExt { big: bool, encoding: Encoding, ) -> Result<()>; + /// Writes data from a reader to the writer. + fn write_from(&mut self, reader: &mut R, offset: u64, len: u64) -> Result<()>; } impl WriteExt for T { @@ -1188,6 +1198,18 @@ impl WriteExt for T { fn write_i128_be(&mut self, value: i128) -> Result<()> { self.write_all(&value.to_be_bytes()) } + fn write_f32(&mut self, value: f32) -> Result<()> { + self.write_all(&value.to_le_bytes()) + } + fn write_f32_be(&mut self, value: f32) -> Result<()> { + self.write_all(&value.to_be_bytes()) + } + fn write_f64(&mut self, value: f64) -> Result<()> { + self.write_all(&value.to_le_bytes()) + } + fn write_f64_be(&mut self, value: f64) -> Result<()> { + self.write_all(&value.to_be_bytes()) + } fn write_cstring(&mut self, value: &CString) -> Result<()> { self.write_all(value.as_bytes_with_nul()) @@ -1203,6 +1225,22 @@ impl WriteExt for T { .pack(self, big, encoding) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) } + + fn write_from(&mut self, reader: &mut R, offset: u64, len: u64) -> Result<()> { + reader.seek(SeekFrom::Start(offset))?; + let mut remaining = len; + let mut buffer = [0u8; 8192]; + while remaining > 0 { + let to_read = std::cmp::min(remaining, buffer.len() as u64) as usize; + let bytes_read = reader.read(&mut buffer[..to_read])?; + if bytes_read == 0 { + break; // EOF reached + } + self.write_all(&buffer[..bytes_read])?; + remaining -= bytes_read as u64; + } + Ok(()) + } } /// A trait to help to write data to a writer at a specific offset. diff --git a/src/scripts/entis_gls/csx/v1/img.rs b/src/scripts/entis_gls/csx/v1/img.rs index 721a1e1..cf06f71 100644 --- a/src/scripts/entis_gls/csx/v1/img.rs +++ b/src/scripts/entis_gls/csx/v1/img.rs @@ -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, + ) -> 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, + ) -> 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) -> 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) -> 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, + 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.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 = + 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(()) + } } diff --git a/src/scripts/entis_gls/csx/v1/mod.rs b/src/scripts/entis_gls/csx/v1/mod.rs index 51ae7e8..2d17555 100644 --- a/src/scripts/entis_gls/csx/v1/mod.rs +++ b/src/scripts/entis_gls/csx/v1/mod.rs @@ -126,4 +126,28 @@ impl Script for CSXScript { } Ok(()) } + + fn custom_import<'a>( + &'a self, + custom_filename: &'a str, + file: Box, + _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 = if self.custom_yaml { + serde_yaml_ng::from_str(&s)? + } else { + serde_json::from_str(&s)? + }; + self.img.import_all(messages, file)?; + Ok(()) + } + } } diff --git a/src/scripts/entis_gls/csx/v1/types.rs b/src/scripts/entis_gls/csx/v1/types.rs index 0cd3353..5eff37f 100644 --- a/src/scripts/entis_gls/csx/v1/types.rs +++ b/src/scripts/entis_gls/csx/v1/types.rs @@ -170,6 +170,20 @@ pub struct FunctionNameList { pub items: Vec, } +impl Deref for FunctionNameList { + type Target = Vec; + + 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(&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)]