Add import message support

This commit is contained in:
2026-01-19 15:54:55 +08:00
parent 4670f4e2e7
commit d780da9215
2 changed files with 393 additions and 0 deletions

View File

@@ -599,6 +599,388 @@ impl ECSExecutionImage {
Ok(messages)
}
pub fn import<'a>(
&self,
messages: Vec<Message>,
file: Box<dyn WriteSeek + 'a>,
replacement: Option<&'a ReplacementTable>,
) -> Result<()> {
let mut cloned = self.clone();
let mut disasm = ECSExecutionImageDisassembler::new(
self.image.to_ref(),
self.ext_const_str.as_ref(),
None,
);
disasm.execute()?;
let mut assembly = disasm.assembly.clone();
let mut index = 0;
let mut dumped_index = 0;
let mut new_image = MemWriter::new();
let mut pre_is_mess = false;
let mut first_mess_index = None;
let mut last_mess_index = None;
let mut message_iter = messages.into_iter();
let mut mess = message_iter.next();
while index < assembly.len() {
let cmd = assembly[index].clone();
if cmd.code == CsicCall {
disasm.stream.pos = cmd.addr as usize + 1;
let csom = disasm.read_csom()?;
let num_args = disasm.stream.read_i32()?;
let func_name = WideString::unpack(&mut disasm.stream, false, Encoding::Utf16LE)?.0;
let mut is_mess = false;
if csom == CsomAuto && num_args == 1 && func_name == "Mess" {
is_mess = true;
if first_mess_index.is_none() {
first_mess_index = Some(index);
}
last_mess_index = Some(index);
}
if pre_is_mess && !is_mess {
let first_index = first_mess_index
.ok_or(anyhow::anyhow!("Internal error: first_mess_index is None"))?;
let last_index = last_mess_index
.ok_or(anyhow::anyhow!("Internal error: last_mess_index is None"))?;
// Load string
let pre_index = first_index - 1;
while dumped_index < pre_index {
let tcmd = &mut assembly[dumped_index];
tcmd.new_addr = new_image.pos as u32;
// Copy original instruction
new_image.write_from(
&mut disasm.stream,
tcmd.addr as u64,
tcmd.size as u64,
)?;
dumped_index += 1;
}
// Free
let post_index = last_index + 1;
let mut message = mess
.take()
.ok_or_else(|| {
anyhow::anyhow!("Not enough messages to import for Mess command.")
})?
.message;
mess = message_iter.next();
if let Some(repl) = replacement {
for (k, v) in repl.map.iter() {
message = message.replace(k, v);
}
}
for i in (first_index..=last_index).step_by(3) {
let tcmd = assembly[i - 1].clone();
disasm.stream.pos = tcmd.addr as usize + 1;
let lcsom = disasm.read_csom()?;
let lcsvt = disasm.read_csvt()?;
if lcsom != CsomImmediate || lcsvt != CsvtString {
return Err(anyhow::anyhow!(
"Invalid load command before Mess at {:08X}.",
tcmd.addr
));
}
}
let mes_list: Vec<_> = message
.replace("\n", &self.lf)
.split(&self.lf)
.map(|s| s.to_string())
.collect();
let mut new_assembly = Vec::new();
let mes_count = mes_list.len();
let mut tmp_index = pre_index;
for i in 0..mes_count {
let mut mes = mes_list[i].clone();
if i < mes_count - 1 {
mes.push_str(&self.lf);
}
let mut tcmd = if tmp_index <= post_index {
let data = assembly[tmp_index].clone();
tmp_index += 1;
if data.code != CsicLoad {
return Err(anyhow::anyhow!(
"Internal error: expected Load command at {:08X}.",
data.addr
));
}
data
} else {
ECSExecutionImageCommandRecord {
code: CsicLoad,
addr: u32::MAX,
size: 0,
new_addr: 0,
}
};
tcmd.new_addr = new_image.pos as u32;
new_image.write_u8(CsicLoad.into())?;
new_image.write_u8(CsomImmediate.into())?;
new_image.write_u8(CsvtString.into())?;
WideString(mes).pack(&mut new_image, false, Encoding::Utf8)?;
new_assembly.push(tcmd);
let mut tcmd = if tmp_index <= post_index {
let data = assembly[tmp_index].clone();
tmp_index += 1;
if data.code != CsicCall {
return Err(anyhow::anyhow!(
"Expected Call command at {:08X}.",
data.addr
));
}
data
} else {
ECSExecutionImageCommandRecord {
code: CsicCall,
addr: u32::MAX,
size: 0,
new_addr: 0,
}
};
tcmd.new_addr = new_image.pos as u32;
new_image.write_u8(CsicCall.into())?;
new_image.write_u8(CsomAuto.into())?;
new_image.write_i32(1)?; // num_args
WideString("Mess".to_string()).pack(
&mut new_image,
false,
Encoding::Utf16LE,
)?;
new_assembly.push(tcmd);
let mut tcmd = if tmp_index <= post_index {
let data = assembly[tmp_index].clone();
tmp_index += 1;
if data.code != CsicFree {
return Err(anyhow::anyhow!(
"Expected Free command at {:08X}.",
data.addr
));
}
data
} else {
ECSExecutionImageCommandRecord {
code: CsicFree,
addr: u32::MAX,
size: 0,
new_addr: 0,
}
};
tcmd.new_addr = new_image.pos as u32;
new_image.write_u8(CsicFree.into())?;
new_assembly.push(tcmd);
}
let ori_count = post_index - pre_index + 1;
let new_count = new_assembly.len();
dumped_index += new_count;
index = (index as isize + (new_count as isize - ori_count as isize)) as usize;
last_mess_index = None;
first_mess_index = None;
assembly.splice(pre_index..post_index + 1, new_assembly);
}
if csom == CsomAuto && num_args == 2 && func_name == "Talk" {
if index < 2 {
return Err(anyhow::anyhow!(
"No enough load command at {:08x}.",
cmd.addr
));
}
let pre_index = index - 2;
while dumped_index < pre_index {
let tcmd = &mut assembly[dumped_index];
tcmd.new_addr = new_image.pos as u32;
// Copy original instruction
new_image.write_from(
&mut disasm.stream,
tcmd.addr as u64,
tcmd.size as u64,
)?;
dumped_index += 1;
}
let tcmd = &mut assembly[pre_index];
tcmd.new_addr = new_image.pos as u32;
disasm.stream.pos = tcmd.addr as usize + 1;
let lcsom = disasm.read_csom()?;
let lcsvt = disasm.read_csvt()?;
if lcsom != CsomImmediate || lcsvt != CsvtString {
return Err(anyhow::anyhow!(
"Invalid load command before Talk at {:08X}.",
tcmd.addr
));
}
let original_name = disasm.get_string_literal()?;
let name = if original_name == "心の声" {
original_name
} else {
let mut name = mess.as_mut().map(|s| s.name.take()).flatten().ok_or(
anyhow::anyhow!("No available name for Talk at {:08X}.", cmd.addr),
)?;
if let Some(repl) = replacement {
for (k, v) in repl.map.iter() {
name = name.replace(k, v);
}
}
name
};
new_image.write_u8(CsicLoad.into())?;
new_image.write_u8(lcsom.into())?;
new_image.write_u8(lcsvt.into())?;
WideString(name).pack(&mut new_image, false, Encoding::Utf8)?;
dumped_index += 1;
while dumped_index <= index {
let tcmd = &mut assembly[dumped_index];
tcmd.new_addr = new_image.pos as u32;
// Copy original instruction
new_image.write_from(
&mut disasm.stream,
tcmd.addr as u64,
tcmd.size as u64,
)?;
dumped_index += 1;
}
} else if csom == CsomAuto && num_args == 2 && func_name == "AddSelect" {
if index < 2 {
return Err(anyhow::anyhow!(
"No enough load command at {:08x}.",
cmd.addr
));
}
let pre_index = index - 2;
while dumped_index < pre_index {
let tcmd = &mut assembly[dumped_index];
tcmd.new_addr = new_image.pos as u32;
// Copy original instruction
new_image.write_from(
&mut disasm.stream,
tcmd.addr as u64,
tcmd.size as u64,
)?;
dumped_index += 1;
}
let tcmd = &mut assembly[pre_index];
tcmd.new_addr = new_image.pos as u32;
disasm.stream.pos = tcmd.addr as usize + 1;
let lcsom = disasm.read_csom()?;
let lcsvt = disasm.read_csvt()?;
if lcsom != CsomImmediate || lcsvt != CsvtString {
return Err(anyhow::anyhow!(
"Invalid load command before AddSelect at {:08X}.",
tcmd.addr
));
}
let mut message = mess
.take()
.ok_or_else(|| {
anyhow::anyhow!(
"No available message for AddSelect at {:08X}.",
cmd.addr
)
})?
.message;
mess = message_iter.next();
if let Some(repl) = replacement {
for (k, v) in repl.map.iter() {
message = message.replace(k, v);
}
}
new_image.write_u8(CsicLoad.into())?;
new_image.write_u8(lcsom.into())?;
new_image.write_u8(lcsvt.into())?;
WideString(message).pack(&mut new_image, false, Encoding::Utf8)?;
dumped_index += 1;
while dumped_index <= index {
let tcmd = &mut assembly[dumped_index];
tcmd.new_addr = new_image.pos as u32;
// Copy original instruction
new_image.write_from(
&mut disasm.stream,
tcmd.addr as u64,
tcmd.size as u64,
)?;
dumped_index += 1;
}
} else if csom == CsomAuto && num_args == 1 && func_name == "SceneTitle" {
if index < 1 {
return Err(anyhow::anyhow!(
"No enough load command at {:08x}.",
cmd.addr
));
}
let pre_index = index - 1;
while dumped_index < pre_index {
let tcmd = &mut assembly[dumped_index];
tcmd.new_addr = new_image.pos as u32;
// Copy original instruction
new_image.write_from(
&mut disasm.stream,
tcmd.addr as u64,
tcmd.size as u64,
)?;
dumped_index += 1;
}
let tcmd = &mut assembly[pre_index];
tcmd.new_addr = new_image.pos as u32;
disasm.stream.pos = tcmd.addr as usize + 1;
let lcsom = disasm.read_csom()?;
let lcsvt = disasm.read_csvt()?;
if lcsom != CsomImmediate || lcsvt != CsvtString {
return Err(anyhow::anyhow!(
"Invalid load command before SceneTitle at {:08X}.",
tcmd.addr
));
}
let mut message = mess
.take()
.ok_or_else(|| {
anyhow::anyhow!(
"No available message for SceneTitle at {:08X}.",
cmd.addr
)
})?
.message;
mess = message_iter.next();
if let Some(repl) = replacement {
for (k, v) in repl.map.iter() {
message = message.replace(k, v);
}
}
new_image.write_u8(CsicLoad.into())?;
new_image.write_u8(lcsom.into())?;
new_image.write_u8(lcsvt.into())?;
WideString(message).pack(&mut new_image, false, Encoding::Utf8)?;
dumped_index += 1;
while dumped_index <= index {
let tcmd = &mut assembly[dumped_index];
tcmd.new_addr = new_image.pos as u32;
// Copy original instruction
new_image.write_from(
&mut disasm.stream,
tcmd.addr as u64,
tcmd.size as u64,
)?;
dumped_index += 1;
}
}
pre_is_mess = is_mess;
}
index += 1;
}
while dumped_index < assembly.len() {
let tcmd = &mut assembly[dumped_index];
tcmd.new_addr = new_image.pos as u32;
// Copy original instruction
new_image.write_from(&mut disasm.stream, tcmd.addr as u64, tcmd.size as u64)?;
dumped_index += 1;
}
if mess.is_some() || message_iter.next().is_some() {
return Err(anyhow::anyhow!("Too many messages to import."));
}
let commands: HashMap<u32, &ECSExecutionImageCommandRecord> =
assembly.iter().map(|c| (c.addr, c)).collect();
Self::fix_image(&assembly, disasm.stream.clone(), &mut new_image, &commands)?;
cloned.image = MemReader::new(new_image.into_inner());
cloned.fix_references(&commands)?;
cloned.save(file)?;
Ok(())
}
pub fn import_multi<'a>(
&self,
mut messages: HashMap<String, Vec<Message>>,

View File

@@ -90,6 +90,17 @@ impl Script for CSXScript {
self.img.export()
}
fn import_messages<'a>(
&'a self,
messages: Vec<Message>,
file: Box<dyn WriteSeek + 'a>,
_filename: &str,
_encoding: Encoding,
replacement: Option<&'a ReplacementTable>,
) -> Result<()> {
self.img.import(messages, file, replacement)
}
fn multiple_message_files(&self) -> bool {
true
}