diff --git a/luca_script/src/main.rs b/luca_script/src/main.rs index 666e270..66790be 100644 --- a/luca_script/src/main.rs +++ b/luca_script/src/main.rs @@ -1,10 +1,12 @@ -use std::{fs::File, io::{Read, Seek}}; +use std::{fs::File, io::{self, BufRead, BufReader, Read, Seek}}; use byteorder::{ReadBytesExt, LE}; +use encoding_rs::SHIFT_JIS; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; +use safe_transmute::{transmute_many, SingleManyGuard}; fn main() { - let mut script = File::open("LOOPERS_scenario_01").unwrap(); + let mut script = BufReader::new(File::open("LOOPERS_scenario_01").unwrap()); while let Ok(byte) = script.read_u8() { if let Some(opcode) = Opcode::from_u8(byte) { @@ -15,65 +17,89 @@ fn main() { opcode ); - match opcode { - Opcode::MESSAGE => { - let variables = script.read_u8().unwrap(); - match variables { - 1 => continue, - 4 => { - script.read_u32::().unwrap(); - continue; - } - 3 => (), - _ => unimplemented!(), - } - let message = Message { - variables, - unknown1: Some(script.read_u16::().unwrap()), - unknown2: Some(script.read_u16::().unwrap()), - index: Some(script.read_u16::().unwrap()), - messages: Some((0..3).map(|_| ScriptString::read(&mut script)).collect()), - }; - message.messages.unwrap().iter().for_each(|m| println!("{}", m.to_string())); - println!("-----"); - }, - Opcode::IMAGELOAD => { - let mode = script.read_u8().unwrap(); - match mode { - 0 => script.read_u16::().unwrap(), - _ => { - script.read_u16::().unwrap(); - script.read_u16::().unwrap() - }, - }; - let image_id = script.read_u16::().unwrap(); - println!("Image ID: {image_id}\n-----"); - }, - /* - Opcode::SELECT => { - let var_id = script.read_u16::().unwrap(); - script.read_u16::().unwrap(); - script.read_u16::().unwrap(); - script.read_u16::().unwrap(); - let msg_str = script.read_u16::().unwrap(); - script.read_u16::().unwrap(); - script.read_u16::().unwrap(); - script.read_u16::().unwrap(); - println!("{var_id} & {msg_str}\n-----"); - }, - */ - Opcode::JUMP => { - script.read_u16::().unwrap(); - } - _ => (), - } + parse_opcode(opcode, &mut script).unwrap(); + } else { + println!( + "{:X?}: {:#04X?} (ยก{:?}!)", + script.stream_position().unwrap() - 1, + byte, + Opcode::UNKNOWN, + ); } } } +fn parse_opcode(opcode: Opcode, mut input: R) -> Result<(), io::Error> { + match opcode { + Opcode::MESSAGE => { + let variables = input.read_u8().unwrap(); + match variables { + // Empty message!? + 1 => return Ok(()), + 3 => (), + // Unknown data + 4 => { + let mut buf = vec![0; 4]; + input.read_exact(&mut buf).unwrap(); + //println!("{:0X?}", buf); + return Ok(()); + } + n => unimplemented!("{n}"), + } + let message = Message { + variables, + unknown1: input.read_u16::().unwrap(), + unknown2: input.read_u16::().unwrap(), + index: input.read_u16::().unwrap(), + strings: (0..3).map(|_| ScriptString::read(&mut input)).collect::, _>>()?, + }; + message.strings.iter().enumerate().for_each(|m| println!("{}: {}, {}", m.0, m.1.length, m.1.to_string())); + println!("-----"); + }, + Opcode::IMAGELOAD => { + let mode = input.read_u8().unwrap(); + println!("Mode: {mode}"); + if mode == 0 { + println!("Unknown: {}", input.read_u16::().unwrap()); + } else { + println!("Unknown: {}", input.read_u16::().unwrap()); + println!("Unknown: {}", input.read_u16::().unwrap()); + } + + let image_id = input.read_u16::().unwrap(); + println!("Image ID: {image_id}"); + println!("-----"); + }, + /* + Opcode::SELECT => { + let var_id = script.read_u16::().unwrap(); + script.read_u16::().unwrap(); + script.read_u16::().unwrap(); + script.read_u16::().unwrap(); + let msg_str = script.read_u16::().unwrap(); + script.read_u16::().unwrap(); + script.read_u16::().unwrap(); + script.read_u16::().unwrap(); + println!("{var_id} & {msg_str}\n-----"); + }, + */ + Opcode::JUMP => { + input.read_u16::().unwrap(); + } + Opcode::VARSTR => { + println!("Unknown: {}", input.read_u32::()?); + println!("-----"); + } + _ => (), + } + + Ok(()) +} + #[allow(non_camel_case_types)] +#[allow(clippy::upper_case_acronyms)] #[repr(u8)] -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] #[derive(FromPrimitive, ToPrimitive)] enum Opcode { EQU, @@ -218,23 +244,11 @@ enum Opcode { #[derive(Debug, PartialEq, Eq)] struct Message { variables: u8, - unknown1: Option, - unknown2: Option, - index: Option, + unknown1: u16, + unknown2: u16, + index: u16, - messages: Option>, -} - -impl Default for Message { - fn default() -> Self { - Self { - variables: 1, - unknown1: None, - unknown2: None, - index: None, - messages: None - } - } + strings: Vec, } #[derive(Debug, PartialEq, Eq)] @@ -253,38 +267,46 @@ enum StringFormat { } impl ScriptString { - fn read(input: &mut R) -> Self { - let length = input.read_i16::().unwrap(); + fn read(input: &mut R) -> Result { + let length = input.read_i16::()?; + + // Catch very long strings, these should be investigated (they're probably broken) + assert!(length < 2_000); + let (mut buffer, format) = if length < 0 { // If the length is negative, then the length is the exact length in - // bytes?? - (vec![0u8; length.abs() as usize + 1], StringFormat::UTF8) + // bytes of the absolute value?!? + (vec![0u8; length.unsigned_abs() as usize], StringFormat::UTF8) } else { // Otherwise double the length - (vec![0u8; (length as usize + 1) * 2], StringFormat::UTF16) + (vec![0u8; (length as usize) * 2], StringFormat::UTF16) }; - input.read_exact(&mut buffer).unwrap(); + // Read the string into the buffer + input.read_exact(&mut buffer)?; - Self { + // Ensure the string is null terminated + let string_end = match format { + StringFormat::UTF16 => input.read_u16::()?, + _ => input.read_u8()? as u16, + }; + + assert!(!(string_end != 0), "String not null terminated!"); + + Ok(Self { length, format, buffer, - } + }) } } impl ToString for ScriptString { fn to_string(&self) -> String { match self.format { - StringFormat::UTF8 => String::from_utf8_lossy(&self.buffer).to_string(), - StringFormat::UTF16 => { - String::from_utf16_lossy( - &self.buffer - .chunks(2) - .map(|c| u16::from_le_bytes(c.try_into().unwrap())).collect::>()).to_owned() - }, - StringFormat::ASCII => String::from_utf8_lossy(&self.buffer).to_string(), + StringFormat::UTF8 | StringFormat::ASCII => String::from_utf8_lossy(&self.buffer).to_string(), + StringFormat::UTF16 => String::from_utf16_lossy(transmute_many::(&self.buffer).unwrap()), + StringFormat::ShiftJIS => SHIFT_JIS.decode(&self.buffer).0.to_string(), _ => unimplemented!(), } }