From af76b42f41a8306824e4f5e11fbc882d74719229 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 23 May 2026 09:47:02 +0800 Subject: [PATCH] Handle more choices in totsulover Allow to trace choice syscall in user function --- src/scripts/softpal/scr/disasm.rs | 57 +++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/src/scripts/softpal/scr/disasm.rs b/src/scripts/softpal/scr/disasm.rs index ed2aa86..4f3bca0 100644 --- a/src/scripts/softpal/scr/disasm.rs +++ b/src/scripts/softpal/scr/disasm.rs @@ -241,7 +241,7 @@ const TEXT_CAT: u16 = 0x0099; pub const CODE_OFFSET: u32 = 0xC; const BIN_XOR: u16 = 0x0008; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] struct Operand { offset: u32, raw_value: u32, @@ -309,10 +309,16 @@ struct UserMessageFunction { message_arg_index: u32, } +struct UserChoiceFunction { + num_args: u32, + choice_arg_indexs: Vec, +} + pub struct Disasm<'a> { reader: MemReaderRef<'a>, label_offsets: Vec, user_message_functions: HashMap, + user_choice_functions: HashMap, variables: HashMap, stack: Vec, strs: Vec, @@ -359,6 +365,7 @@ impl<'a> Disasm<'a> { reader, label_offsets: label_offsets.to_vec(), user_message_functions: HashMap::new(), + user_choice_functions: HashMap::new(), variables: HashMap::new(), stack: Vec::new(), strs: Vec::new(), @@ -373,7 +380,7 @@ impl<'a> Disasm<'a> { mut self, mut writer: Option<&mut W>, ) -> Result> { - self.find_user_message_functions()?; + self.find_user_functions()?; self.reader.pos = CODE_OFFSET as usize; let len = self.reader.data.len(); while self.reader.pos < len { @@ -513,9 +520,10 @@ impl<'a> Disasm<'a> { Ok(()) } - fn find_user_message_functions(&mut self) -> Result<()> { + fn find_user_functions(&mut self) -> Result<()> { let mut current_func_args = None; self.reader.pos = CODE_OFFSET as usize; + let mut choice_indexes = Vec::new(); let len = self.reader.data.len(); while self.reader.pos < len { let instr = self.read_instruction()?; @@ -543,12 +551,34 @@ impl<'a> Disasm<'a> { self.stack.clear(); // self.variables.clear(); continue; + } else if instr.opcode == SYSCALL && instr.operands[0].raw_value == 0x60002 { + let choice = self + .stack + .pop() + .ok_or_else(|| anyhow::anyhow!("No choice for choice sys call"))?; + if choice.typ() == OperandType::Argument { + choice_indexes.push(choice.value() - 1); + } + self.stack.clear(); + if !choice_indexes.is_empty() { + if let Some((func_offset, func_num_args)) = current_func_args { + self.user_choice_functions.insert( + func_offset, + UserChoiceFunction { + num_args: func_num_args, + choice_arg_indexs: choice_indexes.clone(), + }, + ); + } + } + continue; } match instr.opcode { ENTER => { current_func_args = Some((instr.offset, instr.operands[0].value())); self.stack.clear(); self.variables.clear(); // Safe to clear here, entering a new function frame + choice_indexes.clear(); } MOV => { self.handle_mov_instruction(instr)?; @@ -560,6 +590,7 @@ impl<'a> Disasm<'a> { current_func_args = None; self.stack.clear(); self.variables.clear(); // Safe to clear here, leaving function frame + choice_indexes.clear(); } _ => { self.stack.clear(); @@ -635,6 +666,26 @@ impl<'a> Disasm<'a> { return Ok(()); } let target_offset = self.label_offsets[instr.operands[0].value() as usize - 1]; + if let Some(func) = self.user_choice_functions.get(&target_offset) { + if self.stack.len() < func.num_args as usize { + return Ok(()); + } + let mut args = Vec::new(); + for _ in 0..func.num_args { + args.push(self.stack.pop().unwrap()); + } + args.reverse(); + for ind in &func.choice_arg_indexs { + let arg = &args[*ind as usize]; + if arg.typ() == OperandType::Literal { + self.strs.push(PalString { + offset: arg.offset, + typ: StringType::Message, + }); + } + } + return Ok(()); + } let message_func = match self.user_message_functions.get(&target_offset) { Some(func) => func, None => return Ok(()),