From f1e35c904c73f228ef1400c019835808455f0aaa Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 7 Jun 2025 18:18:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20BGI=20=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E5=AF=BC=E5=85=A5=E9=87=8D=E5=A4=8D=E5=AD=97=E7=AC=A6=E4=B8=B2?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81=EF=BC=9B=E6=96=B0=E5=A2=9E=20WriteA?= =?UTF-8?q?t=20=E7=89=B9=E6=80=A7=E4=BB=A5=E6=94=AF=E6=8C=81=E5=9C=A8?= =?UTF-8?q?=E7=89=B9=E5=AE=9A=E5=81=8F=E7=A7=BB=E5=86=99=E5=85=A5=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/args.rs | 5 ++ src/ext/io.rs | 82 +++++++++++++++++ src/main.rs | 4 +- src/scripts/bgi/parser.rs | 6 ++ src/scripts/bgi/script.rs | 184 ++++++++++++++++++++++++++++++++++++-- src/types.rs | 2 + 6 files changed, 277 insertions(+), 6 deletions(-) diff --git a/src/args.rs b/src/args.rs index 9152b80..d1f1e06 100644 --- a/src/args.rs +++ b/src/args.rs @@ -75,6 +75,11 @@ pub struct Arg { #[arg(long, global = true)] /// The path to the Escude enum script file (enum_scr.bin) pub escude_enum_scr: Option, + #[cfg(feature = "bgi")] + #[arg(long, action = ArgAction::SetTrue, global = true)] + /// Duplicate same strings when importing into BGI scripts. + /// Enable this will cause BGI scripts to become very large. + pub bgi_import_duplicate: bool, #[command(subcommand)] /// Command pub command: Command, diff --git a/src/ext/io.rs b/src/ext/io.rs index 1ff590a..db08763 100644 --- a/src/ext/io.rs +++ b/src/ext/io.rs @@ -771,6 +771,88 @@ impl WriteExt for T { } } +pub trait WriteAt { + fn write_at(&mut self, offset: usize, buf: &[u8]) -> Result; + fn write_all_at(&mut self, offset: usize, buf: &[u8]) -> Result<()>; + + fn write_u8_at(&mut self, offset: usize, value: u8) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_u16_at(&mut self, offset: usize, value: u16) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_u16_be_at(&mut self, offset: usize, value: u16) -> Result<()> { + self.write_all_at(offset, &value.to_be_bytes()) + } + fn write_u32_at(&mut self, offset: usize, value: u32) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_u32_be_at(&mut self, offset: usize, value: u32) -> Result<()> { + self.write_all_at(offset, &value.to_be_bytes()) + } + fn write_u64_at(&mut self, offset: usize, value: u64) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_u64_be_at(&mut self, offset: usize, value: u64) -> Result<()> { + self.write_all_at(offset, &value.to_be_bytes()) + } + fn write_u128_at(&mut self, offset: usize, value: u128) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_u128_be_at(&mut self, offset: usize, value: u128) -> Result<()> { + self.write_all_at(offset, &value.to_be_bytes()) + } + fn write_i8_at(&mut self, offset: usize, value: i8) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_i16_at(&mut self, offset: usize, value: i16) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_i16_be_at(&mut self, offset: usize, value: i16) -> Result<()> { + self.write_all_at(offset, &value.to_be_bytes()) + } + fn write_i32_at(&mut self, offset: usize, value: i32) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_i32_be_at(&mut self, offset: usize, value: i32) -> Result<()> { + self.write_all_at(offset, &value.to_be_bytes()) + } + fn write_i64_at(&mut self, offset: usize, value: i64) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_i64_be_at(&mut self, offset: usize, value: i64) -> Result<()> { + self.write_all_at(offset, &value.to_be_bytes()) + } + fn write_i128_at(&mut self, offset: usize, value: i128) -> Result<()> { + self.write_all_at(offset, &value.to_le_bytes()) + } + fn write_i128_be_at(&mut self, offset: usize, value: i128) -> Result<()> { + self.write_all_at(offset, &value.to_be_bytes()) + } + + fn write_cstring_at(&mut self, offset: usize, value: &CString) -> Result<()> { + self.write_all_at(offset, value.as_bytes_with_nul()) + } +} + +impl WriteAt for T { + fn write_at(&mut self, offset: usize, buf: &[u8]) -> Result { + let current_pos = self.stream_position()?; + self.seek(SeekFrom::Start(offset as u64))?; + let bytes_written = self.write(buf)?; + self.seek(SeekFrom::Start(current_pos))?; + Ok(bytes_written) + } + + fn write_all_at(&mut self, offset: usize, buf: &[u8]) -> Result<()> { + let current_pos = self.stream_position()?; + self.seek(SeekFrom::Start(offset as u64))?; + self.write_all(buf)?; + self.seek(SeekFrom::Start(current_pos))?; + Ok(()) + } +} + pub struct MemReader { pub data: Vec, pub pos: usize, diff --git a/src/main.rs b/src/main.rs index fbfe512..27ca2cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1056,9 +1056,11 @@ fn main() { #[cfg(feature = "circus")] circus_mes_type: arg.circus_mes_type.clone(), #[cfg(feature = "escude-arc")] - escude_fake_compress: arg.escude_fake_compress.clone(), + escude_fake_compress: arg.escude_fake_compress, #[cfg(feature = "escude")] escude_enum_scr: arg.escude_enum_scr.clone(), + #[cfg(feature = "bgi")] + bgi_import_duplicate: arg.bgi_import_duplicate, }; match &arg.command { args::Command::Export { input, output } => { diff --git a/src/scripts/bgi/parser.rs b/src/scripts/bgi/parser.rs index 56d607d..45c945f 100644 --- a/src/scripts/bgi/parser.rs +++ b/src/scripts/bgi/parser.rs @@ -221,6 +221,12 @@ pub struct BGIString { pub typ: BGIStringType, } +impl BGIString { + pub fn is_internal(&self) -> bool { + matches!(self.typ, BGIStringType::Internal) + } +} + pub struct V0Parser<'a> { buf: MemReaderRef<'a>, largest_code_address_pperand_encountered: usize, diff --git a/src/scripts/bgi/script.rs b/src/scripts/bgi/script.rs index 5c78fd5..8d34698 100644 --- a/src/scripts/bgi/script.rs +++ b/src/scripts/bgi/script.rs @@ -4,6 +4,7 @@ use crate::scripts::base::*; use crate::types::*; use crate::utils::encoding::{decode_to_string, encode_string}; use anyhow::Result; +use std::collections::BTreeMap; #[derive(Debug)] pub struct BGIScriptBuilder {} @@ -52,6 +53,7 @@ pub struct BGIScript { strings: Vec, is_v1: bool, offset: usize, + import_duplicate: bool, } impl std::fmt::Debug for BGIScript { @@ -63,7 +65,7 @@ impl std::fmt::Debug for BGIScript { } impl BGIScript { - pub fn new(data: Vec, encoding: Encoding, _config: &ExtraConfig) -> Result { + pub fn new(data: Vec, encoding: Encoding, config: &ExtraConfig) -> Result { let data = MemReader::new(data); if data.data.starts_with(b"BurikoCompiledScriptVer1.00\0") { let mut parser = V1Parser::new(data.to_ref(), encoding)?; @@ -76,6 +78,7 @@ impl BGIScript { strings, is_v1: true, offset, + import_duplicate: config.bgi_import_duplicate, }) } else { let mut parser = V0Parser::new(data.to_ref()); @@ -87,6 +90,7 @@ impl BGIScript { strings, is_v1: false, offset: 0, + import_duplicate: config.bgi_import_duplicate, }) } } @@ -138,11 +142,181 @@ impl Script for BGIScript { fn import_messages<'a>( &'a self, - _messages: Vec, - _filename: Box, - _encoding: Encoding, - _replacement: Option<&'a ReplacementTable>, + messages: Vec, + mut file: Box, + encoding: Encoding, + replacement: Option<&'a ReplacementTable>, ) -> Result<()> { + if !self.import_duplicate { + let mut str_map = BTreeMap::new(); + let mut mes = messages.iter(); + let mut cur_mes = mes.next(); + for curs in self.strings.iter() { + if !curs.is_internal() { + if cur_mes.is_none() { + cur_mes = mes.next(); + } + } + if str_map.contains_key(&curs.address) { + continue; + } + let nmes = match curs.typ { + BGIStringType::Internal => self.read_string(curs.address)?, + BGIStringType::Name => match &cur_mes { + Some(m) => { + if let Some(name) = &m.name { + let mut name = name.clone(); + if let Some(replacement) = replacement { + for (key, value) in replacement.map.iter() { + name = name.replace(key, value); + } + } + name + } else { + return Err(anyhow::anyhow!("Name is missing for message.")); + } + } + None => return Err(anyhow::anyhow!("No enough messages.")), + }, + BGIStringType::Message => { + let mes = match &cur_mes { + Some(m) => { + let mut message = m.message.clone(); + if let Some(replacement) = replacement { + for (key, value) in replacement.map.iter() { + message = message.replace(key, value); + } + } + message + } + None => return Err(anyhow::anyhow!("No enough messages.")), + }; + cur_mes.take(); + mes + } + }; + str_map.insert(curs.address, nmes); + } + if cur_mes.is_some() || mes.next().is_some() { + return Err(anyhow::anyhow!("Some messages were not processed.")); + } + let mut old_offset = 0; + let mut new_offset = 0; + let mut new_address_map = BTreeMap::new(); + for (address, nmes) in str_map { + let bgi_str_old_offset = address + self.offset; + if old_offset < bgi_str_old_offset { + file.write_all(&self.data.data[old_offset..bgi_str_old_offset])?; + new_offset += bgi_str_old_offset - old_offset; + old_offset = bgi_str_old_offset; + } + let old_str_len = self + .data + .cpeek_cstring_at(bgi_str_old_offset)? + .as_bytes_with_nul() + .len(); + let nmes = encode_string(encoding, &nmes, false)?; + file.write_all(&nmes)?; + file.write_u8(0)?; // null terminator + let new_address = new_offset - self.offset; + new_address_map.insert(address, new_address); + old_offset += old_str_len; + new_offset += nmes.len() + 1; // +1 for null terminator + } + if old_offset < self.data.data.len() { + file.write_all(&self.data.data[old_offset..])?; + } + for bgis in self.strings.iter() { + let new_address = new_address_map.get(&bgis.address).ok_or(anyhow::anyhow!( + "Address {} not found in new address map.", + bgis.address + ))?; + file.write_u32_at(bgis.offset, *new_address as u32)?; + } + return Ok(()); + } + let mut mes = messages.iter(); + let mut cur_mes = None; + let mut strs = self.strings.iter(); + let mut nstrs = Vec::new(); + let mut cur_str = strs.next(); + let mut old_offset = 0; + let mut new_offset = 0; + while let Some(curs) = cur_str { + if !curs.is_internal() { + if cur_mes.is_none() { + cur_mes = mes.next(); + } + } + let bgi_str_old_offset = curs.address + self.offset; + if old_offset < bgi_str_old_offset { + file.write_all(&self.data.data[old_offset..bgi_str_old_offset])?; + new_offset += bgi_str_old_offset - old_offset; + old_offset = bgi_str_old_offset; + } + let old_str_len = self + .data + .cpeek_cstring_at(curs.address + self.offset)? + .as_bytes_with_nul() + .len(); + let nmes = match curs.typ { + BGIStringType::Internal => self.read_string(curs.address)?, + BGIStringType::Name => match &cur_mes { + Some(m) => { + if let Some(name) = &m.name { + let mut name = name.clone(); + if let Some(replacement) = replacement { + for (key, value) in replacement.map.iter() { + name = name.replace(key, value); + } + } + name + } else { + return Err(anyhow::anyhow!("Name is missing for message.")); + } + } + None => return Err(anyhow::anyhow!("No enough messages.")), + }, + BGIStringType::Message => { + let mes = match &cur_mes { + Some(m) => { + let mut message = m.message.clone(); + if let Some(replacement) = replacement { + for (key, value) in replacement.map.iter() { + message = message.replace(key, value); + } + } + message + } + None => return Err(anyhow::anyhow!("No enough messages.")), + }; + cur_mes.take(); + mes + } + }; + let nmes = encode_string(encoding, &nmes, false)?; + file.write_all(&nmes)?; + file.write_u8(0)?; + let new_str_len = nmes.len() + 1; // +1 for null terminator + let new_address = new_offset - self.offset; + nstrs.push(BGIString { + offset: curs.offset, + address: new_address, + typ: curs.typ.clone(), + }); + old_offset += old_str_len; + new_offset += new_str_len; + cur_str = strs.next(); + } + if cur_mes.is_some() || mes.next().is_some() { + return Err(anyhow::anyhow!("Some messages were not processed.")); + } + for str in nstrs { + file.write_u32_at(str.offset, str.address as u32)?; + } + if old_offset < self.data.data.len() { + file.write_all(&self.data.data[old_offset..])?; + } Ok(()) } } diff --git a/src/types.rs b/src/types.rs index 29ac040..5877e17 100644 --- a/src/types.rs +++ b/src/types.rs @@ -193,6 +193,8 @@ pub struct ExtraConfig { pub escude_fake_compress: bool, #[cfg(feature = "escude")] pub escude_enum_scr: Option, + #[cfg(feature = "bgi")] + pub bgi_import_duplicate: bool, } #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]