From aa73e04ad34db998ebe96bbd5cded523ea3f0bbe Mon Sep 17 00:00:00 2001 From: Kota Date: Thu, 22 Jan 2026 19:46:53 +0300 Subject: [PATCH] add names to favorite scrips --- src/scripts/favorite/disasm.rs | 125 +++++++++++++++++++++++++++++++++ src/scripts/favorite/hcb.rs | 19 ++--- 2 files changed, 131 insertions(+), 13 deletions(-) diff --git a/src/scripts/favorite/disasm.rs b/src/scripts/favorite/disasm.rs index 33051f3..7b1dbf8 100644 --- a/src/scripts/favorite/disasm.rs +++ b/src/scripts/favorite/disasm.rs @@ -3,6 +3,7 @@ use crate::types::*; use crate::utils::encoding::*; use anyhow::Result; use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; use std::io::{Read, Seek, SeekFrom}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -111,6 +112,12 @@ pub struct Data { pub functions: Vec, pub main_script: Vec, pub extra_data: Vec, + #[serde(skip)] + speak_func_indices: HashSet, + #[serde(skip)] + func_pos_map: HashMap, + #[serde(skip)] + speaker_names: HashMap>, } impl Data { @@ -119,6 +126,9 @@ impl Data { functions: Vec::new(), main_script: Vec::new(), extra_data: Vec::new(), + speak_func_indices: HashSet::new(), + func_pos_map: HashMap::new(), + speaker_names: HashMap::new(), }; let script_len = reader.read_u32()? as u64; let main_script_data = reader.peek_u32_at(script_len)? as u64; @@ -135,9 +145,124 @@ impl Data { } reader.seek(SeekFrom::Start(script_len + 4))?; reader.read_to_end(&mut data.extra_data)?; + + data.index_functions(); + data.find_speak_functions(); + data.collect_speaker_names(); + Ok(data) } + fn index_functions(&mut self) { + for (idx, func) in self.functions.iter().enumerate() { + if func.opcode == 0x01 { + self.func_pos_map.insert(func.pos, idx); + } + } + } + + fn find_speak_functions(&mut self) { + for (idx, func) in self.functions.iter().enumerate() { + if func.opcode == 0x01 { + // SPEAK functions have initstack with (3, 0) or (5, 0) parameters + if let (Some(Operand::B(arg_count)), Some(Operand::B(0))) = + (func.operands.first(), func.operands.get(1)) + { + if *arg_count == 3 || *arg_count == 5 { + self.speak_func_indices.insert(idx as u32); + } + } + } + } + } + + fn collect_speaker_names(&mut self) { + let func_starts: Vec = self + .functions + .iter() + .enumerate() + .filter(|(_, f)| f.opcode == 0x01) + .map(|(i, _)| i) + .collect(); + + for &speak_idx in &self.speak_func_indices { + let speak_idx = speak_idx as usize; + + let start_pos = func_starts.iter().position(|&s| s == speak_idx); + if let Some(pos) = start_pos { + let end = func_starts.get(pos + 1).copied().unwrap_or(self.functions.len()); + let names: Vec = (speak_idx..end) + .filter(|&i| self.functions[i].opcode == 0x0e) + .filter_map(|i| match self.functions[i].operands.first() { + Some(Operand::S(s)) if !s.trim().is_empty() => Some(s.clone()), + _ => None, + }) + .collect(); + + if !names.is_empty() { + self.speaker_names.insert(speak_idx, names); + } + } + } + } + + fn get_speaker(&self, func_idx: usize) -> Option { + let names = self.speaker_names.get(&func_idx)?; + + // Prefer names without '?' prefix, take the last one (usually the "known" name) + if let Some(name) = names.iter().filter(|n| !n.contains('?')).last() { + return Some(name.trim().to_string()); + } + + // If all names have '?', strip it from the last one + names.last().and_then(|name| { + let cleaned = name.trim().trim_start_matches('?').trim(); + if !cleaned.is_empty() { + Some(cleaned.to_string()) + } else { + None + } + }) + } + + pub fn extract_messages(&self, filter_ascii: bool) -> Vec<(Option, String)> { + let mut messages = Vec::new(); + + // Extract strings from functions section (no speakers) + for func in &self.functions { + if func.opcode == 0x0e { + if let Some(Operand::S(s)) = func.operands.first() { + if !(filter_ascii && s.chars().all(|c| c.is_ascii())) { + messages.push((None, s.clone())); + } + } + } + } + + // Process main_script, track SPEAK calls for speaker names + let mut current_speaker: Option = None; + + for func in &self.main_script { + if func.opcode == 0x02 { + if let Some(Operand::D(call_target)) = func.operands.first() { + if let Some(&func_idx) = self.func_pos_map.get(&(*call_target as u64)) { + if self.speak_func_indices.contains(&(func_idx as u32)) { + current_speaker = self.get_speaker(func_idx); + } + } + } + } else if func.opcode == 0x0e { + if let Some(Operand::S(s)) = func.operands.first() { + if !(filter_ascii && s.chars().all(|c| c.is_ascii())) { + messages.push((current_speaker.clone(), s.clone())); + } + } + } + } + + messages + } + fn read_func(reader: &mut R, encoding: Encoding) -> Result { let pos = reader.stream_position()?; let opcode = reader.read_u8()?; diff --git a/src/scripts/favorite/hcb.rs b/src/scripts/favorite/hcb.rs index 4e3789e..d904fc7 100644 --- a/src/scripts/favorite/hcb.rs +++ b/src/scripts/favorite/hcb.rs @@ -84,19 +84,12 @@ impl Script for HcbScript { } fn extract_messages(&self) -> Result> { - let mut messages = Vec::new(); - for funcs in [&self.data.functions, &self.data.main_script] { - for func in funcs { - for operand in &func.operands { - if let Operand::S(s) = operand { - if self.filter_ascii && s.chars().all(|c| c.is_ascii()) { - continue; - } - messages.push(Message::new(s.clone(), None)); - } - } - } - } + let messages = self + .data + .extract_messages(self.filter_ascii) + .into_iter() + .map(|(speaker, message)| Message::new(message, speaker)) + .collect(); Ok(messages) }