Compare commits

..

2 commits

Author SHA1 Message Date
f1c23bd3f4 Improved script parsing again 2024-09-18 13:18:11 -05:00
9b3fcb2480 Fixed minor issues with script parsing 2024-09-18 10:28:57 -05:00

View file

@ -1,10 +1,12 @@
use std::{fs::File, io::{Read, Seek}, process::exit}; use std::{fs::File, io::{self, BufRead, BufReader, Read, Seek}};
use byteorder::{ReadBytesExt, LE}; use byteorder::{ReadBytesExt, LE};
use encoding_rs::SHIFT_JIS;
use num_derive::{FromPrimitive, ToPrimitive}; use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive};
use safe_transmute::{transmute_many, SingleManyGuard};
fn main() { 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() { while let Ok(byte) = script.read_u8() {
if let Some(opcode) = Opcode::from_u8(byte) { if let Some(opcode) = Opcode::from_u8(byte) {
@ -14,43 +16,90 @@ fn main() {
opcode.to_u8().unwrap(), opcode.to_u8().unwrap(),
opcode opcode
); );
if opcode == Opcode::TASK {
break; parse_opcode(opcode, &mut script).unwrap();
} } else {
match opcode { println!(
Opcode::MESSAGE => { "{:X?}: {:#04X?} (¡{:?}!)",
let variables = script.read_u8().unwrap(); script.stream_position().unwrap() - 1,
match variables { byte,
1 => continue, Opcode::UNKNOWN,
4 => { );
dbg!(script.read_u32::<LE>().unwrap());
continue;
}
_ => (),
}
let message = Message {
variables,
unknown1: Some(script.read_u16::<LE>().unwrap()),
unknown2: Some(script.read_u16::<LE>().unwrap()),
index: Some(script.read_u16::<LE>().unwrap()),
messages: Some((0..3).map(|_| ScriptString::read(&mut script)).collect()),
};
message.messages.unwrap().iter().for_each(|m| println!("{}", m.to_string()));
println!("-----");
},
Opcode::JUMP => {
dbg!(script.read_u32::<LE>().unwrap());
println!("------");
}
_ => (),
}
} }
} }
} }
fn parse_opcode<R: Read + BufRead>(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::<LE>().unwrap(),
unknown2: input.read_u16::<LE>().unwrap(),
index: input.read_u16::<LE>().unwrap(),
strings: (0..3).map(|_| ScriptString::read(&mut input)).collect::<Result<Vec<_>, _>>()?,
};
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::<LE>().unwrap());
} else {
println!("Unknown: {}", input.read_u16::<LE>().unwrap());
println!("Unknown: {}", input.read_u16::<LE>().unwrap());
}
let image_id = input.read_u16::<LE>().unwrap();
println!("Image ID: {image_id}");
println!("-----");
},
/*
Opcode::SELECT => {
let var_id = script.read_u16::<LE>().unwrap();
script.read_u16::<LE>().unwrap();
script.read_u16::<LE>().unwrap();
script.read_u16::<LE>().unwrap();
let msg_str = script.read_u16::<LE>().unwrap();
script.read_u16::<LE>().unwrap();
script.read_u16::<LE>().unwrap();
script.read_u16::<LE>().unwrap();
println!("{var_id} & {msg_str}\n-----");
},
*/
Opcode::JUMP => {
input.read_u16::<LE>().unwrap();
}
Opcode::VARSTR => {
println!("Unknown: {}", input.read_u32::<LE>()?);
println!("-----");
}
_ => (),
}
Ok(())
}
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
#[repr(u8)] #[repr(u8)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[derive(FromPrimitive, ToPrimitive)] #[derive(FromPrimitive, ToPrimitive)]
enum Opcode { enum Opcode {
EQU, EQU,
@ -195,23 +244,11 @@ enum Opcode {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
struct Message { struct Message {
variables: u8, variables: u8,
unknown1: Option<u16>, unknown1: u16,
unknown2: Option<u16>, unknown2: u16,
index: Option<u16>, index: u16,
messages: Option<Vec<ScriptString>>, strings: Vec<ScriptString>,
}
impl Default for Message {
fn default() -> Self {
Self {
variables: 1,
unknown1: None,
unknown2: None,
index: None,
messages: None
}
}
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@ -230,38 +267,46 @@ enum StringFormat {
} }
impl ScriptString { impl ScriptString {
fn read<R: Read + ReadBytesExt>(input: &mut R) -> Self { fn read<R: Read + BufRead>(input: &mut R) -> Result<Self, io::Error> {
let length = input.read_i16::<LE>().unwrap(); let length = input.read_i16::<LE>()?;
// Catch very long strings, these should be investigated (they're probably broken)
assert!(length < 2_000);
let (mut buffer, format) = if length < 0 { let (mut buffer, format) = if length < 0 {
// If the length is negative, then the length is the exact length in // If the length is negative, then the length is the exact length in
// bytes?? // bytes of the absolute value?!?
(vec![0u8; length.abs() as usize + 1], StringFormat::UTF8) (vec![0u8; length.unsigned_abs() as usize], StringFormat::UTF8)
} else { } else {
// Otherwise double the length // 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::<LE>()?,
_ => input.read_u8()? as u16,
};
assert!(!(string_end != 0), "String not null terminated!");
Ok(Self {
length, length,
format, format,
buffer, buffer,
} })
} }
} }
impl ToString for ScriptString { impl ToString for ScriptString {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self.format { match self.format {
StringFormat::UTF8 => String::from_utf8_lossy(&self.buffer).to_string(), StringFormat::UTF8 | StringFormat::ASCII => String::from_utf8_lossy(&self.buffer).to_string(),
StringFormat::UTF16 => { StringFormat::UTF16 => String::from_utf16_lossy(transmute_many::<u16, SingleManyGuard>(&self.buffer).unwrap()),
String::from_utf16_lossy( StringFormat::ShiftJIS => SHIFT_JIS.decode(&self.buffer).0.to_string(),
&self.buffer
.chunks(2)
.map(|c| u16::from_le_bytes(c.try_into().unwrap())).collect::<Vec<u16>>()).to_owned()
},
StringFormat::ASCII => String::from_utf8_lossy(&self.buffer).to_string(),
_ => unimplemented!(), _ => unimplemented!(),
} }
} }