Added preliminary script parsing

This commit is contained in:
G2-Games 2024-11-16 04:29:43 -06:00
parent 3c4d7a89ec
commit 3437b6c7a9
5 changed files with 354 additions and 1 deletions

View file

@ -3,7 +3,7 @@ resolver = "2"
members = [ members = [
"cz", "cz",
"pak_explorer", "pak_explorer",
"luca_pak", "utils", "luca_pak", "utils", "luca_script",
] ]
[workspace.package] [workspace.package]

12
luca_script/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "luca_script"
version = "0.1.0"
edition = "2021"
authors.workspace = true
[dependencies]
byteorder-lite = "0.1.0"
encoding_rs = "0.8.35"
[lints]
workspace = true

View file

@ -0,0 +1,107 @@
EQU
EQUN
EQUV
ADD
SUB
MUL
DIV
MOD
AND
OR
RANDOM
VARSTR
SET
FLAGCLR
GOTO
ONGOTO
GOSUB
IFY
IFN
RETURN
JUMP
FARCALL
FARRETURN
JUMPPOINT
END
VARSTR_SET
TALKNAME_SET
ARFLAGSET
COLORBG_SET
SPLINE_SET
SHAKELIST_SET
MESSAGE
MESSAGE_CLEAR
SELECT
CLOSE_WINDOW
LOG
LOG_PAUSE
LOG_END
VOICE
WAIT_COUNT
WAIT_TIME
FFSTOP
INIT
STOP
IMAGELOAD
IMAGEUPADTE
ARC
MOVE
MOVE2
ROT
PEND
FADE
SCALE
SHAKE
SHAKELIST
BASE
MCMOVE
MCARC
MCROT
MCSHAKE
MCFADE
WAIT
DRAW
WIPE
FRAMEON
FRAMEOFF
FW
SCISSOR
DELAY
RASTER
TONE
SCALECOSSIN
BMODE
SIZE
SPLINE
DISP
MASK
SG_QUAKE
BGM
BGM_WAITSTART
BGM_WAITFADE
SE
SE_STOP
SE_WAIT
VOLUME
MOVIE
SETCGFLAG
EX
TROPHY
SETBGMFLAG
TASK
BTFUNC
BATTLE
KOEP
BT_ACCESSORY_SELECT
UNDO_CLEAR
PTFUNC
PT
GMFUNC
GM
DEL_CALLSTACK
FULLQUAKE_ZOOM
LBFUNC
LBBG
HAIKEI_SET
SAYAVOICETEXT
UNKNOWN

181
luca_script/src/main.rs Normal file
View file

