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

1use super::super::CSXScriptV2FullVer;
2use super::super::base::*;
3use super::disasm::*;
4use super::types::*;
5use crate::ext::io::*;
6use crate::ext::vec::*;
7use crate::scripts::base::*;
8use crate::types::*;
9use crate::utils::struct_pack::*;
10use anyhow::Result;
11use std::collections::HashMap;
12use std::io::{Seek, Write};
13
14const ID_HEADER: u64 = 0x2020726564616568; // header
15const ID_IMAGE: u64 = 0x2020206567616D69;
16const ID_IMAGE_GLOBAL: u64 = 0x6C626F6C67676D69;
17const ID_IMAGE_CONST: u64 = 0x74736E6F63676D69;
18const ID_IMAGE_SHARED: u64 = 0x6572616873676D69;
19const ID_CLASS_INFO: u64 = 0x666E697373616C63;
20const ID_FUNCTION: u64 = 0x6E6F6974636E7566;
21const ID_INIT_NAKED_FUNC: u64 = 0x636E666E74696E69;
22const ID_FUNC_INFO: u64 = 0x6F666E69636E7566;
23const ID_SYMBOL_INFO: u64 = 0x666E696C626D7973;
24const ID_GLOBAL: u64 = 0x20206C61626F6C67;
25const ID_DATA: u64 = 0x2020202061746164;
26const ID_CONST_STRING: u64 = 0x72747374736E6F63;
27const ID_LINK_INFO: u64 = 0x20666E696B6E696C;
28const ID_LINK_INFO_EX: u64 = 0x343678656B6E696C;
29const ID_REF_FUNC: u64 = 0x20636E7566666572;
30const ID_REF_CODE: u64 = 0x2065646F63666572;
31const ID_REF_CLASS: u64 = 0x7373616C63666572;
32const ID_IMPORT_NATIVE_FUNC: u64 = 0x766974616E706D69;
33
34#[derive(Clone, Debug)]
35#[allow(unused)]
36pub struct ECSExecutionImage {
37    file_header: FileHeader,
38    section_header: SectionHeader,
39    image: MemReader,
40    image_global: Option<MemReader>,
41    image_const: Option<MemReader>,
42    image_shared: Option<MemReader>,
43    section_class_info: SectionClassInfo,
44    section_function: SectionFunction,
45    section_init_naked_func: SectionInitNakedFunc,
46    section_func_info: SectionFuncInfo,
47    section_symbol_info: Option<SectionSymbolInfo>,
48    section_global: Option<SectionGlobal>,
49    section_data: Option<SectionData>,
50    section_const_string: SectionConstString,
51    section_link_info: Option<SectionLinkInfo>,
52    section_link_info_ex: Option<SectionLinkInfoEx>,
53    section_ref_func: Option<SectionRefFunc>,
54    section_ref_code: Option<SectionRefCode>,
55    section_ref_class: Option<SectionRefClass>,
56    section_import_native_func: SectionImportNativeFunc,
57    no_part_label: bool,
58}
59
60impl ECSExecutionImage {
61    pub fn new(reader: MemReaderRef<'_>, config: &ExtraConfig) -> Result<Self> {
62        if let Some(ver) = config.entis_gls_csx_v2_ver {
63            match ver {
64                CSXScriptV2FullVer::V3 => Self::inner_new(reader, config, 3),
65                CSXScriptV2FullVer::V2 => Self::inner_new(reader, config, 2),
66            }
67        } else {
68            match Self::inner_new(reader.clone(), config, 3) {
69                Ok(img) => Ok(img),
70                Err(_) => Self::inner_new(reader, config, 2),
71            }
72        }
73    }
74
75    fn inner_new(mut reader: MemReaderRef<'_>, config: &ExtraConfig, ver: u32) -> Result<Self> {
76        let file_header = FileHeader::unpack(&mut reader, false, Encoding::Utf8, &None)?;
77        if file_header.signagure != *b"Entis\x1a\0\0" {
78            return Err(anyhow::anyhow!("Invalid EMC file signature"));
79        }
80        if !file_header.format_desc.starts_with(b"Cotopha Image file") {
81            return Err(anyhow::anyhow!("Invalid EMC file format description"));
82        }
83        let mut section_header = SectionHeader::default();
84        section_header.full_ver = ver;
85        let len = reader.data.len();
86        let mut image = None;
87        let mut image_global = None;
88        let mut image_const = None;
89        let mut image_shared = None;
90        let mut section_class_info = None;
91        let mut section_function = None;
92        let mut section_init_naked_func = None;
93        let mut section_func_info = None;
94        let mut section_symbol_info = None;
95        let mut section_global = None;
96        let mut section_data = None;
97        let mut section_const_string = None;
98        let mut section_link_info = None;
99        let mut section_link_info_ex = None;
100        let mut section_ref_func = None;
101        let mut section_ref_code = None;
102        let mut section_ref_class = None;
103        let mut section_import_native_func = None;
104        while reader.pos < len {
105            if len - reader.pos < 16 {
106                break;
107            }
108            let id = reader.read_u64()?;
109            if id == 0 {
110                break;
111            }
112            let size = reader.read_u64()?;
113            let pos = reader.pos;
114            match id {
115                ID_HEADER => {
116                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
117                    section_header = SectionHeader::unpack(&mut mem, false, Encoding::Utf8, &None)?;
118                    section_header.full_ver = ver;
119                }
120                ID_IMAGE => {
121                    image = Some(MemReader::new(reader.read_exact_vec(size as usize)?));
122                }
123                ID_IMAGE_GLOBAL => {
124                    image_global = Some(MemReader::new(reader.read_exact_vec(size as usize)?));
125                }
126                ID_IMAGE_CONST => {
127                    image_const = Some(MemReader::new(reader.read_exact_vec(size as usize)?));
128                }
129                ID_IMAGE_SHARED => {
130                    image_shared = Some(MemReader::new(reader.read_exact_vec(size as usize)?));
131                }
132                ID_CLASS_INFO => {
133                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
134                    section_class_info = Some(SectionClassInfo::unpack(
135                        &mut mem,
136                        false,
137                        Encoding::Utf8,
138                        &Some(Box::new(section_header.clone())),
139                    )?);
140                    if mem.stream_position()? != size {
141                        eprintln!(
142                            "WARNING: Some data is not parsed in ECSExecutionImage::CLASS_INFO"
143                        );
144                        crate::COUNTER.inc_warning();
145                    }
146                }
147                ID_FUNCTION => {
148                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
149                    section_function = Some(SectionFunction::unpack(
150                        &mut mem,
151                        false,
152                        Encoding::Utf8,
153                        &Some(Box::new(section_header.clone())),
154                    )?);
155                    if mem.stream_position()? != size {
156                        eprintln!(
157                            "WARNING: Some data is not parsed in ECSExecutionImage::FUNCTION"
158                        );
159                        crate::COUNTER.inc_warning();
160                    }
161                }
162                ID_INIT_NAKED_FUNC => {
163                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
164                    section_init_naked_func = Some(SectionInitNakedFunc::unpack(
165                        &mut mem,
166                        false,
167                        Encoding::Utf8,
168                        &Some(Box::new(section_header.clone())),
169                    )?);
170                    if mem.stream_position()? != size {
171                        eprintln!(
172                            "WARNING: Some data is not parsed in ECSExecutionImage::INIT_NAKED_FUNC"
173                        );
174                        crate::COUNTER.inc_warning();
175                    }
176                }
177                ID_FUNC_INFO => {
178                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
179                    section_func_info = Some(SectionFuncInfo::unpack(
180                        &mut mem,
181                        false,
182                        Encoding::Utf8,
183                        &Some(Box::new(section_header.clone())),
184                    )?);
185                    if mem.stream_position()? != size {
186                        eprintln!(
187                            "WARNING: Some data is not parsed in ECSExecutionImage::FUNC_INFO"
188                        );
189                        crate::COUNTER.inc_warning();
190                    }
191                }
192                ID_SYMBOL_INFO => {
193                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
194                    section_symbol_info = Some(SectionSymbolInfo::unpack(
195                        &mut mem,
196                        false,
197                        Encoding::Utf8,
198                        &Some(Box::new(section_header.clone())),
199                    )?);
200                    if mem.stream_position()? != size {
201                        eprintln!(
202                            "WARNING: Some data is not parsed in ECSExecutionImage::SYMBOL_INFO"
203                        );
204                        crate::COUNTER.inc_warning();
205                    }
206                }
207                ID_GLOBAL => {
208                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
209                    section_global = Some(SectionGlobal::unpack(
210                        &mut mem,
211                        false,
212                        Encoding::Utf8,
213                        &Some(Box::new(section_header.clone())),
214                    )?);
215                    if mem.stream_position()? != size {
216                        eprintln!("WARNING: Some data is not parsed in ECSExecutionImage::GLOBAL");
217                        crate::COUNTER.inc_warning();
218                    }
219                }
220                ID_DATA => {
221                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
222                    section_data = Some(SectionData::unpack(
223                        &mut mem,
224                        false,
225                        Encoding::Utf8,
226                        &Some(Box::new(section_header.clone())),
227                    )?);
228                    if mem.stream_position()? != size {
229                        eprintln!("WARNING: Some data is not parsed in ECSExecutionImage::DATA");
230                        crate::COUNTER.inc_warning();
231                    }
232                }
233                ID_CONST_STRING => {
234                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
235                    section_const_string = Some(SectionConstString::unpack(
236                        &mut mem,
237                        false,
238                        Encoding::Utf8,
239                        &Some(Box::new(section_header.clone())),
240                    )?);
241                    if mem.stream_position()? != size {
242                        eprintln!(
243                            "WARNING: Some data is not parsed in ECSExecutionImage::CONST_STRING"
244                        );
245                        crate::COUNTER.inc_warning();
246                    }
247                }
248                ID_LINK_INFO => {
249                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
250                    section_link_info = Some(SectionLinkInfo::unpack(
251                        &mut mem,
252                        false,
253                        Encoding::Utf8,
254                        &Some(Box::new(section_header.clone())),
255                    )?);
256                    if mem.stream_position()? != size {
257                        eprintln!(
258                            "WARNING: Some data is not parsed in ECSExecutionImage::LINK_INFO"
259                        );
260                        crate::COUNTER.inc_warning();
261                    }
262                }
263                ID_LINK_INFO_EX => {
264                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
265                    section_link_info_ex = Some(SectionLinkInfoEx::unpack(
266                        &mut mem,
267                        false,
268                        Encoding::Utf8,
269                        &Some(Box::new(section_header.clone())),
270                    )?);
271                    if mem.stream_position()? != size {
272                        eprintln!(
273                            "WARNING: Some data is not parsed in ECSExecutionImage::LINK_INFO_EX"
274                        );
275                        crate::COUNTER.inc_warning();
276                    }
277                }
278                ID_REF_FUNC => {
279                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
280                    section_ref_func = Some(SectionRefFunc::unpack(
281                        &mut mem,
282                        false,
283                        Encoding::Utf8,
284                        &Some(Box::new(section_header.clone())),
285                    )?);
286                    if mem.stream_position()? != size {
287                        eprintln!(
288                            "WARNING: Some data is not parsed in ECSExecutionImage::REF_FUNC"
289                        );
290                        crate::COUNTER.inc_warning();
291                    }
292                }
293                ID_REF_CODE => {
294                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
295                    section_ref_code = Some(SectionRefCode::unpack(
296                        &mut mem,
297                        false,
298                        Encoding::Utf8,
299                        &Some(Box::new(section_header.clone())),
300                    )?);
301                    if mem.stream_position()? != size {
302                        eprintln!(
303                            "WARNING: Some data is not parsed in ECSExecutionImage::REF_CODE"
304                        );
305                        crate::COUNTER.inc_warning();
306                    }
307                }
308                ID_REF_CLASS => {
309                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
310                    section_ref_class = Some(SectionRefClass::unpack(
311                        &mut mem,
312                        false,
313                        Encoding::Utf8,
314                        &Some(Box::new(section_header.clone())),
315                    )?);
316                    if mem.stream_position()? != size {
317                        eprintln!(
318                            "WARNING: Some data is not parsed in ECSExecutionImage::REF_CLASS"
319                        );
320                        crate::COUNTER.inc_warning();
321                    }
322                }
323                ID_IMPORT_NATIVE_FUNC => {
324                    let mut mem = StreamRegion::with_size(&mut reader, size)?;
325                    section_import_native_func = Some(SectionImportNativeFunc::unpack(
326                        &mut mem,
327                        false,
328                        Encoding::Utf8,
329                        &Some(Box::new(section_header.clone())),
330                    )?);
331                    if mem.stream_position()? != size {
332                        eprintln!(
333                            "WARNING: Some data is not parsed in ECSExecutionImage::IMPORT_NATIVE_FUNC"
334                        );
335                        crate::COUNTER.inc_warning();
336                    }
337                }
338                0 => {
339                    break;
340                }
341                _ => {
342                    return Err(anyhow::anyhow!(
343                        "Unknown ECSExecutionImage section ID: 0x{:016X}",
344                        id
345                    ));
346                }
347            }
348            reader.pos = pos + size as usize;
349        }
350        Ok(Self {
351            file_header,
352            section_header,
353            image: image.ok_or_else(|| anyhow::anyhow!("Missing image data"))?,
354            image_global,
355            image_const,
356            image_shared,
357            section_class_info: section_class_info
358                .ok_or_else(|| anyhow::anyhow!("Missing class info section"))?,
359            section_function: section_function
360                .ok_or_else(|| anyhow::anyhow!("Missing function section"))?,
361            section_init_naked_func: section_init_naked_func
362                .ok_or_else(|| anyhow::anyhow!("Missing init naked func section"))?,
363            section_func_info: section_func_info
364                .ok_or_else(|| anyhow::anyhow!("Missing func info section"))?,
365            section_symbol_info,
366            section_global,
367            section_data,
368            section_const_string: section_const_string
369                .ok_or_else(|| anyhow::anyhow!("Missing const string section"))?,
370            section_link_info,
371            section_link_info_ex,
372            section_ref_func,
373            section_ref_code,
374            section_ref_class,
375            section_import_native_func: section_import_native_func
376                .ok_or_else(|| anyhow::anyhow!("Missing import native func section"))?,
377            no_part_label: config.entis_gls_csx_no_part_label,
378        })
379    }
380
381    fn fix_image<'a, 'b>(
382        assembly: &ECSExecutionImageAssembly,
383        disasm: &mut ECSExecutionImageDisassembler<'a>,
384        writer: &mut MemWriter,
385        commands: &HashMap<u32, &'b ECSExecutionImageCommandRecord>,
386    ) -> Result<()> {
387        for cmd in assembly.iter() {
388            if cmd.code == CsicEnter {
389                disasm.stream.pos = cmd.addr as usize + 1;
390                let name_length = disasm.stream.read_u32()?;
391                if name_length != 0x80000000 {
392                    disasm.stream.pos += name_length as usize * 2;
393                } else {
394                    disasm.stream.pos += 4;
395                }
396                let num_args = disasm.stream.read_i32()?;
397                if num_args == -1 {
398                    let _flag = disasm.stream.read_u8()?;
399                    let offset = disasm.stream.pos as i64 - cmd.addr as i64;
400                    let original_addr = disasm.stream.read_i32()? as i64 + disasm.stream.pos as i64;
401                    let target_cmd = commands.get(&(original_addr as u32)).ok_or_else(|| anyhow::anyhow!(
402                        "Cannot find target command at address {:08X} for Enter instruction fixup at {:08X}",
403                        original_addr as u32,
404                        cmd.addr
405                    ))?;
406                    let new_addr = target_cmd.new_addr as i64 - cmd.new_addr as i64 - offset - 4;
407                    writer.write_i32_at(cmd.new_addr as u64 + offset as u64, new_addr as i32)?;
408                }
409            } else if matches!(cmd.code, CsicJump | CodeJumpOffset32) {
410                disasm.stream.pos = cmd.addr as usize + 1;
411                let offset = disasm.stream.pos as i64 - cmd.addr as i64;
412                let original_addr = disasm.stream.read_i32()? as i64 + disasm.stream.pos as i64;
413                let target_cmd = commands.get(&(original_addr as u32)).ok_or_else(|| anyhow::anyhow!(
414                    "Cannot find target command at address {:08X} for {:?} instruction fixup at {:08X}",
415                    original_addr as u32,
416                    cmd.code,
417                    cmd.addr
418                ))?;
419                let new_addr = target_cmd.new_addr as i64 - cmd.new_addr as i64 - offset - 4;
420                writer.write_i32_at(cmd.new_addr as u64 + offset as u64, new_addr as i32)?;
421            } else if matches!(cmd.code, CsicCJump | CodeCJumpOffset32 | CodeCNJumpOffset32) {
422                disasm.stream.pos = cmd.addr as usize + 2;
423                let offset = disasm.stream.pos as i64 - cmd.addr as i64;
424                let original_addr = disasm.stream.read_i32()? as i64 + disasm.stream.pos as i64;
425                let target_cmd = commands.get(&(original_addr as u32)).ok_or_else(|| anyhow::anyhow!(
426                    "Cannot find target command at address {:08X} for {:?} instruction fixup at {:08X}",
427                    original_addr as u32,
428                    cmd.code,
429                    cmd.addr
430                ))?;
431                let new_addr = target_cmd.new_addr as i64 - cmd.new_addr as i64 - offset - 4;
432                writer.write_i32_at(cmd.new_addr as u64 + offset as u64, new_addr as i32)?;
433            } else if cmd.code == CsicExCall {
434                disasm.stream.pos = cmd.addr as usize + 1;
435                let _arg_count = disasm.stream.read_i32()?;
436                let csom = disasm.read_csom()?;
437                let csvt = disasm.read_csvt()?;
438                if csom == CsomImmediate && csvt == CsvtInteger {
439                    let offset = disasm.stream.pos as i64 - cmd.addr as i64;
440                    let addr = disasm.stream.read_u32()?;
441                    let target_cmd = commands.get(&addr).ok_or_else(|| anyhow::anyhow!(
442                        "Cannot find target command at address {:08X} for ExCall instruction fixup at {:08X}",
443                        addr,
444                        cmd.addr
445                    ))?;
446                    let new_addr = target_cmd.new_addr;
447                    writer.write_u32_at(cmd.new_addr as u64 + offset as u64, new_addr)?;
448                }
449            } else if cmd.code == CodeCallImm32 {
450                disasm.stream.pos = cmd.addr as usize + 1;
451                let offset = disasm.stream.pos as i64 - cmd.addr as i64;
452                let addr = disasm.stream.read_u32()?;
453                let target_cmd = commands.get(&addr).ok_or_else(|| anyhow::anyhow!(
454                    "Cannot find target command at address {:08X} for CallImm32 instruction fixup at {:08X}",
455                    addr,
456                    cmd.addr
457                ))?;
458                let new_addr = target_cmd.new_addr;
459                writer.write_u32_at(cmd.new_addr as u64 + offset as u64, new_addr)?;
460            }
461        }
462        Ok(())
463    }
464
465    fn fix_references(
466        &mut self,
467        commands: &HashMap<u32, &ECSExecutionImageCommandRecord>,
468    ) -> Result<()> {
469        let mut list: Vec<u32> = commands.iter().map(|(&k, _)| k).collect();
470        list.sort();
471        for cmd in self.section_function.prologue.iter_mut() {
472            let ocmd = *cmd;
473            if let Some(tcmd) = commands.get(&ocmd) {
474                *cmd = tcmd.new_addr;
475            } else {
476                let pre_one_idx = match list.binary_search(&ocmd) {
477                    Ok(idx) => idx,
478                    Err(idx) => {
479                        if idx == 0 {
480                            idx
481                        } else {
482                            idx - 1
483                        }
484                    }
485                };
486                let tcmd = &commands[&list[pre_one_idx]];
487                if !tcmd.internal || tcmd.size + tcmd.addr < ocmd {
488                    return Err(anyhow::anyhow!(
489                        "Cannot find target command at address {:08X} for PIF prologue fixup",
490                        ocmd
491                    ));
492                }
493                let offset = tcmd.new_addr as i64 - tcmd.addr as i64;
494                *cmd = (ocmd as i64 + offset) as u32;
495            }
496        }
497        for cmd in self.section_function.epilogue.iter_mut() {
498            let ocmd = *cmd;
499            *cmd = commands
500                .get(&ocmd)
501                .ok_or_else(|| {
502                    anyhow::anyhow!(
503                        "Cannot find target command at address {:08X} for PIF epilogue fixup",
504                        ocmd
505                    )
506                })?
507                .new_addr;
508        }
509        for func in self.section_function.func_names.iter_mut() {
510            let ocmd = func.address;
511            func.address = commands
512                .get(&ocmd)
513                .ok_or_else(|| {
514                    anyhow::anyhow!(
515                        "Cannot find target command at address {:08X} for function names list fixup",
516                        ocmd
517                    )
518                })?
519                .new_addr;
520        }
521        for cmd in self.section_init_naked_func.naked_prologue.iter_mut() {
522            let ocmd = *cmd;
523            *cmd = commands
524                .get(&ocmd)
525                .ok_or_else(|| {
526                    anyhow::anyhow!(
527                        "Cannot find target command at address {:08X} for NNF prologue fixup",
528                        ocmd
529                    )
530                })?
531                .new_addr;
532        }
533        for cmd in self.section_init_naked_func.naked_epilogue.iter_mut() {
534            let ocmd = *cmd;
535            *cmd = commands
536                .get(&ocmd)
537                .ok_or_else(|| {
538                    anyhow::anyhow!(
539                        "Cannot find target command at address {:08X} for NNF epilogue fixup",
540                        ocmd
541                    )
542                })?
543                .new_addr;
544        }
545        for func in self.section_func_info.functions.iter_mut() {
546            let ocmd = func.header.address;
547            func.header.address = commands
548                .get(&ocmd)
549                .ok_or_else(|| {
550                    anyhow::anyhow!(
551                        "Cannot find target command at address {:08X} for function info list fixup",
552                        ocmd
553                    )
554                })?
555                .new_addr;
556            if func.header.bytes != u32::MAX {
557                let end_ocmd = ocmd + func.header.bytes;
558                let end_tcmd = commands
559                    .get(&end_ocmd)
560                    .ok_or_else(|| {
561                        anyhow::anyhow!(
562                            "Cannot find target command at address {:08X} for function info list fixup",
563                            end_ocmd
564                        )
565                    })?.new_addr;
566                func.header.bytes = end_tcmd - func.header.address;
567            }
568        }
569        Ok(())
570    }
571
572    fn save<'a>(&self, mut writer: Box<dyn Write + 'a>) -> Result<()> {
573        self.file_header
574            .pack(&mut writer, false, Encoding::Utf8, &None)?;
575        if self.section_header.header_size > 0 {
576            let mut mem = MemWriter::new();
577            self.section_header
578                .pack(&mut mem, false, Encoding::Utf8, &None)?;
579            writer.write_u64(ID_HEADER)?;
580            writer.write_u64(mem.data.len() as u64)?;
581            writer.write_all(&mem.into_inner())?;
582        }
583        writer.write_u64(ID_IMAGE)?;
584        writer.write_u64(self.image.data.len() as u64)?;
585        writer.write_all(&self.image.data)?;
586        if let Some(img_global) = &self.image_global {
587            writer.write_u64(ID_IMAGE_GLOBAL)?;
588            writer.write_u64(img_global.data.len() as u64)?;
589            writer.write_all(&img_global.data)?;
590        }
591        if let Some(img_const) = &self.image_const {
592            writer.write_u64(ID_IMAGE_CONST)?;
593            writer.write_u64(img_const.data.len() as u64)?;
594            writer.write_all(&img_const.data)?;
595        }
596        if let Some(img_shared) = &self.image_shared {
597            writer.write_u64(ID_IMAGE_SHARED)?;
598            writer.write_u64(img_shared.data.len() as u64)?;
599            writer.write_all(&img_shared.data)?;
600        }
601        writer.write_u64(ID_CLASS_INFO)?;
602        let mut mem = MemWriter::new();
603        self.section_class_info.pack(
604            &mut mem,
605            false,
606            Encoding::Utf8,
607            &Some(Box::new(self.section_header.clone())),
608        )?;
609        writer.write_u64(mem.data.len() as u64)?;
610        writer.write_all(&mem.into_inner())?;
611        writer.write_u64(ID_FUNCTION)?;
612        let mut mem = MemWriter::new();
613        self.section_function.pack(
614            &mut mem,
615            false,
616            Encoding::Utf8,
617            &Some(Box::new(self.section_header.clone())),
618        )?;
619        writer.write_u64(mem.data.len() as u64)?;
620        writer.write_all(&mem.into_inner())?;
621        writer.write_u64(ID_INIT_NAKED_FUNC)?;
622        let mut mem = MemWriter::new();
623        self.section_init_naked_func.pack(
624            &mut mem,
625            false,
626            Encoding::Utf8,
627            &Some(Box::new(self.section_header.clone())),
628        )?;
629        writer.write_u64(mem.data.len() as u64)?;
630        writer.write_all(&mem.into_inner())?;
631        writer.write_u64(ID_FUNC_INFO)?;
632        let mut mem = MemWriter::new();
633        self.section_func_info.pack(
634            &mut mem,
635            false,
636            Encoding::Utf8,
637            &Some(Box::new(self.section_header.clone())),
638        )?;
639        writer.write_u64(mem.data.len() as u64)?;
640        writer.write_all(&mem.into_inner())?;
641        if let Some(section_symbol_info) = &self.section_symbol_info {
642            writer.write_u64(ID_SYMBOL_INFO)?;
643            let mut mem = MemWriter::new();
644            section_symbol_info.pack(
645                &mut mem,
646                false,
647                Encoding::Utf8,
648                &Some(Box::new(self.section_header.clone())),
649            )?;
650            writer.write_u64(mem.data.len() as u64)?;
651            writer.write_all(&mem.into_inner())?;
652        }
653        if let Some(section_global) = &self.section_global {
654            writer.write_u64(ID_GLOBAL)?;
655            let mut mem = MemWriter::new();
656            section_global.pack(
657                &mut mem,
658                false,
659                Encoding::Utf8,
660                &Some(Box::new(self.section_header.clone())),
661            )?;
662            writer.write_u64(mem.data.len() as u64)?;
663            writer.write_all(&mem.into_inner())?;
664        }
665        if let Some(section_data) = &self.section_data {
666            writer.write_u64(ID_DATA)?;
667            let mut mem = MemWriter::new();
668            section_data.pack(
669                &mut mem,
670                false,
671                Encoding::Utf8,
672                &Some(Box::new(self.section_header.clone())),
673            )?;
674            writer.write_u64(mem.data.len() as u64)?;
675            writer.write_all(&mem.into_inner())?;
676        }
677        writer.write_u64(ID_CONST_STRING)?;
678        let mut mem = MemWriter::new();
679        self.section_const_string.pack(
680            &mut mem,
681            false,
682            Encoding::Utf8,
683            &Some(Box::new(self.section_header.clone())),
684        )?;
685        writer.write_u64(mem.data.len() as u64)?;
686        writer.write_all(&mem.into_inner())?;
687        if let Some(section_link_info) = &self.section_link_info {
688            writer.write_u64(ID_LINK_INFO)?;
689            let mut mem = MemWriter::new();
690            section_link_info.pack(
691                &mut mem,
692                false,
693                Encoding::Utf8,
694                &Some(Box::new(self.section_header.clone())),
695            )?;
696            writer.write_u64(mem.data.len() as u64)?;
697            writer.write_all(&mem.into_inner())?;
698        }
699        if let Some(section_link_info_ex) = &self.section_link_info_ex {
700            writer.write_u64(ID_LINK_INFO_EX)?;
701            let mut mem = MemWriter::new();
702            section_link_info_ex.pack(
703                &mut mem,
704                false,
705                Encoding::Utf8,
706                &Some(Box::new(self.section_header.clone())),
707            )?;
708            writer.write_u64(mem.data.len() as u64)?;
709            writer.write_all(&mem.into_inner())?;
710        }
711        if let Some(section_ref_func) = &self.section_ref_func {
712            writer.write_u64(ID_REF_FUNC)?;
713            let mut mem = MemWriter::new();
714            section_ref_func.pack(
715                &mut mem,
716                false,
717                Encoding::Utf8,
718                &Some(Box::new(self.section_header.clone())),
719            )?;
720            writer.write_u64(mem.data.len() as u64)?;
721            writer.write_all(&mem.into_inner())?;
722        }
723        if let Some(section_ref_code) = &self.section_ref_code {
724            writer.write_u64(ID_REF_CODE)?;
725            let mut mem = MemWriter::new();
726            section_ref_code.pack(
727                &mut mem,
728                false,
729                Encoding::Utf8,
730                &Some(Box::new(self.section_header.clone())),
731            )?;
732            writer.write_u64(mem.data.len() as u64)?;
733            writer.write_all(&mem.into_inner())?;
734        }
735        if let Some(section_ref_class) = &self.section_ref_class {
736            writer.write_u64(ID_REF_CLASS)?;
737            let mut mem = MemWriter::new();
738            section_ref_class.pack(
739                &mut mem,
740                false,
741                Encoding::Utf8,
742                &Some(Box::new(self.section_header.clone())),
743            )?;
744            writer.write_u64(mem.data.len() as u64)?;
745            writer.write_all(&mem.into_inner())?;
746        }
747        writer.write_u64(ID_IMPORT_NATIVE_FUNC)?;
748        let mut mem = MemWriter::new();
749        self.section_import_native_func.pack(
750            &mut mem,
751            false,
752            Encoding::Utf8,
753            &Some(Box::new(self.section_header.clone())),
754        )?;
755        writer.write_u64(mem.data.len() as u64)?;
756        writer.write_all(&mem.into_inner())?;
757        Ok(())
758    }
759}
760
761impl ECSImage for ECSExecutionImage {
762    fn disasm<'a>(&self, writer: Box<dyn std::io::Write + 'a>) -> Result<()> {
763        let mut disasm = ECSExecutionImageDisassembler::new(
764            self.image.to_ref(),
765            &self.section_function,
766            &self.section_func_info,
767            &self.section_import_native_func,
768            &self.section_class_info,
769            &self.section_const_string,
770            Some(writer),
771        );
772        disasm.execute()?;
773        Ok(())
774    }
775
776    fn export(&self) -> Result<Vec<Message>> {
777        let mut messages = Vec::new();
778        let mut disasm = ECSExecutionImageDisassembler::new(
779            self.image.to_ref(),
780            &self.section_function,
781            &self.section_func_info,
782            &self.section_import_native_func,
783            &self.section_class_info,
784            &self.section_const_string,
785            None,
786        );
787        disasm.execute()?;
788        let assembly = disasm.assembly.clone();
789        let mut string_stack = Vec::new();
790        let mut stacks = Vec::new();
791        let mut index = 0;
792        let len = assembly.len();
793        while index < len {
794            let cmd = &assembly[index];
795            if cmd.code == CsicLoad {
796                disasm.stream.pos = cmd.addr as usize + 1;
797                let csom = disasm.read_csom()?;
798                let csvt = disasm.read_csvt()?;
799                let is_string = csom == CsomImmediate && csvt == CsvtString;
800                if is_string {
801                    let s = disasm.get_string_literal()?;
802                    string_stack.push(s);
803                }
804                stacks.push(is_string);
805            } else if matches!(
806                cmd.code,
807                CsicCall
808                    | CsicCallMember
809                    | CsicCallNativeFunction
810                    | CsicEnter
811                    | CsicElementIndirect
812            ) {
813                string_stack.clear();
814                stacks.clear();
815            } else if cmd.code == CsicOperate {
816                disasm.stream.pos = cmd.addr as usize + 1;
817                let csot = disasm.read_csot()?;
818                if csot == CsotAdd {
819                    if string_stack.len() >= 2
820                        && index >= 2
821                        && stacks.len() >= 2
822                        && stacks[stacks.len() - 1]
823                        && stacks[stacks.len() - 2]
824                    {
825                        let s2 = string_stack.pop().unwrap();
826                        let s1 = string_stack.pop().unwrap();
827                        let s = s1 + &s2;
828                        string_stack.push(s);
829                        stacks.pop();
830                        // Remove the two previous load commands and replace with this one
831                        index += 1;
832                        continue;
833                    }
834                }
835                if let Some(is_str) = stacks.pop() {
836                    if is_str && string_stack.is_empty() {
837                        return Err(anyhow::anyhow!(
838                            "String stack is empty when processing Operate at {:08X}",
839                            cmd.addr,
840                        ));
841                    }
842                    if is_str {
843                        string_stack.pop();
844                    }
845                }
846            } else if cmd.code == CsicExCall {
847                disasm.stream.pos = cmd.addr as usize + 1;
848                let arg_count = disasm.stream.read_i32()?;
849                let csom = disasm.read_csom()?;
850                let csvt = disasm.read_csvt()?;
851                if csom == CsomImmediate {
852                    let func_name = if csvt == CsvtString {
853                        disasm.get_string_literal()?
854                    } else if csvt == CsvtInteger {
855                        let func_address = disasm.stream.read_u32()?;
856                        let func = disasm.func_map.get(&func_address).ok_or_else(|| {
857                            anyhow::anyhow!(
858                                "Function address 0x{:08X} not found in ExCall",
859                                func_address
860                            )
861                        })?;
862                        func.name.0.clone()
863                    } else {
864                        return Err(anyhow::anyhow!(
865                            "Unexpected CSVT for function name in ExCall"
866                        ));
867                    };
868                    if func_name == "WitchWizard::OutMsg" && arg_count == 8 {
869                        if string_stack.len() < 2 {
870                            return Err(anyhow::anyhow!(
871                                "String stack has less than 2 items when processing OutMsg at {:08X}",
872                                cmd.addr,
873                            ));
874                        }
875                        if string_stack.len() > 2 {
876                            eprintln!(
877                                "WARNING: String stack has more than 2 items when processing OutMsg at {:08X}",
878                                cmd.addr,
879                            );
880                            crate::COUNTER.inc_warning();
881                        }
882                        let name = string_stack[0].clone();
883                        let message = string_stack[1].clone();
884                        messages.push(Message {
885                            name: if name.is_empty() { None } else { Some(name) },
886                            message,
887                        });
888                    }
889                }
890                string_stack.clear();
891                stacks.clear();
892            } else if cmd.code == CsicCallNativeMember {
893                disasm.stream.pos = cmd.addr as usize + 1;
894                let arg_count = disasm.stream.read_i32()?;
895                let class_index = disasm.stream.read_u32()?;
896                let class = self
897                    .section_class_info
898                    .infos
899                    .get(class_index as usize)
900                    .ok_or_else(|| {
901                        anyhow::anyhow!(
902                            "Invalid class info index: {} (max {}) at {:08x}",
903                            class_index,
904                            self.section_class_info.infos.len(),
905                            cmd.addr
906                        )
907                    })?;
908                let func_index = disasm.stream.read_u32()?;
909                let func = class.method_info.get(func_index as usize).ok_or_else(|| {
910                    anyhow::anyhow!(
911                        "Invalid method info index: {} (max {}) at {:08x}",
912                        func_index,
913                        class.method_info.len(),
914                        cmd.addr
915                    )
916                })?;
917                let func_name = func.prototype_info.global_name.0.as_str();
918                if func_name == "Window::CreateDisplay@2" && arg_count == 5 {
919                    if string_stack.is_empty() {
920                        return Err(anyhow::anyhow!(
921                            "String stack is empty when processing Window::CreateDisplay@2"
922                        ));
923                    }
924                    if string_stack.len() > 1 {
925                        eprintln!(
926                            "WARNING: String stack has more than 1 item when processing Window::CreateDisplay@2 at {:08X}",
927                            cmd.addr,
928                        );
929                        crate::COUNTER.inc_warning();
930                    }
931                    let message = string_stack[0].clone();
932                    messages.push(Message {
933                        name: None,
934                        message,
935                    });
936                }
937                string_stack.clear();
938                stacks.clear();
939            }
940            index += 1;
941        }
942        Ok(messages)
943    }
944
945    fn export_multi(&self) -> Result<HashMap<String, Vec<Message>>> {
946        let mut key = String::from("global");
947        let mut messages: HashMap<String, Vec<Message>> = HashMap::new();
948        let mut disasm = ECSExecutionImageDisassembler::new(
949            self.image.to_ref(),
950            &self.section_function,
951            &self.section_func_info,
952            &self.section_import_native_func,
953            &self.section_class_info,
954            &self.section_const_string,
955            None,
956        );
957        disasm.execute()?;
958        let assembly = disasm.assembly.clone();
959        let mut string_stack = Vec::new();
960        let mut stacks = Vec::new();
961        let mut index = 0;
962        let len = assembly.len();
963        while index < len {
964            let cmd = &assembly[index];
965            if cmd.code == CsicLoad {
966                disasm.stream.pos = cmd.addr as usize + 1;
967                let csom = disasm.read_csom()?;
968                let csvt = disasm.read_csvt()?;
969                let is_string = csom == CsomImmediate && csvt == CsvtString;
970                if is_string {
971                    let s = disasm.get_string_literal()?;
972                    string_stack.push(s);
973                }
974                stacks.push(is_string);
975            } else if matches!(
976                cmd.code,
977                CsicCall
978                    | CsicCallMember
979                    | CsicCallNativeFunction
980                    | CsicEnter
981                    | CsicElementIndirect
982            ) {
983                string_stack.clear();
984                stacks.clear();
985            } else if cmd.code == CsicOperate {
986                disasm.stream.pos = cmd.addr as usize + 1;
987                let csot = disasm.read_csot()?;
988                if csot == CsotAdd {
989                    if string_stack.len() >= 2
990                        && index >= 2
991                        && stacks.len() >= 2
992                        && stacks[stacks.len() - 1]
993                        && stacks[stacks.len() - 2]
994                    {
995                        let s2 = string_stack.pop().unwrap();
996                        let s1 = string_stack.pop().unwrap();
997                        let s = s1 + &s2;
998                        string_stack.push(s);
999                        stacks.pop();
1000                        // Remove the two previous load commands and replace with this one
1001                        index += 1;
1002                        continue;
1003                    }
1004                }
1005                if let Some(is_str) = stacks.pop() {
1006                    if is_str && string_stack.is_empty() {
1007                        return Err(anyhow::anyhow!(
1008                            "String stack is empty when processing Operate at {:08X}",
1009                            cmd.addr,
1010                        ));
1011                    }
1012                    if is_str {
1013                        string_stack.pop();
1014                    }
1015                }
1016            } else if cmd.code == CsicExCall {
1017                disasm.stream.pos = cmd.addr as usize + 1;
1018                let arg_count = disasm.stream.read_i32()?;
1019                let csom = disasm.read_csom()?;
1020                let csvt = disasm.read_csvt()?;
1021                if csom == CsomImmediate {
1022                    let func_name = if csvt == CsvtString {
1023                        disasm.get_string_literal()?
1024                    } else if csvt == CsvtInteger {
1025                        let func_address = disasm.stream.read_u32()?;
1026                        let func = disasm.func_map.get(&func_address).ok_or_else(|| {
1027                            anyhow::anyhow!(
1028                                "Function address 0x{:08X} not found in ExCall",
1029                                func_address
1030                            )
1031                        })?;
1032                        func.name.0.clone()
1033                    } else {
1034                        return Err(anyhow::anyhow!(
1035                            "Unexpected CSVT for function name in ExCall"
1036                        ));
1037                    };
1038                    if func_name == "WitchWizard::SetPastLabel"
1039                        && arg_count == 2
1040                        && !self.no_part_label
1041                    {
1042                        if string_stack.is_empty() {
1043                            return Err(anyhow::anyhow!(
1044                                "String stack is empty when processing SetPastLabel"
1045                            ));
1046                        }
1047                        if string_stack.len() > 1 {
1048                            eprintln!(
1049                                "WARNING: String stack has more than 1 item when processing SetPastLabel at {:08X}",
1050                                cmd.addr,
1051                            );
1052                            crate::COUNTER.inc_warning();
1053                        }
1054                        key = string_stack[0].clone();
1055                    } else if func_name == "WitchWizard::OutMsg" && arg_count == 8 {
1056                        if string_stack.len() < 2 {
1057                            return Err(anyhow::anyhow!(
1058                                "String stack has less than 2 items when processing OutMsg at {:08X}",
1059                                cmd.addr,
1060                            ));
1061                        }
1062                        if string_stack.len() > 2 {
1063                            eprintln!(
1064                                "WARNING: String stack has more than 2 items when processing OutMsg at {:08X}",
1065                                cmd.addr,
1066                            );
1067                            crate::COUNTER.inc_warning();
1068                        }
1069                        let name = string_stack[0].clone();
1070                        let message = string_stack[1].clone();
1071                        messages
1072                            .entry(key.clone())
1073                            .or_insert_with(Vec::new)
1074                            .push(Message {
1075                                name: if name.is_empty() { None } else { Some(name) },
1076                                message,
1077                            });
1078                    } else if func_name == "WitchWizard::SetCurrentScriptName" && arg_count == 2 {
1079                        if string_stack.is_empty() {
1080                            return Err(anyhow::anyhow!(
1081                                "String stack is empty when processing SetCurrentScriptName"
1082                            ));
1083                        }
1084                        if string_stack.len() > 1 {
1085                            eprintln!(
1086                                "WARNING: String stack has more than 1 item when processing SetCurrentScriptName at {:08X}",
1087                                cmd.addr,
1088                            );
1089                            crate::COUNTER.inc_warning();
1090                        }
1091                        key = string_stack[0].clone();
1092                    }
1093                }
1094                string_stack.clear();
1095                stacks.clear();
1096            } else if cmd.code == CsicCallNativeMember {
1097                disasm.stream.pos = cmd.addr as usize + 1;
1098                let arg_count = disasm.stream.read_i32()?;
1099                let class_index = disasm.stream.read_u32()?;
1100                let class = self
1101                    .section_class_info
1102                    .infos
1103                    .get(class_index as usize)
1104                    .ok_or_else(|| {
1105                        anyhow::anyhow!(
1106                            "Invalid class info index: {} (max {}) at {:08x}",
1107                            class_index,
1108                            self.section_class_info.infos.len(),
1109                            cmd.addr
1110                        )
1111                    })?;
1112                let func_index = disasm.stream.read_u32()?;
1113                let func = class.method_info.get(func_index as usize).ok_or_else(|| {
1114                    anyhow::anyhow!(
1115                        "Invalid method info index: {} (max {}) at {:08x}",
1116                        func_index,
1117                        class.method_info.len(),
1118                        cmd.addr
1119                    )
1120                })?;
1121                let func_name = func.prototype_info.global_name.0.as_str();
1122                if func_name == "Window::CreateDisplay@2" && arg_count == 5 {
1123                    if string_stack.is_empty() {
1124                        return Err(anyhow::anyhow!(
1125                            "String stack is empty when processing Window::CreateDisplay@2"
1126                        ));
1127                    }
1128                    if string_stack.len() > 1 {
1129                        eprintln!(
1130                            "WARNING: String stack has more than 1 item when processing Window::CreateDisplay@2 at {:08X}",
1131                            cmd.addr,
1132                        );
1133                        crate::COUNTER.inc_warning();
1134                    }
1135                    let message = string_stack[0].clone();
1136                    messages
1137                        .entry(key.clone())
1138                        .or_insert_with(Vec::new)
1139                        .push(Message {
1140                            name: None,
1141                            message,
1142                        });
1143                }
1144                string_stack.clear();
1145                stacks.clear();
1146            }
1147            index += 1;
1148        }
1149        Ok(messages)
1150    }
1151
1152    fn export_all(&self) -> Result<Vec<String>> {
1153        let mut disasm = ECSExecutionImageDisassembler::new(
1154            self.image.to_ref(),
1155            &self.section_function,
1156            &self.section_func_info,
1157            &self.section_import_native_func,
1158            &self.section_class_info,
1159            &self.section_const_string,
1160            None,
1161        );
1162        disasm.execute()?;
1163        let mut messages = Vec::new();
1164        for cmd in disasm.assembly.clone().iter() {
1165            if cmd.code == CsicLoad {
1166                disasm.stream.pos = cmd.addr as usize + 1;
1167                let csom = disasm.read_csom()?;
1168                let csvt = disasm.read_csvt()?;
1169                if csom == CsomImmediate && csvt == CsvtString {
1170                    let s = disasm.get_string_literal()?;
1171                    messages.push(s);
1172                }
1173            }
1174        }
1175        Ok(messages)
1176    }
1177
1178    fn import<'a>(
1179        &self,
1180        messages: Vec<Message>,
1181        file: Box<dyn WriteSeek + 'a>,
1182        replacement: Option<&'a ReplacementTable>,
1183    ) -> Result<()> {
1184        let mut cloned = self.clone();
1185        let mut mess = messages.iter();
1186        let mut mes = mess.next();
1187        let mut disasm = ECSExecutionImageDisassembler::new(
1188            self.image.to_ref(),
1189            &self.section_function,
1190            &self.section_func_info,
1191            &self.section_import_native_func,
1192            &self.section_class_info,
1193            &self.section_const_string,
1194            None,
1195        );
1196        disasm.execute()?;
1197        let mut constr_map: HashMap<String, u32> = cloned
1198            .section_const_string
1199            .strings
1200            .iter()
1201            .enumerate()
1202            .map(|(i, s)| (s.string.0.clone(), i as u32))
1203            .collect();
1204        let mut assembly = disasm.assembly.clone();
1205        let mut new_image = MemWriter::new();
1206        let mut index = 0;
1207        let mut dumped_index = 0;
1208        let mut string_stack = Vec::new();
1209        let mut stacks = Vec::new();
1210        let len = assembly.len();
1211        while index < len {
1212            let cmd = assembly[index].clone();
1213            if cmd.code == CsicLoad {
1214                disasm.stream.pos = cmd.addr as usize + 1;
1215                let csom = disasm.read_csom()?;
1216                let csvt = disasm.read_csvt()?;
1217                let is_string = csom == CsomImmediate && csvt == CsvtString;
1218                if is_string {
1219                    let s = disasm.get_string_literal()?;
1220                    string_stack.push((Some(index), s));
1221                }
1222                stacks.push(is_string);
1223            } else if matches!(
1224                cmd.code,
1225                CsicCall
1226                    | CsicCallMember
1227                    | CsicCallNativeFunction
1228                    | CsicEnter
1229                    | CsicElementIndirect
1230            ) {
1231                string_stack.clear();
1232                stacks.clear();
1233                while dumped_index <= index {
1234                    let tcmd = &mut assembly[dumped_index];
1235                    tcmd.new_addr = new_image.pos as u32;
1236                    // Copy original command
1237                    new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1238                    dumped_index += 1;
1239                }
1240            } else if cmd.code == CsicOperate {
1241                disasm.stream.pos = cmd.addr as usize + 1;
1242                let csot = disasm.read_csot()?;
1243                if csot == CsotAdd {
1244                    if string_stack.len() >= 2
1245                        && index >= 2
1246                        && stacks.len() >= 2
1247                        && stacks[stacks.len() - 1]
1248                        && stacks[stacks.len() - 2]
1249                    {
1250                        let s2 = string_stack.pop().unwrap().1;
1251                        let s1 = string_stack.pop().unwrap().1;
1252                        let s = s1 + &s2;
1253                        string_stack.push((None, s));
1254                        stacks.pop();
1255                        // Remove the two previous load commands and replace with this one
1256                        index += 1;
1257                        continue;
1258                    }
1259                }
1260                if let Some(is_str) = stacks.pop() {
1261                    if is_str && string_stack.is_empty() {
1262                        return Err(anyhow::anyhow!(
1263                            "String stack is empty when processing Operate at {:08X}",
1264                            cmd.addr,
1265                        ));
1266                    }
1267                    if is_str {
1268                        string_stack.pop();
1269                    }
1270                }
1271            } else if cmd.code == CsicExCall {
1272                disasm.stream.pos = cmd.addr as usize + 1;
1273                let arg_count = disasm.stream.read_i32()?;
1274                let csom = disasm.read_csom()?;
1275                let csvt = disasm.read_csvt()?;
1276                if csom == CsomImmediate {
1277                    let func_name = if csvt == CsvtString {
1278                        disasm.get_string_literal()?
1279                    } else if csvt == CsvtInteger {
1280                        let func_address = disasm.stream.read_u32()?;
1281                        let func = disasm.func_map.get(&func_address).ok_or_else(|| {
1282                            anyhow::anyhow!(
1283                                "Function address 0x{:08X} not found in ExCall",
1284                                func_address
1285                            )
1286                        })?;
1287                        func.name.0.clone()
1288                    } else {
1289                        return Err(anyhow::anyhow!(
1290                            "Unexpected CSVT for function name in ExCall"
1291                        ));
1292                    };
1293                    if func_name == "WitchWizard::OutMsg" && arg_count == 8 {
1294                        if string_stack.len() < 2 {
1295                            return Err(anyhow::anyhow!(
1296                                "String stack has less than 2 items when processing OutMsg at {:08X}",
1297                                cmd.addr,
1298                            ));
1299                        }
1300                        if string_stack.len() > 2 {
1301                            eprintln!(
1302                                "WARNING: String stack has more than 2 items when processing OutMsg at {:08X}",
1303                                cmd.addr,
1304                            );
1305                            crate::COUNTER.inc_warning();
1306                        }
1307                        let name = string_stack[0].clone();
1308                        let message = string_stack[1].clone();
1309                        let message_idx = message.0.ok_or_else(|| {
1310                            anyhow::anyhow!(
1311                                "Cannot replace constructed string for message at {:08X}",
1312                                cmd.addr,
1313                            )
1314                        })?;
1315                        if !name.1.is_empty() {
1316                            let name_idx = name.0.ok_or_else(|| {
1317                                anyhow::anyhow!(
1318                                    "Cannot replace constructed string for message name at {:08X}",
1319                                    cmd.addr,
1320                                )
1321                            })?;
1322                            let mut name = match mes {
1323                                Some(m) => match &m.name {
1324                                    Some(n) => n.clone(),
1325                                    None => {
1326                                        return Err(anyhow::anyhow!(
1327                                            "No name available for OutMsg at {:08X}",
1328                                            cmd.addr,
1329                                        ));
1330                                    }
1331                                },
1332                                None => {
1333                                    return Err(anyhow::anyhow!(
1334                                        "No more messages available for OutMsg at {:08X}",
1335                                        cmd.addr,
1336                                    ));
1337                                }
1338                            };
1339                            if let Some(repl) = replacement {
1340                                for (k, v) in repl.map.iter() {
1341                                    name = name.replace(k, v);
1342                                }
1343                            }
1344                            let constr_idx = if let Some(&idx) = constr_map.get(&name) {
1345                                idx
1346                            } else {
1347                                // Add new string to const string section
1348                                let idx = cloned.section_const_string.strings.len() as u32;
1349                                cloned.section_const_string.strings.push(ConstStringEntry {
1350                                    string: WideString(name.clone()),
1351                                    refs: DWordArray { data: Vec::new() },
1352                                });
1353                                constr_map.insert(name.clone(), idx);
1354                                idx
1355                            };
1356                            while dumped_index < name_idx {
1357                                let tcmd = &mut assembly[dumped_index];
1358                                tcmd.new_addr = new_image.pos as u32;
1359                                // Copy original command
1360                                new_image.write_from(
1361                                    &mut disasm.stream,
1362                                    tcmd.addr as u64,
1363                                    tcmd.size as u64,
1364                                )?;
1365                                dumped_index += 1;
1366                            }
1367                            let name_cmd = &mut assembly[name_idx];
1368                            name_cmd.new_addr = new_image.pos as u32;
1369                            // Write new load command for name
1370                            new_image.write_u8(CsicLoad as u8)?;
1371                            new_image.write_u8(CsomImmediate as u8)?;
1372                            new_image.write_u8(CsvtString as u8)?;
1373                            new_image.write_u32(0x80000000)?;
1374                            new_image.write_u32(constr_idx)?;
1375                            dumped_index += 1;
1376                        }
1377                        let mut message = match mes {
1378                            Some(m) => m.message.clone(),
1379                            None => {
1380                                return Err(anyhow::anyhow!(
1381                                    "No more messages available for OutMsg at {:08X}",
1382                                    cmd.addr,
1383                                ));
1384                            }
1385                        };
1386                        mes = mess.next();
1387                        if let Some(repl) = replacement {
1388                            for (k, v) in repl.map.iter() {
1389                                message = message.replace(k, v);
1390                            }
1391                        }
1392                        while dumped_index < message_idx {
1393                            let tcmd = &mut assembly[dumped_index];
1394                            tcmd.new_addr = new_image.pos as u32;
1395                            // Copy original command
1396                            new_image.write_from(
1397                                &mut disasm.stream,
1398                                tcmd.addr as u64,
1399                                tcmd.size as u64,
1400                            )?;
1401                            dumped_index += 1;
1402                        }
1403                        let message_cmd = &mut assembly[message_idx];
1404                        message_cmd.new_addr = new_image.pos as u32;
1405                        // Write new load command for message
1406                        new_image.write_u8(CsicLoad as u8)?;
1407                        new_image.write_u8(CsomImmediate as u8)?;
1408                        new_image.write_u8(CsvtString as u8)?;
1409                        new_image.write_u32(0x80000000)?;
1410                        let constr_idx = if let Some(&idx) = constr_map.get(&message) {
1411                            idx
1412                        } else {
1413                            // Add new string to const string section
1414                            let idx = cloned.section_const_string.strings.len() as u32;
1415                            cloned.section_const_string.strings.push(ConstStringEntry {
1416                                string: WideString(message.clone()),
1417                                refs: DWordArray { data: Vec::new() },
1418                            });
1419                            constr_map.insert(message.clone(), idx);
1420                            idx
1421                        };
1422                        new_image.write_u32(constr_idx)?;
1423                        dumped_index += 1;
1424                    }
1425                }
1426                string_stack.clear();
1427                stacks.clear();
1428                while dumped_index <= index {
1429                    let tcmd = &mut assembly[dumped_index];
1430                    tcmd.new_addr = new_image.pos as u32;
1431                    // Copy original command
1432                    new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1433                    dumped_index += 1;
1434                }
1435            } else if cmd.code == CsicCallNativeMember {
1436                disasm.stream.pos = cmd.addr as usize + 1;
1437                let arg_count = disasm.stream.read_i32()?;
1438                let class_index = disasm.stream.read_u32()?;
1439                let class = self
1440                    .section_class_info
1441                    .infos
1442                    .get(class_index as usize)
1443                    .ok_or_else(|| {
1444                        anyhow::anyhow!(
1445                            "Invalid class info index: {} (max {}) at {:08x}",
1446                            class_index,
1447                            self.section_class_info.infos.len(),
1448                            cmd.addr
1449                        )
1450                    })?;
1451                let func_index = disasm.stream.read_u32()?;
1452                let func = class.method_info.get(func_index as usize).ok_or_else(|| {
1453                    anyhow::anyhow!(
1454                        "Invalid method info index: {} (max {}) at {:08x}",
1455                        func_index,
1456                        class.method_info.len(),
1457                        cmd.addr
1458                    )
1459                })?;
1460                let func_name = func.prototype_info.global_name.0.as_str();
1461                if func_name == "Window::CreateDisplay@2" && arg_count == 5 {
1462                    if string_stack.is_empty() {
1463                        return Err(anyhow::anyhow!(
1464                            "String stack is empty when processing Window::CreateDisplay@2"
1465                        ));
1466                    }
1467                    if string_stack.len() > 1 {
1468                        eprintln!(
1469                            "WARNING: String stack has more than 1 item when processing Window::CreateDisplay@2 at {:08X}",
1470                            cmd.addr,
1471                        );
1472                        crate::COUNTER.inc_warning();
1473                    }
1474                    let message = string_stack[0].clone();
1475                    let message_idx = message.0.ok_or_else(|| {
1476                        anyhow::anyhow!(
1477                            "Cannot replace constructed string for message at {:08X}",
1478                            cmd.addr,
1479                        )
1480                    })?;
1481                    let mut message = match mes {
1482                        Some(m) => m.message.clone(),
1483                        None => {
1484                            return Err(anyhow::anyhow!(
1485                                "No more messages available for Window::CreateDisplay@2 at {:08X}",
1486                                cmd.addr,
1487                            ));
1488                        }
1489                    };
1490                    mes = mess.next();
1491                    if let Some(repl) = replacement {
1492                        for (k, v) in repl.map.iter() {
1493                            message = message.replace(k, v);
1494                        }
1495                    }
1496                    while dumped_index < message_idx {
1497                        let tcmd = &mut assembly[dumped_index];
1498                        tcmd.new_addr = new_image.pos as u32;
1499                        // Copy original command
1500                        new_image.write_from(
1501                            &mut disasm.stream,
1502                            tcmd.addr as u64,
1503                            tcmd.size as u64,
1504                        )?;
1505                        dumped_index += 1;
1506                    }
1507                    let message_cmd = &mut assembly[message_idx];
1508                    message_cmd.new_addr = new_image.pos as u32;
1509                    // Write new load command for message
1510                    new_image.write_u8(CsicLoad as u8)?;
1511                    new_image.write_u8(CsomImmediate as u8)?;
1512                    new_image.write_u8(CsvtString as u8)?;
1513                    new_image.write_u32(0x80000000)?;
1514                    let constr_idx = if let Some(&idx) = constr_map.get(&message) {
1515                        idx
1516                    } else {
1517                        // Add new string to const string section
1518                        let idx = cloned.section_const_string.strings.len() as u32;
1519                        cloned.section_const_string.strings.push(ConstStringEntry {
1520                            string: WideString(message.clone()),
1521                            refs: DWordArray { data: Vec::new() },
1522                        });
1523                        constr_map.insert(message.clone(), idx);
1524                        idx
1525                    };
1526                    new_image.write_u32(constr_idx)?;
1527                    dumped_index += 1;
1528                }
1529                string_stack.clear();
1530                stacks.clear();
1531                while dumped_index <= index {
1532                    let tcmd = &mut assembly[dumped_index];
1533                    tcmd.new_addr = new_image.pos as u32;
1534                    // Copy original command
1535                    new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1536                    dumped_index += 1;
1537                }
1538            }
1539            index += 1;
1540        }
1541        while dumped_index < len {
1542            let tcmd = &mut assembly[dumped_index];
1543            tcmd.new_addr = new_image.pos as u32;
1544            // Copy original command
1545            new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1546            dumped_index += 1;
1547        }
1548        if mes.is_some() || mess.next().is_some() {
1549            return Err(anyhow::anyhow!("Not all messages were used."));
1550        }
1551        let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
1552            assembly.iter().map(|c| (c.addr, c)).collect();
1553        Self::fix_image(&assembly, &mut disasm, &mut new_image, &commands)?;
1554        cloned.image = MemReader::new(new_image.into_inner());
1555        cloned.fix_references(&commands)?;
1556        cloned.save(file)?;
1557        Ok(())
1558    }
1559
1560    fn import_multi<'a>(
1561        &self,
1562        mut messages: HashMap<String, Vec<Message>>,
1563        file: Box<dyn WriteSeek + 'a>,
1564        replacement: Option<&'a ReplacementTable>,
1565    ) -> Result<()> {
1566        let mut cloned = self.clone();
1567        let mut key = String::from("global");
1568        let mut disasm = ECSExecutionImageDisassembler::new(
1569            self.image.to_ref(),
1570            &self.section_function,
1571            &self.section_func_info,
1572            &self.section_import_native_func,
1573            &self.section_class_info,
1574            &self.section_const_string,
1575            None,
1576        );
1577        disasm.execute()?;
1578        let mut constr_map: HashMap<String, u32> = cloned
1579            .section_const_string
1580            .strings
1581            .iter()
1582            .enumerate()
1583            .map(|(i, s)| (s.string.0.clone(), i as u32))
1584            .collect();
1585        let mut assembly = disasm.assembly.clone();
1586        let mut new_image = MemWriter::new();
1587        let mut index = 0;
1588        let mut dumped_index = 0;
1589        let mut string_stack = Vec::new();
1590        let mut stacks = Vec::new();
1591        let len = assembly.len();
1592        while index < len {
1593            let cmd = assembly[index].clone();
1594            if cmd.code == CsicLoad {
1595                disasm.stream.pos = cmd.addr as usize + 1;
1596                let csom = disasm.read_csom()?;
1597                let csvt = disasm.read_csvt()?;
1598                let is_string = csom == CsomImmediate && csvt == CsvtString;
1599                if is_string {
1600                    let s = disasm.get_string_literal()?;
1601                    string_stack.push((Some(index), s));
1602                }
1603                stacks.push(is_string);
1604            } else if matches!(
1605                cmd.code,
1606                CsicCall
1607                    | CsicCallMember
1608                    | CsicCallNativeFunction
1609                    | CsicEnter
1610                    | CsicElementIndirect
1611            ) {
1612                string_stack.clear();
1613                stacks.clear();
1614                while dumped_index <= index {
1615                    let tcmd = &mut assembly[dumped_index];
1616                    tcmd.new_addr = new_image.pos as u32;
1617                    // Copy original command
1618                    new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1619                    dumped_index += 1;
1620                }
1621            } else if cmd.code == CsicOperate {
1622                disasm.stream.pos = cmd.addr as usize + 1;
1623                let csot = disasm.read_csot()?;
1624                if csot == CsotAdd {
1625                    if string_stack.len() >= 2
1626                        && index >= 2
1627                        && stacks.len() >= 2
1628                        && stacks[stacks.len() - 1]
1629                        && stacks[stacks.len() - 2]
1630                    {
1631                        let s2 = string_stack.pop().unwrap().1;
1632                        let s1 = string_stack.pop().unwrap().1;
1633                        let s = s1 + &s2;
1634                        string_stack.push((None, s));
1635                        stacks.pop();
1636                        // Remove the two previous load commands and replace with this one
1637                        index += 1;
1638                        continue;
1639                    }
1640                }
1641                if let Some(is_str) = stacks.pop() {
1642                    if is_str && string_stack.is_empty() {
1643                        return Err(anyhow::anyhow!(
1644                            "String stack is empty when processing Operate at {:08X}",
1645                            cmd.addr,
1646                        ));
1647                    }
1648                    if is_str {
1649                        string_stack.pop();
1650                    }
1651                }
1652            } else if cmd.code == CsicExCall {
1653                disasm.stream.pos = cmd.addr as usize + 1;
1654                let arg_count = disasm.stream.read_i32()?;
1655                let csom = disasm.read_csom()?;
1656                let csvt = disasm.read_csvt()?;
1657                if csom == CsomImmediate {
1658                    let func_name = if csvt == CsvtString {
1659                        disasm.get_string_literal()?
1660                    } else if csvt == CsvtInteger {
1661                        let func_address = disasm.stream.read_u32()?;
1662                        let func = disasm.func_map.get(&func_address).ok_or_else(|| {
1663                            anyhow::anyhow!(
1664                                "Function address 0x{:08X} not found in ExCall",
1665                                func_address
1666                            )
1667                        })?;
1668                        func.name.0.clone()
1669                    } else {
1670                        return Err(anyhow::anyhow!(
1671                            "Unexpected CSVT for function name in ExCall"
1672                        ));
1673                    };
1674                    if func_name == "WitchWizard::SetPastLabel"
1675                        && arg_count == 2
1676                        && !self.no_part_label
1677                    {
1678                        if string_stack.is_empty() {
1679                            return Err(anyhow::anyhow!(
1680                                "String stack is empty when processing SetPastLabel"
1681                            ));
1682                        }
1683                        if string_stack.len() > 1 {
1684                            eprintln!(
1685                                "WARNING: String stack has more than 1 item when processing SetPastLabel at {:08X}",
1686                                cmd.addr,
1687                            );
1688                            crate::COUNTER.inc_warning();
1689                        }
1690                        key = string_stack[0].1.clone();
1691                    } else if func_name == "WitchWizard::OutMsg" && arg_count == 8 {
1692                        if string_stack.len() < 2 {
1693                            return Err(anyhow::anyhow!(
1694                                "String stack has less than 2 items when processing OutMsg at {:08X}",
1695                                cmd.addr,
1696                            ));
1697                        }
1698                        if string_stack.len() > 2 {
1699                            eprintln!(
1700                                "WARNING: String stack has more than 2 items when processing OutMsg at {:08X}",
1701                                cmd.addr,
1702                            );
1703                            crate::COUNTER.inc_warning();
1704                        }
1705                        let name = string_stack[0].clone();
1706                        let message = string_stack[1].clone();
1707                        let message_idx = message.0.ok_or_else(|| {
1708                            anyhow::anyhow!(
1709                                "Cannot replace constructed string for message at {:08X}",
1710                                cmd.addr,
1711                            )
1712                        })?;
1713                        if !name.1.is_empty() {
1714                            let name_idx = name.0.ok_or_else(|| {
1715                                anyhow::anyhow!(
1716                                    "Cannot replace constructed string for message name at {:08X}",
1717                                    cmd.addr,
1718                                )
1719                            })?;
1720                            let mut name = messages
1721                                .get_mut(&key)
1722                                .and_then(|messages| messages.first_mut().map(|m| m.name.take()))
1723                                .flatten()
1724                                .ok_or(anyhow::anyhow!(
1725                                    "No available name message at {:08X}.",
1726                                    cmd.addr
1727                                ))?;
1728                            if let Some(repl) = replacement {
1729                                for (k, v) in repl.map.iter() {
1730                                    name = name.replace(k, v);
1731                                }
1732                            }
1733                            let constr_idx = if let Some(&idx) = constr_map.get(&name) {
1734                                idx
1735                            } else {
1736                                // Add new string to const string section
1737                                let idx = cloned.section_const_string.strings.len() as u32;
1738                                cloned.section_const_string.strings.push(ConstStringEntry {
1739                                    string: WideString(name.clone()),
1740                                    refs: DWordArray { data: Vec::new() },
1741                                });
1742                                constr_map.insert(name.clone(), idx);
1743                                idx
1744                            };
1745                            while dumped_index < name_idx {
1746                                let tcmd = &mut assembly[dumped_index];
1747                                tcmd.new_addr = new_image.pos as u32;
1748                                // Copy original command
1749                                new_image.write_from(
1750                                    &mut disasm.stream,
1751                                    tcmd.addr as u64,
1752                                    tcmd.size as u64,
1753                                )?;
1754                                dumped_index += 1;
1755                            }
1756                            let name_cmd = &mut assembly[name_idx];
1757                            name_cmd.new_addr = new_image.pos as u32;
1758                            // Write new load command for name
1759                            new_image.write_u8(CsicLoad as u8)?;
1760                            new_image.write_u8(CsomImmediate as u8)?;
1761                            new_image.write_u8(CsvtString as u8)?;
1762                            new_image.write_u32(0x80000000)?;
1763                            new_image.write_u32(constr_idx)?;
1764                            dumped_index += 1;
1765                        }
1766                        let mut message = messages
1767                            .get_mut(&key)
1768                            .and_then(|messages| messages.pop_first())
1769                            .ok_or(anyhow::anyhow!(
1770                                "No available message for AddSelect at {:08X}.",
1771                                cmd.addr
1772                            ))?
1773                            .message;
1774                        if let Some(repl) = replacement {
1775                            for (k, v) in repl.map.iter() {
1776                                message = message.replace(k, v);
1777                            }
1778                        }
1779                        while dumped_index < message_idx {
1780                            let tcmd = &mut assembly[dumped_index];
1781                            tcmd.new_addr = new_image.pos as u32;
1782                            // Copy original command
1783                            new_image.write_from(
1784                                &mut disasm.stream,
1785                                tcmd.addr as u64,
1786                                tcmd.size as u64,
1787                            )?;
1788                            dumped_index += 1;
1789                        }
1790                        let message_cmd = &mut assembly[message_idx];
1791                        message_cmd.new_addr = new_image.pos as u32;
1792                        // Write new load command for message
1793                        new_image.write_u8(CsicLoad as u8)?;
1794                        new_image.write_u8(CsomImmediate as u8)?;
1795                        new_image.write_u8(CsvtString as u8)?;
1796                        new_image.write_u32(0x80000000)?;
1797                        let constr_idx = if let Some(&idx) = constr_map.get(&message) {
1798                            idx
1799                        } else {
1800                            // Add new string to const string section
1801                            let idx = cloned.section_const_string.strings.len() as u32;
1802                            cloned.section_const_string.strings.push(ConstStringEntry {
1803                                string: WideString(message.clone()),
1804                                refs: DWordArray { data: Vec::new() },
1805                            });
1806                            constr_map.insert(message.clone(), idx);
1807                            idx
1808                        };
1809                        new_image.write_u32(constr_idx)?;
1810                        dumped_index += 1;
1811                    } else if func_name == "WitchWizard::SetCurrentScriptName" && arg_count == 2 {
1812                        if string_stack.is_empty() {
1813                            return Err(anyhow::anyhow!(
1814                                "String stack is empty when processing SetCurrentScriptName"
1815                            ));
1816                        }
1817                        if string_stack.len() > 1 {
1818                            eprintln!(
1819                                "WARNING: String stack has more than 1 item when processing SetCurrentScriptName at {:08X}",
1820                                cmd.addr,
1821                            );
1822                            crate::COUNTER.inc_warning();
1823                        }
1824                        key = string_stack[0].1.clone();
1825                    }
1826                }
1827                string_stack.clear();
1828                stacks.clear();
1829                while dumped_index <= index {
1830                    let tcmd = &mut assembly[dumped_index];
1831                    tcmd.new_addr = new_image.pos as u32;
1832                    // Copy original command
1833                    new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1834                    dumped_index += 1;
1835                }
1836            } else if cmd.code == CsicCallNativeMember {
1837                disasm.stream.pos = cmd.addr as usize + 1;
1838                let arg_count = disasm.stream.read_i32()?;
1839                let class_index = disasm.stream.read_u32()?;
1840                let class = self
1841                    .section_class_info
1842                    .infos
1843                    .get(class_index as usize)
1844                    .ok_or_else(|| {
1845                        anyhow::anyhow!(
1846                            "Invalid class info index: {} (max {}) at {:08x}",
1847                            class_index,
1848                            self.section_class_info.infos.len(),
1849                            cmd.addr
1850                        )
1851                    })?;
1852                let func_index = disasm.stream.read_u32()?;
1853                let func = class.method_info.get(func_index as usize).ok_or_else(|| {
1854                    anyhow::anyhow!(
1855                        "Invalid method info index: {} (max {}) at {:08x}",
1856                        func_index,
1857                        class.method_info.len(),
1858                        cmd.addr
1859                    )
1860                })?;
1861                let func_name = func.prototype_info.global_name.0.as_str();
1862                if func_name == "Window::CreateDisplay@2" && arg_count == 5 {
1863                    if string_stack.is_empty() {
1864                        return Err(anyhow::anyhow!(
1865                            "String stack is empty when processing Window::CreateDisplay@2"
1866                        ));
1867                    }
1868                    if string_stack.len() > 1 {
1869                        eprintln!(
1870                            "WARNING: String stack has more than 1 item when processing Window::CreateDisplay@2 at {:08X}",
1871                            cmd.addr,
1872                        );
1873                        crate::COUNTER.inc_warning();
1874                    }
1875                    let message = string_stack[0].clone();
1876                    let message_idx = message.0.ok_or_else(|| {
1877                        anyhow::anyhow!(
1878                            "Cannot replace constructed string for message at {:08X}",
1879                            cmd.addr,
1880                        )
1881                    })?;
1882                    let mut message = messages
1883                        .get_mut(&key)
1884                        .and_then(|messages| messages.pop_first())
1885                        .ok_or(anyhow::anyhow!(
1886                            "No available message for CreateDisplay at {:08X}.",
1887                            cmd.addr,
1888                        ))?
1889                        .message;
1890                    if let Some(repl) = replacement {
1891                        for (k, v) in repl.map.iter() {
1892                            message = message.replace(k, v);
1893                        }
1894                    }
1895                    while dumped_index < message_idx {
1896                        let tcmd = &mut assembly[dumped_index];
1897                        tcmd.new_addr = new_image.pos as u32;
1898                        // Copy original command
1899                        new_image.write_from(
1900                            &mut disasm.stream,
1901                            tcmd.addr as u64,
1902                            tcmd.size as u64,
1903                        )?;
1904                        dumped_index += 1;
1905                    }
1906                    let message_cmd = &mut assembly[message_idx];
1907                    message_cmd.new_addr = new_image.pos as u32;
1908                    // Write new load command for message
1909                    new_image.write_u8(CsicLoad as u8)?;
1910                    new_image.write_u8(CsomImmediate as u8)?;
1911                    new_image.write_u8(CsvtString as u8)?;
1912                    new_image.write_u32(0x80000000)?;
1913                    let constr_idx = if let Some(&idx) = constr_map.get(&message) {
1914                        idx
1915                    } else {
1916                        // Add new string to const string section
1917                        let idx = cloned.section_const_string.strings.len() as u32;
1918                        cloned.section_const_string.strings.push(ConstStringEntry {
1919                            string: WideString(message.clone()),
1920                            refs: DWordArray { data: Vec::new() },
1921                        });
1922                        constr_map.insert(message.clone(), idx);
1923                        idx
1924                    };
1925                    new_image.write_u32(constr_idx)?;
1926                    dumped_index += 1;
1927                }
1928                string_stack.clear();
1929                stacks.clear();
1930                while dumped_index <= index {
1931                    let tcmd = &mut assembly[dumped_index];
1932                    tcmd.new_addr = new_image.pos as u32;
1933                    // Copy original command
1934                    new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1935                    dumped_index += 1;
1936                }
1937            }
1938            index += 1;
1939        }
1940        while dumped_index < len {
1941            let tcmd = &mut assembly[dumped_index];
1942            tcmd.new_addr = new_image.pos as u32;
1943            // Copy original command
1944            new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
1945            dumped_index += 1;
1946        }
1947        for (s, mes) in messages {
1948            if !mes.is_empty() {
1949                return Err(anyhow::anyhow!(
1950                    "Not all messages were used for key '{}', {} remaining.",
1951                    s,
1952                    mes.len()
1953                ));
1954            }
1955        }
1956        let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
1957            assembly.iter().map(|c| (c.addr, c)).collect();
1958        Self::fix_image(&assembly, &mut disasm, &mut new_image, &commands)?;
1959        cloned.image = MemReader::new(new_image.into_inner());
1960        cloned.fix_references(&commands)?;
1961        cloned.save(file)?;
1962        Ok(())
1963    }
1964
1965    fn import_all<'a>(&self, messages: Vec<String>, file: Box<dyn WriteSeek + 'a>) -> Result<()> {
1966        let mut cloned = self.clone();
1967        let mut mess = messages.into_iter();
1968        let mut mes = mess.next();
1969        let mut disasm = ECSExecutionImageDisassembler::new(
1970            self.image.to_ref(),
1971            &self.section_function,
1972            &self.section_func_info,
1973            &self.section_import_native_func,
1974            &self.section_class_info,
1975            &self.section_const_string,
1976            None,
1977        );
1978        disasm.execute()?;
1979        let mut conststr_map: HashMap<String, u32> = cloned
1980            .section_const_string
1981            .strings
1982            .iter()
1983            .enumerate()
1984            .map(|(i, s)| (s.string.0.clone(), i as u32))
1985            .collect();
1986        let mut assembly = disasm.assembly.clone();
1987        let mut new_image = MemWriter::new();
1988        for cmd in assembly.iter_mut() {
1989            cmd.new_addr = new_image.pos as u32;
1990            if cmd.code == CsicLoad {
1991                disasm.stream.pos = cmd.addr as usize + 1;
1992                let csom = disasm.read_csom()?;
1993                let csvt = disasm.read_csvt()?;
1994                if csom == CsomImmediate && csvt == CsvtString {
1995                    let s = match mes {
1996                        Some(s) => s,
1997                        None => {
1998                            return Err(anyhow::anyhow!(
1999                                "Not enough messages for import_all at {:08X}",
2000                                cmd.addr,
2001                            ));
2002                        }
2003                    };
2004                    mes = mess.next();
2005                    let constr_idx = if let Some(&idx) = conststr_map.get(&s) {
2006                        idx
2007                    } else {
2008                        // Add new string to const string section
2009                        let idx = cloned.section_const_string.strings.len() as u32;
2010                        cloned.section_const_string.strings.push(ConstStringEntry {
2011                            string: WideString(s.clone()),
2012                            refs: DWordArray { data: Vec::new() },
2013                        });
2014                        conststr_map.insert(s.clone(), idx);
2015                        idx
2016                    };
2017                    new_image.write_u8(CsicLoad as u8)?;
2018                    new_image.write_u8(CsomImmediate as u8)?;
2019                    new_image.write_u8(CsvtString as u8)?;
2020                    new_image.write_u32(0x80000000)?;
2021                    new_image.write_u32(constr_idx)?;
2022                    continue;
2023                }
2024            }
2025            // Copy original command
2026            new_image.write_from(&mut disasm.stream, cmd.addr as u64, cmd.size as u64)?;
2027        }
2028        if mes.is_some() || mess.next().is_some() {
2029            return Err(anyhow::anyhow!("Too many messages for import_all"));
2030        }
2031        let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
2032            assembly.iter().map(|c| (c.addr, c)).collect();
2033        Self::fix_image(&assembly, &mut disasm, &mut new_image, &commands)?;
2034        cloned.image = MemReader::new(new_image.into_inner());
2035        cloned.fix_references(&commands)?;
2036        cloned.save(file)?;
2037        Ok(())
2038    }
2039}