diff --git a/Cargo.lock b/Cargo.lock index 900b851..ead02d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "int-enum" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366a1634cccc76b4cfd3e7580de9b605e4d93f1edac48d786c1f867c0def495" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -202,6 +214,7 @@ dependencies = [ "clap", "csv", "encoding_rs", + "int-enum", "lazy_static", "msg_tool_macro", "rand", @@ -243,6 +256,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", +] + [[package]] name = "quote" version = "1.0.40" @@ -360,6 +385,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 8eadda1..5145b38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ anyhow = "1" clap = { version = "4.5", features = ["derive"] } csv = "1.3" encoding_rs = "0.8" +int-enum = { version = "1.2", optional = true } lazy_static = "1.5.0" msg_tool_macro = { path = "./msg_tool_macro" } rand = { version = "0.9", optional = true } @@ -19,7 +20,7 @@ unicode-segmentation = "1.12" default = ["bgi", "circus", "escude", "escude-arc"] bgi = [] circus = [] -escude = [] +escude = ["int-enum"] escude-arc = ["escude", "rand"] [target.'cfg(windows)'.dependencies] diff --git a/src/args.rs b/src/args.rs index d872401..8245f08 100644 --- a/src/args.rs +++ b/src/args.rs @@ -71,6 +71,10 @@ pub struct Arg { #[arg(long, action = ArgAction::SetTrue, global = true)] /// Whether to use fake compression for Escude archive pub escude_fake_compress: bool, + #[cfg(feature = "escude")] + #[arg(long, global = true)] + /// The path to the Escude enum script file (enum_scr.bin) + pub escude_enum_scr: Option, #[command(subcommand)] /// Command pub command: Command, diff --git a/src/ext/io.rs b/src/ext/io.rs index 0126a46..7e80f44 100644 --- a/src/ext/io.rs +++ b/src/ext/io.rs @@ -542,6 +542,15 @@ impl std::fmt::Debug for MemReader { } } +impl<'a> std::fmt::Debug for MemReaderRef<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MemReaderRef") + .field("pos", &self.pos) + .field("data_length", &self.data.len()) + .finish_non_exhaustive() + } +} + impl MemReader { pub fn new(data: Vec) -> Self { MemReader { data, pos: 0 } @@ -553,12 +562,20 @@ impl MemReader { pos: self.pos, } } + + pub fn is_eof(&self) -> bool { + self.pos >= self.data.len() + } } impl<'a> MemReaderRef<'a> { pub fn new(data: &'a [u8]) -> Self { MemReaderRef { data, pos: 0 } } + + pub fn is_eof(&self) -> bool { + self.pos >= self.data.len() + } } impl Read for MemReader { diff --git a/src/main.rs b/src/main.rs index 6739efe..1075fd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -989,6 +989,8 @@ fn main() { circus_mes_type: arg.circus_mes_type.clone(), #[cfg(feature = "escude-arc")] escude_fake_compress: arg.escude_fake_compress.clone(), + #[cfg(feature = "escude")] + escude_enum_scr: arg.escude_enum_scr.clone(), }; match &arg.command { args::Command::Export { input, output } => { diff --git a/src/scripts/escude/list.rs b/src/scripts/escude/list.rs index adfcb70..9f31e01 100644 --- a/src/scripts/escude/list.rs +++ b/src/scripts/escude/list.rs @@ -53,7 +53,7 @@ impl ScriptBuilder for EscudeBinListBuilder { #[derive(Debug)] pub struct EscudeBinList { - entries: Vec, + pub entries: Vec, } impl EscudeBinList { @@ -318,7 +318,7 @@ impl Script for EscudeBinList { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct ScriptT { +pub struct ScriptT { #[fstring = 64] #[fstring_pad = 0x20] /// File name @@ -330,7 +330,7 @@ struct ScriptT { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct NameT { +pub struct NameT { #[fstring = 64] #[fstring_pad = 0x20] /// Name of the character @@ -344,7 +344,7 @@ struct NameT { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct VarT { +pub struct VarT { /// Variable name #[fstring = 32] #[fstring_pad = 0x20] @@ -356,7 +356,7 @@ struct VarT { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct SceneT { +pub struct SceneT { /// The scene script ID pub script: u32, /// The scene name @@ -373,7 +373,7 @@ struct SceneT { #[derive(Debug, Serialize, Deserialize, StructPack)] #[serde(tag = "type", content = "data")] -enum EnumScr { +pub enum EnumScr { Scripts(Vec), Names(Vec), Vars(Vec), @@ -381,7 +381,7 @@ enum EnumScr { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct BgT { +pub struct BgT { /// Background image name #[fstring = 32] #[fstring_pad = 0x20] @@ -402,7 +402,7 @@ struct BgT { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct EvT { +pub struct EvT { /// Event image name #[fstring = 32] #[fstring_pad = 0x20] @@ -423,7 +423,7 @@ struct EvT { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct StT { +pub struct StT { #[fstring = 32] #[fstring_pad = 0x20] name: String, @@ -442,7 +442,7 @@ struct StT { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct EfxT { +pub struct EfxT { /// Effect image name #[fstring = 32] #[fstring_pad = 0x20] @@ -465,20 +465,20 @@ fn exft_padding() -> Vec { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct Point { +pub struct Point { x: i16, y: i16, } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct LocT { +pub struct LocT { #[fvec = 8] pt: Vec, } #[derive(Debug, Serialize, Deserialize, StructPack)] #[serde(tag = "type", content = "data")] -enum EnumGfx { +pub enum EnumGfx { Bgs(Vec), Evs(Vec), Sts(Vec), @@ -487,7 +487,7 @@ enum EnumGfx { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct BgmT { +pub struct BgmT { #[fstring = 64] #[fstring_pad = 0x20] pub name: String, @@ -501,7 +501,7 @@ struct BgmT { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct AmbT { +pub struct AmbT { #[fstring = 64] #[fstring_pad = 0x20] pub name: String, @@ -511,7 +511,7 @@ struct AmbT { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct SeT { +pub struct SeT { #[fstring = 64] #[fstring_pad = 0x20] pub name: String, @@ -521,7 +521,7 @@ struct SeT { } #[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)] -struct SfxT { +pub struct SfxT { #[fstring = 64] #[fstring_pad = 0x20] pub name: String, @@ -532,7 +532,7 @@ struct SfxT { #[derive(Debug, Serialize, Deserialize, StructPack)] #[serde(tag = "type", content = "data")] -enum EnumSnd { +pub enum EnumSnd { Bgm(Vec), Amb(Vec), Se(Vec), @@ -541,7 +541,7 @@ enum EnumSnd { #[derive(Debug, Serialize, Deserialize, StructPack)] #[serde(tag = "type", content = "data")] -enum ListData { +pub enum ListData { Scr(EnumScr), Gfx(EnumGfx), Snd(EnumSnd), @@ -549,7 +549,7 @@ enum ListData { } #[derive(Debug, Serialize, Deserialize)] -struct ListEntry { +pub struct ListEntry { id: u32, - data: ListData, + pub data: ListData, } diff --git a/src/scripts/escude/mod.rs b/src/scripts/escude/mod.rs index 6cf2c46..74c148f 100644 --- a/src/scripts/escude/mod.rs +++ b/src/scripts/escude/mod.rs @@ -5,4 +5,5 @@ mod crypto; pub mod list; #[cfg(feature = "escude-arc")] mod lzw; +mod ops; pub mod script; diff --git a/src/scripts/escude/ops/base.rs b/src/scripts/escude/ops/base.rs new file mode 100644 index 0000000..6988dc6 --- /dev/null +++ b/src/scripts/escude/ops/base.rs @@ -0,0 +1,31 @@ +use super::super::script::{ReadParam, VM}; +use crate::ext::io::*; +use anyhow::Result; + +pub trait CustomOps>: std::fmt::Debug { + fn run<'a>(&mut self, vm: &mut VM<'a, T>, op: u8) -> Result + where + MemReaderRef<'a>: ReadParam, + T: TryInto + + Default + + Eq + + Ord + + Copy + + std::fmt::Debug + + std::fmt::Display + + std::hash::Hash + + From + + std::ops::Neg + + std::ops::Add + + std::ops::Sub + + std::ops::Mul + + std::ops::Div + + std::ops::Rem + + std::ops::Not + + std::ops::BitAnd + + std::ops::BitOr + + std::ops::BitXor + + std::ops::Shr + + std::ops::Shl, + anyhow::Error: From<>::Error>; +} diff --git a/src/scripts/escude/ops/mod.rs b/src/scripts/escude/ops/mod.rs new file mode 100644 index 0000000..39ffcaf --- /dev/null +++ b/src/scripts/escude/ops/mod.rs @@ -0,0 +1,2 @@ +pub mod base; +pub mod panicon; diff --git a/src/scripts/escude/ops/panicon.rs b/src/scripts/escude/ops/panicon.rs new file mode 100644 index 0000000..70ddaf6 --- /dev/null +++ b/src/scripts/escude/ops/panicon.rs @@ -0,0 +1,238 @@ +use super::super::script::ReadParam; +use super::base::CustomOps; +use crate::ext::io::*; +use anyhow::Result; +use int_enum::IntEnum; +use std::collections::HashMap; +use std::io::Seek; + +#[repr(u8)] +#[derive(Debug, IntEnum)] +enum PaniconOp { + End = 0x22, + Jump, + Call, + AutoPlay, + Frame, + Text, + Clear, + Gap, + Mes, + Tlk, + Menu, + Select, + LsfInit, + LsfSet, + Cg, + Em, + Clr, + Disp, + Path, + Trans, + BgmPlay, + BgmStop, + BgmVolume, + BgmFx, + AmbPlay, + AmbStop, + AmbVolume, + AmbFx, + SePlay, + SeStop, + SeWait, + SeVolume, + SeFx, + VocPlay, + VocStop, + VocWait, + VocVolume, + VocFx, + Quake, + Flash, + Filter, + Effect, + Sync, + Wait, + Movie, + Credit, + Event, + Scene, + Title, + Notice, + SetPass, + IsPass, + AutoSave, + Place, + OpenName, + Name, + LogNew, + LogOut, +} + +#[derive(Debug)] +pub struct PaniconOps { + prev_name: Option, + menus: HashMap, + last_select: usize, +} + +impl PaniconOps { + pub fn new() -> Self { + Self { + prev_name: None, + menus: HashMap::new(), + last_select: 0, + } + } +} + +use PaniconOp::*; + +impl CustomOps for PaniconOps +where + T: std::fmt::Debug + TryInto + std::hash::Hash, +{ + fn run<'a>(&mut self, vm: &mut super::super::script::VM<'a, T>, op: u8) -> Result + where + MemReaderRef<'a>: ReadParam, + T: TryInto + + Default + + Eq + + Ord + + Copy + + std::fmt::Debug + + std::fmt::Display + + std::hash::Hash + + From + + std::ops::Neg + + std::ops::Add + + std::ops::Sub + + std::ops::Mul + + std::ops::Div + + std::ops::Rem + + std::ops::Not + + std::ops::BitAnd + + std::ops::BitOr + + std::ops::BitXor + + std::ops::Shr + + std::ops::Shl, + anyhow::Error: From<>::Error>, + { + if let Ok(op) = PaniconOp::try_from(op) { + // println!("Running Panicon operation: {:?}", op); + match op { + End => vm.skip_n_params(1, false), + Jump => vm.skip_n_params(1, false), + Call => vm.skip_n_params(1, false), + AutoPlay => vm.skip_n_params(1, false), + Frame => vm.skip_n_params(1, false), + Text => vm.skip_n_params(2, false), + Clear => vm.skip_n_params(1, false), + Gap => vm.skip_n_params(2, false), + // Handle concat name + Mes => { + let mes = vm.pop_data()?; + vm.mess.insert(mes); + if let Some(name) = self.prev_name.take() { + vm.names.insert(mes, name); + } + Ok(false) + } + Tlk => { + let params = vm.read_params(None)?; + let name = params + .get(0) + .cloned() + .ok_or(anyhow::anyhow!("Missing name parameter"))?; + self.prev_name = Some(name); + Ok(false) + } + Menu => { + let params = vm.read_params(Some(3))?; + let id = params[0]; + let mes = params[1]; + vm.mess.insert(mes); + self.menus.insert(id, mes); + Ok(false) + } + Select => { + if let Some(var) = vm.vars.get_mut(&T::from(131)) { + *var = *var + T::from(1); + return Ok(false); + } + let offset = vm.reader.stream_position()? - 1; + for _ in self.last_select + 1..self.menus.len() { + vm.stack.push(offset); + // println!("Pushing offset: {offset:#x} to stack"); + } + vm.vars.insert(T::from(131), T::from(0)); + vm.skip_n_params(2, false) + } + LsfInit => vm.skip_n_params(1, false), + LsfSet => vm.skip_params(false), + Cg => vm.skip_params(false), + Em => vm.skip_n_params(5, false), + Clr => vm.skip_n_params(1, false), + Disp => vm.skip_n_params(4, false), + Path => vm.skip_params(false), + Trans => Ok(false), + BgmPlay => vm.skip_n_params(3, false), + BgmStop => vm.skip_n_params(1, false), + BgmVolume => vm.skip_n_params(2, false), + BgmFx => vm.skip_n_params(1, false), + AmbPlay => vm.skip_n_params(3, false), + AmbStop => vm.skip_n_params(1, false), + AmbVolume => vm.skip_n_params(2, false), + AmbFx => vm.skip_n_params(1, false), + SePlay => vm.skip_n_params(5, false), + SeStop => vm.skip_n_params(2, false), + SeWait => vm.skip_n_params(1, false), + SeVolume => vm.skip_n_params(3, false), + SeFx => vm.skip_n_params(1, false), + VocPlay => vm.skip_n_params(4, false), + VocStop => vm.skip_n_params(2, false), + VocWait => vm.skip_n_params(1, false), + VocVolume => vm.skip_n_params(3, false), + VocFx => vm.skip_n_params(1, false), + Quake => vm.skip_n_params(4, false), + Flash => vm.skip_n_params(2, false), + Filter => vm.skip_n_params(2, false), + Effect => vm.skip_n_params(1, false), + Sync => vm.skip_n_params(2, false), + Wait => vm.skip_n_params(1, false), + Movie => vm.skip_n_params(1, false), + Credit => vm.skip_n_params(1, false), + Event => vm.skip_n_params(1, false), + Scene => vm.skip_n_params(1, false), + Title => { + let title = vm.pop_data()?; + vm.mess.insert(title); + Ok(false) + } + Notice => { + let notice = vm.pop_data()?; + vm.mess.insert(notice); + vm.skip_n_params(2, false) + } + SetPass => vm.skip_n_params(2, false), + IsPass => vm.skip_n_params(1, false), + AutoSave => Ok(false), + Place => vm.skip_n_params(1, false), + OpenName => vm.skip_n_params(1, false), + Name => { + eprintln!("Name operation."); + let mes = vm.pop_data()?; + let name = vm.pop_data()?; + vm.mess.insert(mes); + vm.names.insert(mes, name); + Ok(false) + } + LogNew => vm.skip_n_params(1, false), + LogOut => vm.skip_params(false), + } + } else { + // return Err(anyhow::anyhow!("Unknown Panicon operation: {op:#02x}")); + Ok(false) + } + } +} diff --git a/src/scripts/escude/script.rs b/src/scripts/escude/script.rs index 026e813..50edfdd 100644 --- a/src/scripts/escude/script.rs +++ b/src/scripts/escude/script.rs @@ -1,12 +1,15 @@ +use super::list::{EnumScr, EscudeBinList, ListData, NameT}; +use super::ops::base::CustomOps; use crate::ext::io::*; use crate::scripts::base::*; use crate::types::*; use crate::utils::encoding::{decode_to_string, encode_string}; use crate::utils::struct_pack::StructPack; use anyhow::Result; -use std::collections::HashMap; +use int_enum::IntEnum; +use std::collections::{BTreeSet, HashMap}; use std::ffi::CString; -use std::io::Read; +use std::io::{Read, Seek, SeekFrom}; use unicode_segmentation::UnicodeSegmentation; #[derive(Debug)] @@ -55,10 +58,32 @@ pub struct EscudeBinScript { vms: Vec, unk1: u32, strings: Vec, + names: Option>, +} + +fn load_enum_script( + filename: &str, + encoding: Encoding, + config: &ExtraConfig, +) -> Result> { + let buf = crate::utils::files::read_file(filename)?; + let scr = EscudeBinList::new(buf, filename, encoding, config)?; + for scr in scr.entries { + match scr.data { + ListData::Scr(scr) => match scr { + EnumScr::Names(names) => return Ok(names), + _ => {} + }, + _ => {} + } + } + Err(anyhow::anyhow!( + "Failed to find name table in Escude enum script", + )) } impl EscudeBinScript { - pub fn new(data: Vec, encoding: Encoding, _config: &ExtraConfig) -> Result { + pub fn new(data: Vec, encoding: Encoding, config: &ExtraConfig) -> Result { let mut reader = MemReader::new(data); let mut magic = [0u8; 8]; reader.read_exact(&mut magic)?; @@ -92,7 +117,45 @@ impl EscudeBinScript { strings.push(decode_to_string(encoding, s.as_bytes())?); } } - Ok(EscudeBinScript { vms, unk1, strings }) + let names = match &config.escude_enum_scr { + Some(loc) => match load_enum_script(loc, encoding, config) { + Ok(list) => { + let mut names = HashMap::new(); + let mut vm = VM::new(&vms); + vm.vars.insert(1, 1); + vm.vars.insert(132, 0); + vm.vars.insert(133, 0); + vm.vars.insert(134, 0); + vm.vars.insert(1001, 0); + vm.vars.insert(1003, 0); + for i in 135..140 { + vm.vars.insert(i, 1); + } + let _ = vm.run(Some(Box::new(super::ops::panicon::PaniconOps::new()))); + for (index, name) in vm.names.iter() { + if let Some(name) = list.get(*name as usize) { + names.insert(*index as usize, name.text.clone()); + } + } + Some(names) + } + Err(e) => { + eprintln!( + "WARN: Failed to load Escude enum script from {}: {}", + loc, e + ); + crate::COUNTER.inc_warning(); + None + } + }, + None => None, + }; + Ok(EscudeBinScript { + vms, + unk1, + strings, + names, + }) } } @@ -109,9 +172,10 @@ impl Script for EscudeBinScript { Ok(self .strings .iter() - .map(|s| Message { + .enumerate() + .map(|(i, s)| Message { message: s.to_string(), - name: None, + name: self.names.as_ref().map(|n| n.get(&i).cloned()).flatten(), }) .collect()) } @@ -173,7 +237,7 @@ impl StrReplacer { let half_width_katakana = "!? 。「」、…をぁぃぅぇぉゃゅょっーあいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわん゛゜"; let mut bytes: Vec = (0xa0..=0xde).collect(); bytes.insert(0, 0x21); - bytes.insert(1, 0x22); + bytes.insert(1, 0x3f); s.add(&bytes, half_width_katakana)?; Ok(s) } @@ -230,3 +294,369 @@ impl StrReplacer { Ok(output) } } + +#[repr(u8)] +#[derive(Debug, IntEnum)] +enum BaseOp { + End, + Jump, + JumpZ, + Call, + Ret, + Push, + Pop, + Str, + SetVar, + GetVar, + SetFlag, + GetFlag, + Neg, + Add, + Sub, + Mul, + Div, + Mod, + Not, + And, + Or, + Xor, + Shr, + Shl, + Eq, + Ne, + Gt, + Ge, + Lt, + Le, + LNot, + LAnd, + LOr, + FileLine, +} + +pub trait ReadParam { + fn read_param(&mut self) -> Result; +} + +#[derive(Debug)] +pub struct VM<'a, T: std::fmt::Debug> { + pub reader: MemReaderRef<'a>, + pub data: Vec, + pub stack: Vec, + pub strs: Vec, + pub vars: HashMap, + pub flags: HashMap, + pub mess: BTreeSet, + pub names: HashMap, +} + +impl ReadParam for MemReaderRef<'_> { + fn read_param(&mut self) -> Result { + Ok(self.read_i32()?) + } +} + +impl<'a, T> VM<'a, T> +where + MemReaderRef<'a>: ReadParam, + T: TryInto + + Default + + Eq + + Ord + + Copy + + std::fmt::Debug + + std::fmt::Display + + std::hash::Hash + + From + + std::ops::Neg + + std::ops::Add + + std::ops::Sub + + std::ops::Mul + + std::ops::Div + + std::ops::Rem + + std::ops::Not + + std::ops::BitAnd + + std::ops::BitOr + + std::ops::BitXor + + std::ops::Shr + + std::ops::Shl, + anyhow::Error: From<>::Error>, +{ + pub fn new(data: &'a [u8]) -> Self { + VM { + reader: MemReaderRef::new(data), + data: Vec::new(), + stack: Vec::new(), + strs: Vec::new(), + vars: HashMap::new(), + flags: HashMap::new(), + mess: BTreeSet::new(), + names: HashMap::new(), + } + } + + pub fn pop_data(&mut self) -> Result { + self.data + .pop() + .ok_or_else(|| anyhow::anyhow!("No data to pop")) + } + + fn pop_stack(&mut self) -> Result { + self.stack + .pop() + .ok_or_else(|| anyhow::anyhow!("No stack to pop")) + } + + pub fn run(&mut self, mut custom_ops: Option>>) -> Result<()> { + loop { + if self.reader.is_eof() { + break; + } + let op = self.reader.read_u8()?; + if let Ok(op) = BaseOp::try_from(op) { + // println!("Op code: {op:?}"); + match op { + BaseOp::End => break, + BaseOp::Jump => { + let offset: T = self.reader.read_param()?; + let offset: u64 = offset.try_into()?; + self.reader.seek(SeekFrom::Start(offset))?; + } + BaseOp::JumpZ => { + let offset: T = self.reader.read_param()?; + let offset: u64 = offset.try_into()?; + if self.pop_data()? == Default::default() { + self.reader.seek(SeekFrom::Start(offset))?; + } + } + BaseOp::Call => { + let offset: T = self.reader.read_param()?; + let offset: u64 = offset.try_into()?; + let pos = self.reader.stream_position()?; + self.stack.push(pos); + self.reader.seek(SeekFrom::Start(offset))?; + } + BaseOp::Ret => { + if self.stack.is_empty() { + let code = self.reader.read_u8()?; + if code == 0 && self.reader.is_eof() { + break; + } + } + let stack = self.pop_stack()?; + self.reader.seek(SeekFrom::Start(stack))?; + } + BaseOp::Push => { + let d = self.reader.read_param()?; + self.data.push(d); + } + BaseOp::Pop => { + self.pop_data()?; + } + BaseOp::Str => { + let param = self.reader.read_param()?; + self.strs.push(param); + self.data.push(param); + } + BaseOp::SetVar => { + let value = self.pop_data()?; + let index = self.pop_data()?; + self.vars.insert(index, value); + self.data.push(value); + } + BaseOp::GetVar => { + let index = self.pop_data()?; + let value = self + .vars + .get(&index) + .ok_or_else(|| anyhow::anyhow!("Variable not found: {}", index))?; + self.data.push(*value); + } + BaseOp::SetFlag => { + let value = self.pop_data()?; + let index = self.pop_data()?; + let flag = value != Default::default(); + self.flags.insert(index, flag); + self.data.push(value); + } + BaseOp::GetFlag => { + let index = self.pop_data()?; + let flag = self.flags.get(&index).cloned().unwrap_or(false); + self.data + .push(if flag { T::from(1u8) } else { T::from(0u8) }); + } + BaseOp::Neg => { + let value = -self.pop_data()?; + self.data.push(value); + } + BaseOp::Add => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data.push(a + b); + } + BaseOp::Sub => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data.push(a - b); + } + BaseOp::Mul => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data.push(a * b); + } + BaseOp::Div => { + let b = self.pop_data()?; + if b == Default::default() { + return Err(anyhow::anyhow!("Division by zero")); + } + let a = self.pop_data()?; + self.data.push(a / b); + } + BaseOp::Mod => { + let b = self.pop_data()?; + if b == Default::default() { + return Err(anyhow::anyhow!("Division by zero")); + } + let a = self.pop_data()?; + self.data.push(a % b); + } + BaseOp::Not => { + let value = self.pop_data()?; + self.data.push(!value); + } + BaseOp::And => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data.push(a & b); + } + BaseOp::Or => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data.push(a | b); + } + BaseOp::Xor => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data.push(a ^ b); + } + BaseOp::Shr => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data.push(a >> b); + } + BaseOp::Shl => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data.push(a << b); + } + BaseOp::Eq => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data + .push(if a == b { T::from(1u8) } else { T::from(0u8) }); + } + BaseOp::Ne => { + let b = self.pop_data()?; + let a = self.pop_data()?; + self.data + .push(if a != b { T::from(1u8) } else { T::from(0u8) }); + } + // Original code may contains undefined behavior for these operations + BaseOp::Gt => { + let a = self.pop_data()?; + let b = self.pop_data()?; + self.data + .push(if a > b { T::from(1u8) } else { T::from(0u8) }); + } + BaseOp::Ge => { + let a = self.pop_data()?; + let b = self.pop_data()?; + self.data + .push(if a >= b { T::from(1u8) } else { T::from(0u8) }); + } + BaseOp::Lt => { + let a = self.pop_data()?; + let b = self.pop_data()?; + self.data + .push(if a < b { T::from(1u8) } else { T::from(0u8) }); + } + BaseOp::Le => { + let a = self.pop_data()?; + let b = self.pop_data()?; + self.data + .push(if a <= b { T::from(1u8) } else { T::from(0u8) }); + } + BaseOp::LNot => { + let value = self.pop_data()?; + self.data.push(if value == Default::default() { + T::from(1u8) + } else { + T::from(0u8) + }); + } + BaseOp::LAnd => { + let b = self.pop_data()? != Default::default(); + let a = self.pop_data()? != Default::default(); + self.data + .push(if a && b { T::from(1u8) } else { T::from(0u8) }); + } + BaseOp::LOr => { + let b = self.pop_data()? != Default::default(); + let a = self.pop_data()? != Default::default(); + self.data + .push(if a || b { T::from(1u8) } else { T::from(0u8) }); + } + BaseOp::FileLine => { + let _: T = self.reader.read_param()?; + } + } + continue; + } + if let Some(ops) = &mut custom_ops { + let nbreak = ops.run(self, op)?; + if nbreak { + break; + } + } else { + return Err(anyhow::anyhow!("Unknown operation: {}", op)); + } + } + Ok(()) + } + + pub fn skip_n_params(&mut self, n: u64, nbreak: bool) -> Result { + for _ in 0..n { + self.pop_data()?; + } + Ok(nbreak) + } + + pub fn skip_params(&mut self, nbreak: bool) -> Result { + let count: T = self.reader.read_param()?; + let count: u64 = count.try_into()?; + self.skip_n_params(count, nbreak) + } + + pub fn read_params(&mut self, ncount: Option) -> Result> { + let count = match ncount { + Some(count) => count, + None => { + let count: T = self.reader.read_param()?; + count.try_into()? + } + }; + let data_len = self.data.len(); + if (data_len as u64) < count { + return Err(anyhow::anyhow!( + "Not enough data to read {} parameters, only {} parameters available", + count, + data_len + )); + } + let mut params = Vec::with_capacity(count as usize); + params.resize(count as usize, Default::default()); + params.copy_from_slice(&self.data[data_len - count as usize..]); + self.data.truncate(data_len - count as usize); + Ok(params) + } +} diff --git a/src/types.rs b/src/types.rs index 96c60dc..29ac040 100644 --- a/src/types.rs +++ b/src/types.rs @@ -191,6 +191,8 @@ pub struct ExtraConfig { pub circus_mes_type: Option, #[cfg(feature = "escude-arc")] pub escude_fake_compress: bool, + #[cfg(feature = "escude")] + pub escude_enum_scr: Option, } #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]