msg_tool\scripts\bgi/
bp.rs1use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::{decode_to_string, encode_string};
6use anyhow::Result;
7use std::io::{Seek, SeekFrom};
8
9#[derive(Debug)]
10pub struct BGIBpScriptBuilder {}
12
13impl BGIBpScriptBuilder {
14 pub fn new() -> Self {
16 BGIBpScriptBuilder {}
17 }
18}
19
20impl ScriptBuilder for BGIBpScriptBuilder {
21 fn default_encoding(&self) -> Encoding {
22 Encoding::Cp932
23 }
24
25 fn build_script(
26 &self,
27 buf: Vec<u8>,
28 _filename: &str,
29 encoding: Encoding,
30 _archive_encoding: Encoding,
31 config: &ExtraConfig,
32 _archive: Option<&Box<dyn Script>>,
33 ) -> Result<Box<dyn Script>> {
34 Ok(Box::new(BGIBpScript::new(buf, encoding, config)?))
35 }
36
37 fn extensions(&self) -> &'static [&'static str] {
38 &["_bp"]
39 }
40
41 fn script_type(&self) -> &'static ScriptType {
42 &ScriptType::BGIBp
43 }
44}
45
46#[derive(Debug)]
47struct BpString {
48 offset_pos: usize,
49 text_offset: u16,
50}
51
52#[derive(Debug)]
53pub struct BGIBpScript {
55 data: MemReader,
56 header_size: u32,
57 strings: Vec<BpString>,
58 encoding: Encoding,
59}
60
61impl BGIBpScript {
62 pub fn new(buf: Vec<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
68 let mut reader = MemReader::new(buf);
69 let header_size = reader.read_u32()?;
70 let instr_size = reader.read_u32()?;
71 if header_size as usize + instr_size as usize != reader.data.len() {
72 return Err(anyhow::anyhow!("Invalid bp script file size"));
73 }
74 let mut last_instr_pos = 0;
75 reader.seek(SeekFrom::Start(header_size as u64))?;
76 let max_instr_len = reader.data.len() - 4;
77 while reader.pos < max_instr_len {
78 let instr = reader.cpeek_u32()?;
79 if instr == 0x17 {
80 last_instr_pos = reader.pos;
81 reader.pos += 4;
82 } else {
83 reader.pos += 1;
84 }
85 }
86 if last_instr_pos == 0 {
87 last_instr_pos = reader.data.len();
89 }
90 reader.seek(SeekFrom::Start(header_size as u64))?;
91 let mut strings = Vec::new();
92 while reader.pos < last_instr_pos {
93 let ins = reader.read_u8()?;
94 if ins == 5 {
95 let text_offset = reader.peek_u16()?;
96 let text_address = reader.pos + text_offset as usize - 1;
97 if text_address >= last_instr_pos
98 && text_address < reader.data.len()
99 && (text_address == last_instr_pos || reader.data[text_address - 1] == 0)
100 {
101 strings.push(BpString {
102 offset_pos: reader.pos,
103 text_offset,
104 });
105 reader.pos += 2;
106 }
107 }
108 }
109 return Ok(BGIBpScript {
110 data: reader,
111 header_size,
112 strings,
113 encoding,
114 });
115 }
116}
117
118impl Script for BGIBpScript {
119 fn default_output_script_type(&self) -> OutputScriptType {
120 OutputScriptType::Json
121 }
122
123 fn default_format_type(&self) -> FormatOptions {
124 FormatOptions::None
125 }
126
127 fn extract_messages(&self) -> Result<Vec<Message>> {
128 let mut messages = Vec::new();
129 for i in self.strings.iter() {
130 let text_address = i.offset_pos + i.text_offset as usize - 1;
131 let str = self.data.cpeek_cstring_at(text_address as u64)?;
133 let str = decode_to_string(self.encoding, str.as_bytes(), true)?;
134 messages.push(Message {
135 name: None,
136 message: str,
137 });
138 }
139 Ok(messages)
140 }
141
142 fn import_messages<'a>(
143 &'a self,
144 messages: Vec<Message>,
145 mut file: Box<dyn WriteSeek + 'a>,
146 _filename: &str,
147 encoding: Encoding,
148 replacement: Option<&'a ReplacementTable>,
149 ) -> Result<()> {
150 if messages.len() != self.strings.len() {
151 return Err(anyhow::anyhow!(
152 "Number of messages does not match the number of strings in the script"
153 ));
154 }
155 file.write_all(&self.data.data)?;
156 let mut new_pos = self.data.data.len();
157 for (i, mes) in self.strings.iter().zip(messages) {
158 let text_address = i.offset_pos + i.text_offset as usize - 1;
159 let old_str_len = self
160 .data
161 .cpeek_cstring_at(text_address as u64)?
162 .as_bytes_with_nul()
163 .len();
164 let mut str = mes.message;
165 if let Some(replacement) = replacement {
166 for (key, value) in replacement.map.iter() {
167 str = str.replace(key, value);
168 }
169 }
170 let mut str = encode_string(encoding, &str, false)?;
171 str.push(0); let new_str_len = str.len();
173 if new_str_len > old_str_len {
174 file.write_all(&str)?;
175 let new_text_offset = (new_pos - i.offset_pos + 1) as u16;
176 file.write_u16_at(i.offset_pos as u64, new_text_offset)?;
177 new_pos += new_str_len;
178 } else {
179 file.write_all_at(text_address as u64, &str)?;
180 }
181 }
182 let new_instr_size = (new_pos - self.header_size as usize) as u32;
183 file.write_u32_at(4, new_instr_size)?;
184 Ok(())
185 }
186}