mirror of
https://github.com/G2-Games/lbee-utils.git
synced 2025-04-19 07:12:55 -05:00
Added preliminary script parsing
This commit is contained in:
parent
3c4d7a89ec
commit
3437b6c7a9
5 changed files with 354 additions and 1 deletions
|
@ -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
12
luca_script/Cargo.toml
Normal 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
|
107
luca_script/src/LBEE_opcodes
Normal file
107
luca_script/src/LBEE_opcodes
Normal 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
181
luca_script/src/main.rs
Normal 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(¶m_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
53
luca_script/src/utils.rs
Normal 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))
|
||||||
|
}
|
Loading…
Reference in a new issue