msg_tool\scripts\entis_gls\csx\v1/
img.rs

1use super::super::base::*;
2use super::disasm::*;
3use super::types::*;
4use crate::ext::io::*;
5use crate::ext::vec::*;
6use crate::scripts::base::*;
7use crate::types::*;
8use crate::utils::struct_pack::*;
9use anyhow::Result;
10use std::collections::HashMap;
11use std::io::Write;
12
13use CSInstructionCode::*;
14use CSObjectMode::*;
15use CSVariableType::*;
16
17#[derive(Clone, Debug)]
18#[allow(dead_code)]
19pub struct ECSExecutionImage {
20    file_header: EMCFileHeader,
21    exi_header: Option<Vec<u8>>,
22    header: Option<EXIHeader>,
23    image: MemReader,
24    pif_prologue: DWordArray,
25    pif_epilogue: DWordArray,
26    function_list: FunctionNameList,
27    csg_global: ECSGlobal,
28    csg_data: ECSGlobal,
29    ext_const_str: Option<TaggedRefAddressList>,
30    ext_global_ref: DWordArray,
31    ext_data_ref: DWordArray,
32    imp_global_ref: TaggedRefAddressList,
33    imp_data_ref: TaggedRefAddressList,
34    lf: String,
35}
36
37impl ECSExecutionImage {
38    pub fn new(mut reader: MemReaderRef<'_>, config: &ExtraConfig) -> Result<Self> {
39        let file_header = EMCFileHeader::unpack(&mut reader, false, Encoding::Utf8, &None)?;
40        if file_header.signagure != *b"Entis\x1a\0\0" {
41            return Err(anyhow::anyhow!("Invalid EMC file signature"));
42        }
43        let len = reader.data.len();
44        let mut exi_header = None;
45        let mut header = None;
46        let mut image = None;
47        let mut pif_prologue = None;
48        let mut pif_epilogue = None;
49        let mut function_list = None;
50        let mut csg_global = None;
51        let mut int64 = false;
52        let mut csg_data = None;
53        let mut ext_const_str = None;
54        let mut ext_global_ref = DWordArray::default();
55        let mut ext_data_ref = DWordArray::default();
56        let mut imp_global_ref = TaggedRefAddressList::default();
57        let mut imp_data_ref = TaggedRefAddressList::default();
58        while reader.pos < len {
59            if len - reader.pos < 16 {
60                break;
61            }
62            let id = reader.read_u64()?;
63            if id == 0 {
64                break;
65            }
66            let size = reader.read_u64()?;
67            match id {
68                // header
69                0x2020726564616568 => {
70                    let buf = reader.read_exact_vec(size as usize)?;
71                    {
72                        let mut sread = MemReaderRef::new(&buf);
73                        header = Some(EXIHeader::unpack(&mut sread, false, Encoding::Utf8, &None)?);
74                    }
75                    exi_header = Some(buf);
76                    if let Some(hdr) = &header {
77                        if hdr.int_base == 64 {
78                            int64 = true;
79                        }
80                    }
81                }
82                // image
83                0x2020206567616D69 => {
84                    image = Some(MemReader::new(reader.read_exact_vec(size as usize)?));
85                }
86                // function
87                0x6E6F6974636E7566 => {
88                    pif_prologue = Some(DWordArray::unpack(
89                        &mut reader,
90                        false,
91                        Encoding::Utf8,
92                        &None,
93                    )?);
94                    pif_epilogue = Some(DWordArray::unpack(
95                        &mut reader,
96                        false,
97                        Encoding::Utf8,
98                        &None,
99                    )?);
100                    function_list = Some(FunctionNameList::unpack(
101                        &mut reader,
102                        false,
103                        Encoding::Utf8,
104                        &None,
105                    )?);
106                }
107                // global
108                0x20206C61626F6C67 => {
109                    let count = reader.read_u32()?;
110                    let mut items = Vec::with_capacity(count as usize);
111                    for _ in 0..count {
112                        let name =
113                            WideString::unpack(&mut reader, false, Encoding::Utf16LE, &None)?.0;
114                        let obj = ECSObject::read_from(&mut reader, int64)?;
115                        items.push(ECSObjectItem { name, obj });
116                    }
117                    csg_global = Some(ECSGlobal(items));
118                }
119                // data
120                0x2020202061746164 => {
121                    let count = reader.read_u32()?;
122                    let mut items = Vec::with_capacity(count as usize);
123                    for _ in 0..count {
124                        let name =
125                            WideString::unpack(&mut reader, false, Encoding::Utf16LE, &None)?.0;
126                        let length = reader.read_i32()?;
127                        let obj = if length >= 0 {
128                            let mut datas = Vec::with_capacity(length as usize);
129                            for _ in 0..length {
130                                let name = WideString::unpack(
131                                    &mut reader,
132                                    false,
133                                    Encoding::Utf16LE,
134                                    &None,
135                                )?
136                                .0;
137                                let obj = ECSObject::read_from(&mut reader, int64)?;
138                                datas.push(ECSObjectItem { name, obj });
139                            }
140                            ECSObject::Global(ECSGlobal(datas))
141                        } else {
142                            ECSObject::read_from(&mut reader, int64)?
143                        };
144                        items.push(ECSObjectItem { name, obj });
145                    }
146                    csg_data = Some(ECSGlobal(items));
147                }
148                // conststr
149                0x72747374736E6F63 => {
150                    ext_const_str = Some(TaggedRefAddressList::unpack(
151                        &mut reader,
152                        false,
153                        Encoding::Utf8,
154                        &None,
155                    )?);
156                }
157                // linkinf
158                0x20666E696B6E696C => {
159                    ext_global_ref = DWordArray::unpack(&mut reader, false, Encoding::Utf8, &None)?;
160                    ext_data_ref = DWordArray::unpack(&mut reader, false, Encoding::Utf8, &None)?;
161                    imp_global_ref =
162                        TaggedRefAddressList::unpack(&mut reader, false, Encoding::Utf8, &None)?;
163                    imp_data_ref =
164                        TaggedRefAddressList::unpack(&mut reader, false, Encoding::Utf8, &None)?;
165                    if !ext_global_ref.is_empty()
166                        || !ext_data_ref.is_empty()
167                        || !imp_global_ref.is_empty()
168                        || !imp_data_ref.is_empty()
169                    {
170                        eprintln!(
171                            "Warning: External/global references(linker data) are not supported and will be ignored. This may cause script rebuild errors."
172                        );
173                        crate::COUNTER.inc_warning();
174                    }
175                }
176                0 => {
177                    break;
178                }
179                _ => {
180                    return Err(anyhow::anyhow!(
181                        "Unknown ECSExecutionImage section ID: 0x{:016X}",
182                        id
183                    ));
184                }
185            }
186        }
187        Ok(Self {
188            file_header,
189            exi_header,
190            header,
191            image: image.ok_or_else(|| anyhow::anyhow!("Missing image data"))?,
192            pif_prologue: pif_prologue.ok_or_else(|| anyhow::anyhow!("Missing PIF prologue"))?,
193            pif_epilogue: pif_epilogue.ok_or_else(|| anyhow::anyhow!("Missing PIF epilogue"))?,
194            function_list: function_list.ok_or_else(|| anyhow::anyhow!("Missing function list"))?,
195            csg_global: csg_global.ok_or_else(|| anyhow::anyhow!("Missing CSG global"))?,
196            csg_data: csg_data.ok_or_else(|| anyhow::anyhow!("Missing CSG data"))?,
197            ext_const_str,
198            ext_global_ref,
199            ext_data_ref,
200            imp_global_ref,
201            imp_data_ref,
202            lf: config.entis_gls_csx_lf.clone(),
203        })
204    }
205
206    fn fix_image<'a, 'b>(
207        assembly: &ECSExecutionImageAssembly,
208        mut reader: MemReaderRef<'a>,
209        writer: &mut MemWriter,
210        commands: &HashMap<u32, &'b ECSExecutionImageCommandRecord>,
211    ) -> Result<()> {
212        for cmd in assembly.iter() {
213            // Fix Enter Try Catch address offsets
214            if cmd.code == CsicEnter {
215                reader.pos = cmd.addr as usize + 1;
216                let name_length = reader.read_u32()?;
217                if name_length != 0x80000000 {
218                    reader.pos += name_length as usize * 2;
219                } else {
220                    reader.pos += 4;
221                }
222                let num_args = reader.read_i32()?;
223                if num_args == -1 {
224                    let _flag = reader.read_u8()?;
225                    let offset = reader.pos as i64 - cmd.addr as i64;
226                    let original_addr = reader.read_i32()? as i64 + reader.pos as i64;
227                    let target_cmd = commands.get(&(original_addr as u32)).ok_or_else(|| anyhow::anyhow!(
228                        "Cannot find target command at address {:08X} for Enter instruction fixup at {:08X}",
229                        original_addr as u32,
230                        cmd.addr
231                    ))?;
232                    let new_addr = target_cmd.new_addr as i64 - cmd.new_addr as i64 - offset - 4;
233                    writer.write_i32_at(cmd.new_addr as u64 + offset as u64, new_addr as i32)?;
234                }
235            } else if cmd.code == CsicJump {
236                // Fix Jump address offsets
237                reader.pos = cmd.addr as usize + 1;
238                let offset = reader.pos as i64 - cmd.addr as i64;
239                let original_addr = reader.read_i32()? as i64 + reader.pos as i64;
240                let target_cmd = commands.get(&(original_addr as u32)).ok_or_else(|| anyhow::anyhow!(
241                    "Cannot find target command at address {:08X} for Jump instruction fixup at {:08X}",
242                    original_addr as u32,
243                    cmd.addr
244                ))?;
245                let new_addr = target_cmd.new_addr as i64 - cmd.new_addr as i64 - offset - 4;
246                writer.write_i32_at(cmd.new_addr as u64 + offset as u64, new_addr as i32)?;
247            } else if cmd.code == CsicCJump {
248                // Fix CJump address offsets
249                reader.pos = cmd.addr as usize + 2;
250                let offset = reader.pos as i64 - cmd.addr as i64;
251                let original_addr = reader.read_i32()? as i64 + reader.pos as i64;
252                let target_cmd = commands.get(&(original_addr as u32)).ok_or_else(|| anyhow::anyhow!(
253                    "Cannot find target command at address {:08X} for CJump instruction fixup at {:08X}",
254                    original_addr as u32,
255                    cmd.addr
256                ))?;
257                let new_addr = target_cmd.new_addr as i64 - cmd.new_addr as i64 - offset - 4;
258                writer.write_i32_at(cmd.new_addr as u64 + offset as u64, new_addr as i32)?;
259            }
260        }
261        Ok(())
262    }
263
264    fn fix_references(
265        &mut self,
266        commands: &HashMap<u32, &ECSExecutionImageCommandRecord>,
267    ) -> Result<()> {
268        for cmd in self.pif_prologue.iter_mut() {
269            let ocmd = *cmd;
270            *cmd = commands
271                .get(&ocmd)
272                .ok_or_else(|| {
273                    anyhow::anyhow!(
274                        "Cannot find target command at address {:08X} for PIF prologue fixup",
275                        ocmd
276                    )
277                })?
278                .new_addr;
279        }
280        for cmd in self.pif_epilogue.iter_mut() {
281            let ocmd = *cmd;
282            *cmd = commands
283                .get(&ocmd)
284                .ok_or_else(|| {
285                    anyhow::anyhow!(
286                        "Cannot find target command at address {:08X} for PIF epilogue fixup",
287                        ocmd
288                    )
289                })?
290                .new_addr;
291        }
292        for func in self.function_list.iter_mut() {
293            let ocmd = func.addr;
294            func.addr = commands
295                .get(&ocmd)
296                .ok_or_else(|| {
297                    anyhow::anyhow!(
298                        "Cannot find target command at address {:08X} for function list fixup",
299                        ocmd
300                    )
301                })?
302                .new_addr;
303        }
304        Ok(())
305    }
306
307    fn save<'a>(&self, mut writer: Box<dyn Write + 'a>) -> Result<()> {
308        self.file_header
309            .pack(&mut writer, false, Encoding::Utf8, &None)?;
310        if let Some(exi_header) = &self.exi_header {
311            writer.write_u64(0x2020726564616568)?; // header
312            writer.write_u64(exi_header.len() as u64)?;
313            writer.write_all(&exi_header)?;
314        }
315        writer.write_u64(0x2020206567616D69)?; // image
316        writer.write_u64(self.image.data.len() as u64)?;
317        writer.write_all(&self.image.data)?;
318        writer.write_u64(0x6E6F6974636E7566)?; // function
319        let mut mem = MemWriter::new();
320        self.pif_prologue
321            .pack(&mut mem, false, Encoding::Utf8, &None)?;
322        self.pif_epilogue
323            .pack(&mut mem, false, Encoding::Utf8, &None)?;
324        self.function_list
325            .pack(&mut mem, false, Encoding::Utf8, &None)?;
326        let data = mem.into_inner();
327        writer.write_u64(data.len() as u64)?;
328        writer.write_all(&data)?;
329        writer.write_u64(0x20206C61626F6C67)?; // global
330        let mut mem = MemWriter::new();
331        let int64 = if let Some(hdr) = &self.header {
332            hdr.int_base == 64
333        } else {
334            false
335        };
336        mem.write_u32(self.csg_global.len() as u32)?;
337        for item in self.csg_global.iter() {
338            WideString(item.name.clone()).pack(&mut mem, false, Encoding::Utf16LE, &None)?;
339            item.obj.write_to(&mut mem, int64)?;
340        }
341        let data = mem.into_inner();
342        writer.write_u64(data.len() as u64)?;
343        writer.write_all(&data)?;
344        writer.write_u64(0x2020202061746164)?; // data
345        let mut mem = MemWriter::new();
346        mem.write_u32(self.csg_data.len() as u32)?;
347        for item in self.csg_data.iter() {
348            WideString(item.name.clone()).pack(&mut mem, false, Encoding::Utf16LE, &None)?;
349            match &item.obj {
350                ECSObject::Global(g) => {
351                    mem.write_i32(g.len() as i32)?;
352                    for data_item in g.iter() {
353                        WideString(data_item.name.clone()).pack(
354                            &mut mem,
355                            false,
356                            Encoding::Utf16LE,
357                            &None,
358                        )?;
359                        data_item.obj.write_to(&mut mem, int64)?;
360                    }
361                }
362                _ => {
363                    mem.write_u32(0x80000000)?;
364                    item.obj.write_to(&mut mem, int64)?;
365                }
366            }
367        }
368        let data = mem.into_inner();
369        writer.write_u64(data.len() as u64)?;
370        writer.write_all(&data)?;
371        if let Some(ext_const_str) = &self.ext_const_str {
372            writer.write_u64(0x72747374736E6F63)?; // conststr
373            let mut mem = MemWriter::new();
374            ext_const_str.pack(&mut mem, false, Encoding::Utf8, &None)?;
375            let data = mem.into_inner();
376            writer.write_u64(data.len() as u64)?;
377            writer.write_all(&data)?;
378        }
379        writer.write_u64(0x20666E696B6E696C)?; // linkinf
380        let mut mem = MemWriter::new();
381        self.ext_global_ref
382            .pack(&mut mem, false, Encoding::Utf8, &None)?;
383        self.ext_data_ref
384            .pack(&mut mem, false, Encoding::Utf8, &None)?;
385        self.imp_global_ref
386            .pack(&mut mem, false, Encoding::Utf8, &None)?;
387        self.imp_data_ref
388            .pack(&mut mem, false, Encoding::Utf8, &None)?;
389        let data = mem.into_inner();
390        writer.write_u64(data.len() as u64)?;
391        writer.write_all(&data)?;
392        Ok(())
393    }
394}
395
396impl ECSImage for ECSExecutionImage {
397    fn disasm<'a>(&self, writer: Box<dyn Write + 'a>) -> Result<()> {
398        let mut disasm = ECSExecutionImageDisassembler::new(
399            self.image.to_ref(),
400            self.ext_const_str.as_ref(),
401            Some(writer),
402        );
403        disasm.execute()?;
404        Ok(())
405    }
406
407    fn export(&self) -> Result<Vec<Message>> {
408        let mut disasm = ECSExecutionImageDisassembler::new(
409            self.image.to_ref(),
410            self.ext_const_str.as_ref(),
411            None,
412        );
413        disasm.execute()?;
414        let mut messages = Vec::new();
415        let assembly = disasm.assembly.clone();
416        let mut name = None;
417        let mut pre_is_mess = false;
418        let mut string_stack = Vec::new();
419        let mut message = String::new();
420        for cmd in assembly.iter() {
421            if cmd.code == CsicLoad {
422                disasm.stream.pos = cmd.addr as usize + 1;
423                let csom = disasm.read_csom()?;
424                let csvt = disasm.read_csvt()?;
425                if csom == CsomImmediate && csvt == CsvtString {
426                    let text = disasm.get_string_literal()?;
427                    string_stack.push(text);
428                }
429            } else if cmd.code == CsicCall {
430                disasm.stream.pos = cmd.addr as usize + 1;
431                let _csom = disasm.read_csom()?;
432                let num_args = disasm.stream.read_i32()?;
433                let func_name =
434                    WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE, &None)?.0;
435                let mut is_mess = false;
436                if num_args == 1 {
437                    if func_name == "SceneTitle" {
438                        if string_stack.is_empty() {
439                            return Err(anyhow::anyhow!(
440                                "SceneTitle call without string argument at {:08X}",
441                                cmd.addr
442                            ));
443                        }
444                        messages.push(Message::new(string_stack[0].clone(), None));
445                    } else if func_name == "Mess" {
446                        is_mess = true;
447                        if string_stack.is_empty() {
448                            return Err(anyhow::anyhow!(
449                                "Mess call without string argument at {:08X}",
450                                cmd.addr
451                            ));
452                        }
453                        if string_stack[0].starts_with("@") {
454                            string_stack.clear();
455                            continue;
456                        }
457                        message.push_str(string_stack[0].as_str());
458                    }
459                } else if num_args == 2 {
460                    if func_name == "Talk" {
461                        if string_stack.is_empty() {
462                            return Err(anyhow::anyhow!(
463                                "Talk call without string argument at {:08X}",
464                                cmd.addr
465                            ));
466                        }
467                        if string_stack[0] == "心の声" {
468                            string_stack.clear();
469                            // 傻逼旁白
470                            continue;
471                        }
472                        name = Some(string_stack[0].clone());
473                    } else if func_name == "AddSelect" {
474                        if string_stack.is_empty() {
475                            return Err(anyhow::anyhow!(
476                                "AddSelect call without string argument at {:08X}",
477                                cmd.addr
478                            ));
479                        }
480                        messages.push(Message::new(string_stack[0].clone(), None));
481                    }
482                }
483                if pre_is_mess && !is_mess {
484                    messages.push(Message::new(message.replace(&self.lf, "\n"), name.take()));
485                    message.clear();
486                }
487                string_stack.clear();
488                pre_is_mess = is_mess;
489            }
490        }
491        Ok(messages)
492    }
493
494    fn export_multi(&self) -> Result<HashMap<String, Vec<Message>>> {
495        let mut key = String::from("global");
496        let mut messages = HashMap::new();
497        let mut disasm = ECSExecutionImageDisassembler::new(
498            self.image.to_ref(),
499            self.ext_const_str.as_ref(),
500            None,
501        );
502        disasm.execute()?;
503        let assembly = disasm.assembly.clone();
504        let mut name = None;
505        let mut pre_is_mess = false;
506        let mut pre_is_enter = false;
507        let mut string_stack = Vec::new();
508        let mut message = String::new();
509        let mut pre_enter_name = String::new();
510        for cmd in assembly.iter() {
511            let is_enter = cmd.code == CsicEnter;
512            if cmd.code == CsicLoad {
513                disasm.stream.pos = cmd.addr as usize + 1;
514                let csom = disasm.read_csom()?;
515                let csvt = disasm.read_csvt()?;
516                if csom == CsomImmediate && csvt == CsvtString {
517                    let text = disasm.get_string_literal()?;
518                    string_stack.push(text);
519                }
520            } else if cmd.code == CsicCall {
521                disasm.stream.pos = cmd.addr as usize + 1;
522                let csom = disasm.read_csom()?;
523                let num_args = disasm.stream.read_i32()?;
524                let func_name =
525                    WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE, &None)?.0;
526                let mut is_mess = false;
527                if num_args == 1 {
528                    if func_name == "SceneTitle" {
529                        if string_stack.is_empty() {
530                            return Err(anyhow::anyhow!(
531                                "SceneTitle call without string argument at {:08X}",
532                                cmd.addr
533                            ));
534                        }
535                        messages
536                            .entry(key.clone())
537                            .or_insert_with(|| Vec::new())
538                            .push(Message::new(string_stack[0].clone(), None));
539                    } else if func_name == "Mess" {
540                        is_mess = true;
541                        if string_stack.is_empty() {
542                            return Err(anyhow::anyhow!(
543                                "Mess call without string argument at {:08X}",
544                                cmd.addr
545                            ));
546                        }
547                        if string_stack[0].starts_with("@") {
548                            eprintln!(
549                                "Skipping control string at 0x{:08x}: {}",
550                                cmd.addr, string_stack[0]
551                            );
552                            string_stack.clear();
553                            continue;
554                        }
555                        message.push_str(string_stack[0].as_str());
556                    }
557                } else if num_args == 2 {
558                    if func_name == "Talk" {
559                        if string_stack.is_empty() {
560                            return Err(anyhow::anyhow!(
561                                "Talk call without string argument at {:08X}",
562                                cmd.addr
563                            ));
564                        }
565                        if string_stack[0] == "心の声" {
566                            // 傻逼旁白
567                            string_stack.clear();
568                            continue;
569                        }
570                        name = Some(string_stack[0].clone());
571                    } else if func_name == "AddSelect" {
572                        if string_stack.is_empty() {
573                            return Err(anyhow::anyhow!(
574                                "AddSelect call without string argument at {:08X}",
575                                cmd.addr
576                            ));
577                        }
578                        messages
579                            .entry(key.clone())
580                            .or_insert_with(|| Vec::new())
581                            .push(Message::new(string_stack[0].clone(), None));
582                    }
583                } else if num_args == 0 && csom == CsomAuto && func_name == "ScenarioEnter" {
584                    if pre_is_enter {
585                        key = pre_enter_name.clone();
586                    } else {
587                        key = "global".to_string();
588                    }
589                }
590                if pre_is_mess && !is_mess {
591                    messages
592                        .entry(key.clone())
593                        .or_insert_with(|| Vec::new())
594                        .push(Message::new(message.replace(&self.lf, "\n"), name.take()));
595                    message.clear();
596                }
597                pre_is_mess = is_mess;
598                string_stack.clear();
599            } else if is_enter {
600                disasm.stream.pos = cmd.addr as usize + 1;
601                let name =
602                    WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE, &None)?.0;
603                let num_args = disasm.stream.read_i32()?;
604                if num_args == 0 {
605                    pre_enter_name = name.clone();
606                }
607            }
608            pre_is_enter = is_enter;
609        }
610        Ok(messages)
611    }
612
613    fn export_all(&self) -> Result<Vec<String>> {
614        let mut disasm = ECSExecutionImageDisassembler::new(
615            self.image.to_ref(),
616            self.ext_const_str.as_ref(),
617            None,
618        );
619        disasm.execute()?;
620        let mut messages = Vec::new();
621        let assembly = disasm.assembly.clone();
622        for cmd in assembly.iter() {
623            if cmd.code == CsicLoad {
624                disasm.stream.pos = cmd.addr as usize + 1;
625                let csom = disasm.read_csom()?;
626                let csvt = disasm.read_csvt()?;
627                if csom == CsomImmediate && csvt == CsvtString {
628                    let text = disasm.get_string_literal()?;
629                    messages.push(text);
630                }
631            }
632        }
633        Ok(messages)
634    }
635
636    fn import<'a>(
637        &self,
638        messages: Vec<Message>,
639        file: Box<dyn WriteSeek + 'a>,
640        replacement: Option<&'a ReplacementTable>,
641    ) -> Result<()> {
642        let mut cloned = self.clone();
643        let mut disasm = ECSExecutionImageDisassembler::new(
644            self.image.to_ref(),
645            self.ext_const_str.as_ref(),
646            None,
647        );
648        disasm.execute()?;
649        let mut assembly = disasm.assembly.clone();
650        let mut index = 0;
651        let mut dumped_index = 0;
652        let mut new_image = MemWriter::new();
653        let mut pre_is_mess = false;
654        let mut first_mess_index = None;
655        let mut last_mess_index = None;
656        let mut message_iter = messages.into_iter();
657        let mut mess = message_iter.next();
658        while index < assembly.len() {
659            let cmd = assembly[index].clone();
660            if cmd.code == CsicCall {
661                disasm.stream.pos = cmd.addr as usize + 1;
662                let csom = disasm.read_csom()?;
663                let num_args = disasm.stream.read_i32()?;
664                let func_name =
665                    WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE, &None)?.0;
666                let mut is_mess = false;
667                if csom == CsomAuto && num_args == 1 && func_name == "Mess" {
668                    is_mess = true;
669                    if first_mess_index.is_none() {
670                        first_mess_index = Some(index);
671                    }
672                    last_mess_index = Some(index);
673                }
674                if pre_is_mess && !is_mess {
675                    let first_index = first_mess_index
676                        .ok_or(anyhow::anyhow!("Internal error: first_mess_index is None"))?;
677                    let last_index = last_mess_index
678                        .ok_or(anyhow::anyhow!("Internal error: last_mess_index is None"))?;
679                    // Load string
680                    let pre_index = first_index - 1;
681                    while dumped_index < pre_index {
682                        let tcmd = &mut assembly[dumped_index];
683                        tcmd.new_addr = new_image.pos as u32;
684                        // Copy original instruction
685                        new_image.write_from(
686                            &mut disasm.stream,
687                            tcmd.addr as u64,
688                            tcmd.size as u64,
689                        )?;
690                        dumped_index += 1;
691                    }
692                    // Free
693                    let post_index = last_index + 1;
694                    let mut message = mess
695                        .take()
696                        .ok_or_else(|| {
697                            anyhow::anyhow!("Not enough messages to import for Mess command.")
698                        })?
699                        .message;
700                    mess = message_iter.next();
701                    if let Some(repl) = replacement {
702                        for (k, v) in repl.map.iter() {
703                            message = message.replace(k, v);
704                        }
705                    }
706                    for i in (first_index..=last_index).step_by(3) {
707                        let tcmd = assembly[i - 1].clone();
708                        disasm.stream.pos = tcmd.addr as usize + 1;
709                        let lcsom = disasm.read_csom()?;
710                        let lcsvt = disasm.read_csvt()?;
711                        if lcsom != CsomImmediate || lcsvt != CsvtString {
712                            return Err(anyhow::anyhow!(
713                                "Invalid load command before Mess at {:08X}.",
714                                tcmd.addr
715                            ));
716                        }
717                    }
718                    let mes_list: Vec<_> = message
719                        .replace("\n", &self.lf)
720                        .split(&self.lf)
721                        .map(|s| s.to_string())
722                        .collect();
723                    let mut new_assembly = Vec::new();
724                    let mes_count = mes_list.len();
725                    let mut tmp_index = pre_index;
726                    for i in 0..mes_count {
727                        let mut mes = mes_list[i].clone();
728                        if i < mes_count - 1 {
729                            mes.push_str(&self.lf);
730                        }
731                        let mut tcmd = if tmp_index <= post_index {
732                            let data = assembly[tmp_index].clone();
733                            tmp_index += 1;
734                            if data.code != CsicLoad {
735                                return Err(anyhow::anyhow!(
736                                    "Internal error: expected Load command at {:08X}.",
737                                    data.addr
738                                ));
739                            }
740                            data
741                        } else {
742                            ECSExecutionImageCommandRecord {
743                                code: CsicLoad,
744                                addr: u32::MAX,
745                                size: 0,
746                                new_addr: 0,
747                            }
748                        };
749                        tcmd.new_addr = new_image.pos as u32;
750                        new_image.write_u8(CsicLoad.into())?;
751                        new_image.write_u8(CsomImmediate.into())?;
752                        new_image.write_u8(CsvtString.into())?;
753                        WideString(mes).pack(&mut new_image, false, Encoding::Utf8, &None)?;
754                        new_assembly.push(tcmd);
755                        let mut tcmd = if tmp_index <= post_index {
756                            let data = assembly[tmp_index].clone();
757                            tmp_index += 1;
758                            if data.code != CsicCall {
759                                return Err(anyhow::anyhow!(
760                                    "Expected Call command at {:08X}.",
761                                    data.addr
762                                ));
763                            }
764                            data
765                        } else {
766                            ECSExecutionImageCommandRecord {
767                                code: CsicCall,
768                                addr: u32::MAX,
769                                size: 0,
770                                new_addr: 0,
771                            }
772                        };
773                        tcmd.new_addr = new_image.pos as u32;
774                        new_image.write_u8(CsicCall.into())?;
775                        new_image.write_u8(CsomAuto.into())?;
776                        new_image.write_i32(1)?; // num_args
777                        WideString("Mess".to_string()).pack(
778                            &mut new_image,
779                            false,
780                            Encoding::Utf16LE,
781                            &None,
782                        )?;
783                        new_assembly.push(tcmd);
784                        let mut tcmd = if tmp_index <= post_index {
785                            let data = assembly[tmp_index].clone();
786                            tmp_index += 1;
787                            if data.code != CsicFree {
788                                return Err(anyhow::anyhow!(
789                                    "Expected Free command at {:08X}.",
790                                    data.addr
791                                ));
792                            }
793                            data
794                        } else {
795                            ECSExecutionImageCommandRecord {
796                                code: CsicFree,
797                                addr: u32::MAX,
798                                size: 0,
799                                new_addr: 0,
800                            }
801                        };
802                        tcmd.new_addr = new_image.pos as u32;
803                        new_image.write_u8(CsicFree.into())?;
804                        new_assembly.push(tcmd);
805                    }
806                    let ori_count = post_index - pre_index + 1;
807                    let new_count = new_assembly.len();
808                    dumped_index += new_count;
809                    index = (index as isize + (new_count as isize - ori_count as isize)) as usize;
810                    last_mess_index = None;
811                    first_mess_index = None;
812                    assembly.splice(pre_index..post_index + 1, new_assembly);
813                }
814                if csom == CsomAuto && num_args == 2 && func_name == "Talk" {
815                    if index < 2 {
816                        return Err(anyhow::anyhow!(
817                            "No enough load command at {:08x}.",
818                            cmd.addr
819                        ));
820                    }
821                    let pre_index = index - 2;
822                    while dumped_index < pre_index {
823                        let tcmd = &mut assembly[dumped_index];
824                        tcmd.new_addr = new_image.pos as u32;
825                        // Copy original instruction
826                        new_image.write_from(
827                            &mut disasm.stream,
828                            tcmd.addr as u64,
829                            tcmd.size as u64,
830                        )?;
831                        dumped_index += 1;
832                    }
833                    let tcmd = &mut assembly[pre_index];
834                    tcmd.new_addr = new_image.pos as u32;
835                    disasm.stream.pos = tcmd.addr as usize + 1;
836                    let lcsom = disasm.read_csom()?;
837                    let lcsvt = disasm.read_csvt()?;
838                    if lcsom != CsomImmediate || lcsvt != CsvtString {
839                        return Err(anyhow::anyhow!(
840                            "Invalid load command before Talk at {:08X}.",
841                            tcmd.addr
842                        ));
843                    }
844                    let original_name = disasm.get_string_literal()?;
845                    let name = if original_name == "心の声" {
846                        original_name
847                    } else {
848                        let mut name = mess.as_mut().map(|s| s.name.take()).flatten().ok_or(
849                            anyhow::anyhow!("No available name for Talk at {:08X}.", cmd.addr),
850                        )?;
851                        if let Some(repl) = replacement {
852                            for (k, v) in repl.map.iter() {
853                                name = name.replace(k, v);
854                            }
855                        }
856                        name
857                    };
858                    new_image.write_u8(CsicLoad.into())?;
859                    new_image.write_u8(lcsom.into())?;
860                    new_image.write_u8(lcsvt.into())?;
861                    WideString(name).pack(&mut new_image, false, Encoding::Utf8, &None)?;
862                    dumped_index += 1;
863                    while dumped_index <= index {
864                        let tcmd = &mut assembly[dumped_index];
865                        tcmd.new_addr = new_image.pos as u32;
866                        // Copy original instruction
867                        new_image.write_from(
868                            &mut disasm.stream,
869                            tcmd.addr as u64,
870                            tcmd.size as u64,
871                        )?;
872                        dumped_index += 1;
873                    }
874                } else if csom == CsomAuto && num_args == 2 && func_name == "AddSelect" {
875                    if index < 2 {
876                        return Err(anyhow::anyhow!(
877                            "No enough load command at {:08x}.",
878                            cmd.addr
879                        ));
880                    }
881                    let pre_index = index - 2;
882                    while dumped_index < pre_index {
883                        let tcmd = &mut assembly[dumped_index];
884                        tcmd.new_addr = new_image.pos as u32;
885                        // Copy original instruction
886                        new_image.write_from(
887                            &mut disasm.stream,
888                            tcmd.addr as u64,
889                            tcmd.size as u64,
890                        )?;
891                        dumped_index += 1;
892                    }
893                    let tcmd = &mut assembly[pre_index];
894                    tcmd.new_addr = new_image.pos as u32;
895                    disasm.stream.pos = tcmd.addr as usize + 1;
896                    let lcsom = disasm.read_csom()?;
897                    let lcsvt = disasm.read_csvt()?;
898                    if lcsom != CsomImmediate || lcsvt != CsvtString {
899                        return Err(anyhow::anyhow!(
900                            "Invalid load command before AddSelect at {:08X}.",
901                            tcmd.addr
902                        ));
903                    }
904                    let mut message = mess
905                        .take()
906                        .ok_or_else(|| {
907                            anyhow::anyhow!(
908                                "No available message for AddSelect at {:08X}.",
909                                cmd.addr
910                            )
911                        })?
912                        .message;
913                    mess = message_iter.next();
914                    if let Some(repl) = replacement {
915                        for (k, v) in repl.map.iter() {
916                            message = message.replace(k, v);
917                        }
918                    }
919                    new_image.write_u8(CsicLoad.into())?;
920                    new_image.write_u8(lcsom.into())?;
921                    new_image.write_u8(lcsvt.into())?;
922                    WideString(message).pack(&mut new_image, false, Encoding::Utf8, &None)?;
923                    dumped_index += 1;
924                    while dumped_index <= index {
925                        let tcmd = &mut assembly[dumped_index];
926                        tcmd.new_addr = new_image.pos as u32;
927                        // Copy original instruction
928                        new_image.write_from(
929                            &mut disasm.stream,
930                            tcmd.addr as u64,
931                            tcmd.size as u64,
932                        )?;
933                        dumped_index += 1;
934                    }
935                } else if csom == CsomAuto && num_args == 1 && func_name == "SceneTitle" {
936                    if index < 1 {
937                        return Err(anyhow::anyhow!(
938                            "No enough load command at {:08x}.",
939                            cmd.addr
940                        ));
941                    }
942                    let pre_index = index - 1;
943                    while dumped_index < pre_index {
944                        let tcmd = &mut assembly[dumped_index];
945                        tcmd.new_addr = new_image.pos as u32;
946                        // Copy original instruction
947                        new_image.write_from(
948                            &mut disasm.stream,
949                            tcmd.addr as u64,
950                            tcmd.size as u64,
951                        )?;
952                        dumped_index += 1;
953                    }
954                    let tcmd = &mut assembly[pre_index];
955                    tcmd.new_addr = new_image.pos as u32;
956                    disasm.stream.pos = tcmd.addr as usize + 1;
957                    let lcsom = disasm.read_csom()?;
958                    let lcsvt = disasm.read_csvt()?;
959                    if lcsom != CsomImmediate || lcsvt != CsvtString {
960                        return Err(anyhow::anyhow!(
961                            "Invalid load command before SceneTitle at {:08X}.",
962                            tcmd.addr
963                        ));
964                    }
965                    let mut message = mess
966                        .take()
967                        .ok_or_else(|| {
968                            anyhow::anyhow!(
969                                "No available message for SceneTitle at {:08X}.",
970                                cmd.addr
971                            )
972                        })?
973                        .message;
974                    mess = message_iter.next();
975                    if let Some(repl) = replacement {
976                        for (k, v) in repl.map.iter() {
977                            message = message.replace(k, v);
978                        }
979                    }
980                    new_image.write_u8(CsicLoad.into())?;
981                    new_image.write_u8(lcsom.into())?;
982                    new_image.write_u8(lcsvt.into())?;
983                    WideString(message).pack(&mut new_image, false, Encoding::Utf8, &None)?;
984                    dumped_index += 1;
985                    while dumped_index <= index {
986                        let tcmd = &mut assembly[dumped_index];
987                        tcmd.new_addr = new_image.pos as u32;
988                        // Copy original instruction
989                        new_image.write_from(
990                            &mut disasm.stream,
991                            tcmd.addr as u64,
992                            tcmd.size as u64,
993                        )?;
994                        dumped_index += 1;
995                    }
996                }
997                pre_is_mess = is_mess;
998            }
999            index += 1;
1000        }
1001        while dumped_index < assembly.len() {
1002            let tcmd = &mut assembly[dumped_index];
1003            tcmd.new_addr = new_image.pos as u32;
1004            // Copy original instruction
1005            new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1006            dumped_index += 1;
1007        }
1008        if mess.is_some() || message_iter.next().is_some() {
1009            return Err(anyhow::anyhow!("Too many messages to import."));
1010        }
1011        let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
1012            assembly.iter().map(|c| (c.addr, c)).collect();
1013        Self::fix_image(&assembly, disasm.stream.clone(), &mut new_image, &commands)?;
1014        cloned.image = MemReader::new(new_image.into_inner());
1015        cloned.fix_references(&commands)?;
1016        cloned.save(file)?;
1017        Ok(())
1018    }
1019
1020    fn import_multi<'a>(
1021        &self,
1022        mut messages: HashMap<String, Vec<Message>>,
1023        file: Box<dyn WriteSeek + 'a>,
1024        replacement: Option<&'a ReplacementTable>,
1025    ) -> Result<()> {
1026        let mut cloned = self.clone();
1027        let mut key = String::from("global");
1028        let mut disasm = ECSExecutionImageDisassembler::new(
1029            self.image.to_ref(),
1030            self.ext_const_str.as_ref(),
1031            None,
1032        );
1033        disasm.execute()?;
1034        let mut assembly = disasm.assembly.clone();
1035        let mut index = 0;
1036        let mut dumped_index = 0;
1037        let mut new_image = MemWriter::new();
1038        let mut pre_is_enter = false;
1039        let mut pre_enter_name = String::new();
1040        let mut pre_is_mess = false;
1041        let mut first_mess_index = None;
1042        let mut last_mess_index = None;
1043        while index < assembly.len() {
1044            let cmd = assembly[index].clone();
1045            let is_enter = cmd.code == CsicEnter;
1046            if cmd.code == CsicCall {
1047                disasm.stream.pos = cmd.addr as usize + 1;
1048                let csom = disasm.read_csom()?;
1049                let num_args = disasm.stream.read_i32()?;
1050                let func_name =
1051                    WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE, &None)?.0;
1052                let mut is_mess = false;
1053                if csom == CsomAuto && num_args == 1 && func_name == "Mess" {
1054                    is_mess = true;
1055                    if first_mess_index.is_none() {
1056                        first_mess_index = Some(index);
1057                    }
1058                    last_mess_index = Some(index);
1059                }
1060                if pre_is_mess && !is_mess {
1061                    let first_index = first_mess_index
1062                        .ok_or(anyhow::anyhow!("Internal error: first_mess_index is None"))?;
1063                    let last_index = last_mess_index
1064                        .ok_or(anyhow::anyhow!("Internal error: last_mess_index is None"))?;
1065                    // Load string
1066                    let pre_index = first_index - 1;
1067                    while dumped_index < pre_index {
1068                        let tcmd = &mut assembly[dumped_index];
1069                        tcmd.new_addr = new_image.pos as u32;
1070                        // Copy original instruction
1071                        new_image.write_from(
1072                            &mut disasm.stream,
1073                            tcmd.addr as u64,
1074                            tcmd.size as u64,
1075                        )?;
1076                        dumped_index += 1;
1077                    }
1078                    // Free
1079                    let post_index = last_index + 1;
1080                    let mut message = messages
1081                        .get_mut(&key)
1082                        .and_then(|messages| messages.pop_first())
1083                        .ok_or(anyhow::anyhow!(
1084                            "No available message for Mess at {:08X}.",
1085                            cmd.addr
1086                        ))?
1087                        .message;
1088                    if let Some(repl) = replacement {
1089                        for (k, v) in repl.map.iter() {
1090                            message = message.replace(k, v);
1091                        }
1092                    }
1093                    for i in (first_index..=last_index).step_by(3) {
1094                        let tcmd = assembly[i - 1].clone();
1095                        disasm.stream.pos = tcmd.addr as usize + 1;
1096                        let lcsom = disasm.read_csom()?;
1097                        let lcsvt = disasm.read_csvt()?;
1098                        if lcsom != CsomImmediate || lcsvt != CsvtString {
1099                            return Err(anyhow::anyhow!(
1100                                "Invalid load command before Mess at {:08X}.",
1101                                tcmd.addr
1102                            ));
1103                        }
1104                    }
1105                    let mes_list: Vec<_> = message
1106                        .replace("\n", &self.lf)
1107                        .split(&self.lf)
1108                        .map(|s| s.to_string())
1109                        .collect();
1110                    let mut new_assembly = Vec::new();
1111                    let mes_count = mes_list.len();
1112                    let mut tmp_index = pre_index;
1113                    for i in 0..mes_count {
1114                        let mut mes = mes_list[i].clone();
1115                        if i < mes_count - 1 {
1116                            mes.push_str(&self.lf);
1117                        }
1118                        let mut tcmd = if tmp_index <= post_index {
1119                            let data = assembly[tmp_index].clone();
1120                            tmp_index += 1;
1121                            if data.code != CsicLoad {
1122                                return Err(anyhow::anyhow!(
1123                                    "Internal error: expected Load command at {:08X}.",
1124                                    data.addr
1125                                ));
1126                            }
1127                            data
1128                        } else {
1129                            ECSExecutionImageCommandRecord {
1130                                code: CsicLoad,
1131                                addr: u32::MAX,
1132                                size: 0,
1133                                new_addr: 0,
1134                            }
1135                        };
1136                        tcmd.new_addr = new_image.pos as u32;
1137                        new_image.write_u8(CsicLoad.into())?;
1138                        new_image.write_u8(CsomImmediate.into())?;
1139                        new_image.write_u8(CsvtString.into())?;
1140                        WideString(mes).pack(&mut new_image, false, Encoding::Utf8, &None)?;
1141                        new_assembly.push(tcmd);
1142                        let mut tcmd = if tmp_index <= post_index {
1143                            let data = assembly[tmp_index].clone();
1144                            tmp_index += 1;
1145                            if data.code != CsicCall {
1146                                return Err(anyhow::anyhow!(
1147                                    "Expected Call command at {:08X}.",
1148                                    data.addr
1149                                ));
1150                            }
1151                            data
1152                        } else {
1153                            ECSExecutionImageCommandRecord {
1154                                code: CsicCall,
1155                                addr: u32::MAX,
1156                                size: 0,
1157                                new_addr: 0,
1158                            }
1159                        };
1160                        tcmd.new_addr = new_image.pos as u32;
1161                        new_image.write_u8(CsicCall.into())?;
1162                        new_image.write_u8(CsomAuto.into())?;
1163                        new_image.write_i32(1)?; // num_args
1164                        WideString("Mess".to_string()).pack(
1165                            &mut new_image,
1166                            false,
1167                            Encoding::Utf16LE,
1168                            &None,
1169                        )?;
1170                        new_assembly.push(tcmd);
1171                        let mut tcmd = if tmp_index <= post_index {
1172                            let data = assembly[tmp_index].clone();
1173                            tmp_index += 1;
1174                            if data.code != CsicFree {
1175                                return Err(anyhow::anyhow!(
1176                                    "Expected Free command at {:08X}.",
1177                                    data.addr
1178                                ));
1179                            }
1180                            data
1181                        } else {
1182                            ECSExecutionImageCommandRecord {
1183                                code: CsicFree,
1184                                addr: u32::MAX,
1185                                size: 0,
1186                                new_addr: 0,
1187                            }
1188                        };
1189                        tcmd.new_addr = new_image.pos as u32;
1190                        new_image.write_u8(CsicFree.into())?;
1191                        new_assembly.push(tcmd);
1192                    }
1193                    let ori_count = post_index - pre_index + 1;
1194                    let new_count = new_assembly.len();
1195                    dumped_index += new_count;
1196                    index = (index as isize + (new_count as isize - ori_count as isize)) as usize;
1197                    last_mess_index = None;
1198                    first_mess_index = None;
1199                    assembly.splice(pre_index..post_index + 1, new_assembly);
1200                }
1201                if csom == CsomAuto && num_args == 2 && func_name == "Talk" {
1202                    if index < 2 {
1203                        return Err(anyhow::anyhow!(
1204                            "No enough load command at {:08x}.",
1205                            cmd.addr
1206                        ));
1207                    }
1208                    let pre_index = index - 2;
1209                    while dumped_index < pre_index {
1210                        let tcmd = &mut assembly[dumped_index];
1211                        tcmd.new_addr = new_image.pos as u32;
1212                        // Copy original instruction
1213                        new_image.write_from(
1214                            &mut disasm.stream,
1215                            tcmd.addr as u64,
1216                            tcmd.size as u64,
1217                        )?;
1218                        dumped_index += 1;
1219                    }
1220                    let tcmd = &mut assembly[pre_index];
1221                    tcmd.new_addr = new_image.pos as u32;
1222                    disasm.stream.pos = tcmd.addr as usize + 1;
1223                    let lcsom = disasm.read_csom()?;
1224                    let lcsvt = disasm.read_csvt()?;
1225                    if lcsom != CsomImmediate || lcsvt != CsvtString {
1226                        return Err(anyhow::anyhow!(
1227                            "Invalid load command before Talk at {:08X}.",
1228                            tcmd.addr
1229                        ));
1230                    }
1231                    let original_name = disasm.get_string_literal()?;
1232                    let name = if original_name == "心の声" {
1233                        original_name
1234                    } else {
1235                        let mut name = messages
1236                            .get_mut(&key)
1237                            .and_then(|messages| messages.first_mut().map(|m| m.name.take()))
1238                            .flatten()
1239                            .ok_or(anyhow::anyhow!(
1240                                "No available name message for Talk at {:08X}.",
1241                                cmd.addr
1242                            ))?;
1243                        if let Some(repl) = replacement {
1244                            for (k, v) in repl.map.iter() {
1245                                name = name.replace(k, v);
1246                            }
1247                        }
1248                        name
1249                    };
1250                    new_image.write_u8(CsicLoad.into())?;
1251                    new_image.write_u8(lcsom.into())?;
1252                    new_image.write_u8(lcsvt.into())?;
1253                    WideString(name).pack(&mut new_image, false, Encoding::Utf8, &None)?;
1254                    dumped_index += 1;
1255                    while dumped_index <= index {
1256                        let tcmd = &mut assembly[dumped_index];
1257                        tcmd.new_addr = new_image.pos as u32;
1258                        // Copy original instruction
1259                        new_image.write_from(
1260                            &mut disasm.stream,
1261                            tcmd.addr as u64,
1262                            tcmd.size as u64,
1263                        )?;
1264                        dumped_index += 1;
1265                    }
1266                } else if csom == CsomAuto && num_args == 2 && func_name == "AddSelect" {
1267                    if index < 2 {
1268                        return Err(anyhow::anyhow!(
1269                            "No enough load command at {:08x}.",
1270                            cmd.addr
1271                        ));
1272                    }
1273                    let pre_index = index - 2;
1274                    while dumped_index < pre_index {
1275                        let tcmd = &mut assembly[dumped_index];
1276                        tcmd.new_addr = new_image.pos as u32;
1277                        // Copy original instruction
1278                        new_image.write_from(
1279                            &mut disasm.stream,
1280                            tcmd.addr as u64,
1281                            tcmd.size as u64,
1282                        )?;
1283                        dumped_index += 1;
1284                    }
1285                    let tcmd = &mut assembly[pre_index];
1286                    tcmd.new_addr = new_image.pos as u32;
1287                    disasm.stream.pos = tcmd.addr as usize + 1;
1288                    let lcsom = disasm.read_csom()?;
1289                    let lcsvt = disasm.read_csvt()?;
1290                    if lcsom != CsomImmediate || lcsvt != CsvtString {
1291                        return Err(anyhow::anyhow!(
1292                            "Invalid load command before AddSelect at {:08X}.",
1293                            tcmd.addr
1294                        ));
1295                    }
1296                    let mut message = messages
1297                        .get_mut(&key)
1298                        .and_then(|messages| messages.pop_first())
1299                        .ok_or(anyhow::anyhow!(
1300                            "No available message for AddSelect at {:08X}.",
1301                            cmd.addr
1302                        ))?
1303                        .message;
1304                    if let Some(repl) = replacement {
1305                        for (k, v) in repl.map.iter() {
1306                            message = message.replace(k, v);
1307                        }
1308                    }
1309                    new_image.write_u8(CsicLoad.into())?;
1310                    new_image.write_u8(lcsom.into())?;
1311                    new_image.write_u8(lcsvt.into())?;
1312                    WideString(message).pack(&mut new_image, false, Encoding::Utf8, &None)?;
1313                    dumped_index += 1;
1314                    while dumped_index <= index {
1315                        let tcmd = &mut assembly[dumped_index];
1316                        tcmd.new_addr = new_image.pos as u32;
1317                        // Copy original instruction
1318                        new_image.write_from(
1319                            &mut disasm.stream,
1320                            tcmd.addr as u64,
1321                            tcmd.size as u64,
1322                        )?;
1323                        dumped_index += 1;
1324                    }
1325                } else if csom == CsomAuto && num_args == 0 && func_name == "ScenarioEnter" {
1326                    if pre_is_enter {
1327                        key = pre_enter_name.clone();
1328                    } else {
1329                        key = "global".to_string();
1330                    }
1331                } else if csom == CsomAuto && num_args == 1 && func_name == "SceneTitle" {
1332                    if index < 1 {
1333                        return Err(anyhow::anyhow!(
1334                            "No enough load command at {:08x}.",
1335                            cmd.addr
1336                        ));
1337                    }
1338                    let pre_index = index - 1;
1339                    while dumped_index < pre_index {
1340                        let tcmd = &mut assembly[dumped_index];
1341                        tcmd.new_addr = new_image.pos as u32;
1342                        // Copy original instruction
1343                        new_image.write_from(
1344                            &mut disasm.stream,
1345                            tcmd.addr as u64,
1346                            tcmd.size as u64,
1347                        )?;
1348                        dumped_index += 1;
1349                    }
1350                    let tcmd = &mut assembly[pre_index];
1351                    tcmd.new_addr = new_image.pos as u32;
1352                    disasm.stream.pos = tcmd.addr as usize + 1;
1353                    let lcsom = disasm.read_csom()?;
1354                    let lcsvt = disasm.read_csvt()?;
1355                    if lcsom != CsomImmediate || lcsvt != CsvtString {
1356                        return Err(anyhow::anyhow!(
1357                            "Invalid load command before SceneTitle at {:08X}.",
1358                            tcmd.addr
1359                        ));
1360                    }
1361                    let mut message = messages
1362                        .get_mut(&key)
1363                        .and_then(|messages| messages.pop_first())
1364                        .ok_or(anyhow::anyhow!(
1365                            "No available message for SceneTitle at {:08X}.",
1366                            cmd.addr
1367                        ))?
1368                        .message;
1369                    if let Some(repl) = replacement {
1370                        for (k, v) in repl.map.iter() {
1371                            message = message.replace(k, v);
1372                        }
1373                    }
1374                    new_image.write_u8(CsicLoad.into())?;
1375                    new_image.write_u8(lcsom.into())?;
1376                    new_image.write_u8(lcsvt.into())?;
1377                    WideString(message).pack(&mut new_image, false, Encoding::Utf8, &None)?;
1378                    dumped_index += 1;
1379                    while dumped_index <= index {
1380                        let tcmd = &mut assembly[dumped_index];
1381                        tcmd.new_addr = new_image.pos as u32;
1382                        // Copy original instruction
1383                        new_image.write_from(
1384                            &mut disasm.stream,
1385                            tcmd.addr as u64,
1386                            tcmd.size as u64,
1387                        )?;
1388                        dumped_index += 1;
1389                    }
1390                }
1391                pre_is_mess = is_mess;
1392            } else if is_enter {
1393                disasm.stream.pos = cmd.addr as usize + 1;
1394                let original_name =
1395                    WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE, &None)?.0;
1396                let num_args = disasm.stream.read_i32()?;
1397                if num_args == 0 {
1398                    pre_enter_name = original_name.clone();
1399                }
1400            }
1401            pre_is_enter = is_enter;
1402            index += 1;
1403        }
1404        while dumped_index < assembly.len() {
1405            let tcmd = &mut assembly[dumped_index];
1406            tcmd.new_addr = new_image.pos as u32;
1407            // Copy original instruction
1408            new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1409            dumped_index += 1;
1410        }
1411        for (s, mes) in messages {
1412            if !mes.is_empty() {
1413                return Err(anyhow::anyhow!(
1414                    "Not all messages were used for key '{}', {} remaining.",
1415                    s,
1416                    mes.len()
1417                ));
1418            }
1419        }
1420        let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
1421            assembly.iter().map(|c| (c.addr, c)).collect();
1422        Self::fix_image(&assembly, disasm.stream.clone(), &mut new_image, &commands)?;
1423        cloned.image = MemReader::new(new_image.into_inner());
1424        cloned.fix_references(&commands)?;
1425        cloned.save(file)?;
1426        Ok(())
1427    }
1428
1429    fn import_all<'a>(&self, messages: Vec<String>, file: Box<dyn WriteSeek + 'a>) -> Result<()> {
1430        let mut cloned = self.clone();
1431        let mut mess = messages.into_iter();
1432        let mut mes = mess.next();
1433        let mut disasm = ECSExecutionImageDisassembler::new(
1434            self.image.to_ref(),
1435            self.ext_const_str.as_ref(),
1436            None,
1437        );
1438        disasm.execute()?;
1439        let mut assembly = disasm.assembly.clone();
1440        let mut new_image = MemWriter::new();
1441        for cmd in assembly.iter_mut() {
1442            cmd.new_addr = new_image.pos as u32;
1443            if cmd.code == CsicLoad {
1444                disasm.stream.pos = cmd.addr as usize + 1;
1445                let csom = disasm.read_csom()?;
1446                let csvt = disasm.read_csvt()?;
1447                if csom == CsomImmediate && csvt == CsvtString {
1448                    let code: u8 = CsicLoad.into();
1449                    let csom: u8 = csom.into();
1450                    let csvt: u8 = csvt.into();
1451                    let s = match mes.take() {
1452                        Some(v) => WideString(v),
1453                        None => {
1454                            return Err(anyhow::anyhow!(
1455                                "Not enough messages to import, ran out at instruction address {:08X}",
1456                                cmd.addr
1457                            ));
1458                        }
1459                    };
1460                    mes = mess.next();
1461                    new_image.write_u8(code)?;
1462                    new_image.write_u8(csom)?;
1463                    new_image.write_u8(csvt)?;
1464                    s.pack(&mut new_image, false, Encoding::Utf8, &None)?;
1465                    continue;
1466                }
1467            }
1468            // Copy original instruction
1469            new_image.write_from(&mut disasm.stream, cmd.addr as u64, cmd.size as u64)?;
1470        }
1471        let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
1472            assembly.iter().map(|c| (c.addr, c)).collect();
1473        Self::fix_image(&assembly, disasm.stream.clone(), &mut new_image, &commands)?;
1474        cloned.image = MemReader::new(new_image.into_inner());
1475        cloned.fix_references(&commands)?;
1476        cloned.save(file)?;
1477        Ok(())
1478    }
1479}