@ -0,0 +1,181 @@
mod utils;
use std::{cell::LazyCell, fs::{self, File}, io::Read, path::PathBuf};
use byteorder_lite::{ReadBytesExt, LE};
use utils::Encoding;
const OPCODES: LazyCell<Vec<String>> = LazyCell::new(|| fs::read_to_string("LBEE_opcodes")
.unwrap()
.split("\n")
.map(|s| s.to_owned())
.collect()
);
fn main() {
let file_path = PathBuf::from("SEEN0513");
let mut script = File::open(&file_path).unwrap();
println!("Start parsing script");
let script = parse_script(
&mut script,
file_path.file_name().unwrap().to_str().unwrap()
);
println!("Parsing finished");
for c in script.opcodes {
let ascii_string = String::from_utf8_lossy(&c.param_bytes);
//println!("{:>4}: '{:>11}' — {}", c.index, c.string, ascii_string);
SpecificOpcode::decode(&c.string, c.param_bytes);
}
}
fn parse_script<S: Read>(script_stream: &mut S, name: &str) -> Script {
let mut opcodes = Vec::new();
let mut _offset = 0;
let mut i = 0;
let mut pos = 0;
loop {
// Read all base info
let (length, number, flag) = (
script_stream.read_u16::<LE>().unwrap() as usize,
script_stream.read_u8().unwrap(),
script_stream.read_u8().unwrap()
);
let string = OPCODES[number as usize].clone();
_offset += 4;
let raw_len = length - 4;
let mut raw_bytes = vec![0u8; raw_len];
script_stream.read_exact(&mut raw_bytes).unwrap();
_offset += raw_len;
// Read extra align byte if alignment needed
let align = if length % 2 != 0 {
_offset += 1;
Some(script_stream.read_u8().unwrap())
} else {
None
};
let mut fixed_param = None;
let param_bytes = match flag {
0 => raw_bytes.clone(),
f if f < 2 => {
fixed_param = Some(vec![
u16::from_le_bytes(raw_bytes[..2].try_into().unwrap()),
]);
raw_bytes[2..].to_vec()
}
_ => {
fixed_param = Some(vec![
u16::from_le_bytes(raw_bytes[..2].try_into().unwrap()),
u16::from_le_bytes(raw_bytes[2..4].try_into().unwrap()),
]);
raw_bytes[4..].to_vec()
}
};
opcodes.push(Opcode {
index: i,
position: pos,
length,
number,
string: string.clone(),
flag,
raw_bytes,
align,
fixed_param,
param_bytes
});
// Break if END opcode reached
if &string == "END" {
break;
}
pos += (length + 1) & !1;
i += 1;
}
Script {
name: name.to_string(),
code_count: opcodes.len(),
opcodes,
}
}
#[derive(Debug, Clone)]
struct Script {
name: String,
opcodes: Vec<Opcode>,
code_count: usize,
}
#[derive(Debug, Clone)]
struct Opcode {
index: usize,
position: usize,
length: usize,
number: u8,
string: String,
flag: u8,
raw_bytes: Vec<u8>,
align: Option<u8>,
fixed_param: Option<Vec<u16>>,
param_bytes: Vec<u8>,
}
#[derive(Debug, Clone)]
enum SpecificOpcode {
Message {
voice_id: u16,
messages: Vec<String>,
end: Vec<u8>,
},
Select,
Battle,
Task,
SayAVoiceText,
VarStrSet,
GoTo,
GoSub,
Jump,
FarCall,
IFN,
IFY,
Random,
ImageLoad,
Unknown,
}
impl SpecificOpcode {
pub fn decode(opcode_str: &str, param_bytes: Vec<u8>) -> Self {
match opcode_str {
"MESSAGE" => Self::message(param_bytes),
_ => Self::Unknown
}
}
fn message(param_bytes: Vec<u8>) -> Self {
let voice_id = u16::from_le_bytes(param_bytes[0..2].try_into().unwrap());
let mut messages = Vec::new();
let mut offset = 2;
for _ in 0..2 {
let (o, string) = utils::get_string(&param_bytes, offset, Encoding::UTF16, None).unwrap();
messages.push(string);
offset = o;
}
dbg!(&messages);
Self::Message {
voice_id,
messages,
end: param_bytes[offset..].to_vec()
}
}
}

53
luca_script/src/utils.rs Normal file
View file

@ -0,0 +1,53 @@
use std::error::Error;
use encoding_rs::*;
pub enum Encoding {
UTF8,
UTF16,
ShiftJIS,
}
pub fn get_string(
bytes: &[u8],
offset: usize,
format: Encoding,
len: Option<usize>
) -> Result<(usize, String), Box<dyn Error>> {
let slice = &bytes[offset..];
// Find the end of the string
let mut end = 0;
let mut char_width = 1;
if let Some(l) = len {
end = l;
} else {
match format {
Encoding::UTF8 | Encoding::ShiftJIS => {
while (end < slice.len()) && (slice[end] != 0) {
end += 1
}
},
Encoding::UTF16 => {
char_width = 2;
while (end + 1 < slice.len()) && !((slice[end] == 0) && (slice[end + 1] == 0)) {
end += 2
}
},
}
};
let string = match format {
Encoding::UTF8 => String::from_utf8(slice[..end].to_vec())?,
Encoding::UTF16 => {
String::from_utf16(
&slice[..end].chunks_exact(2)
.map(|e| u16::from_le_bytes(e.try_into().unwrap()))
.collect::<Vec<u16>>()
)?
}
Encoding::ShiftJIS => SHIFT_JIS.decode(&slice[..end]).0.to_string(),
};
Ok((offset + end + char_width, string))
}