mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-07 13:28:47 +08:00
Add support to dump name for panicon
This commit is contained in:
@@ -53,7 +53,7 @@ impl ScriptBuilder for EscudeBinListBuilder {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EscudeBinList {
|
||||
entries: Vec<ListEntry>,
|
||||
pub entries: Vec<ListEntry>,
|
||||
}
|
||||
|
||||
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<ScriptT>),
|
||||
Names(Vec<NameT>),
|
||||
Vars(Vec<VarT>),
|
||||
@@ -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<u8> {
|
||||
}
|
||||
|
||||
#[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<Point>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, StructPack)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
enum EnumGfx {
|
||||
pub enum EnumGfx {
|
||||
Bgs(Vec<BgT>),
|
||||
Evs(Vec<EvT>),
|
||||
Sts(Vec<StT>),
|
||||
@@ -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<BgmT>),
|
||||
Amb(Vec<AmbT>),
|
||||
Se(Vec<SeT>),
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ mod crypto;
|
||||
pub mod list;
|
||||
#[cfg(feature = "escude-arc")]
|
||||
mod lzw;
|
||||
mod ops;
|
||||
pub mod script;
|
||||
|
||||
31
src/scripts/escude/ops/base.rs
Normal file
31
src/scripts/escude/ops/base.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use super::super::script::{ReadParam, VM};
|
||||
use crate::ext::io::*;
|
||||
use anyhow::Result;
|
||||
|
||||
pub trait CustomOps<T: std::fmt::Debug + TryInto<u64>>: std::fmt::Debug {
|
||||
fn run<'a>(&mut self, vm: &mut VM<'a, T>, op: u8) -> Result<bool>
|
||||
where
|
||||
MemReaderRef<'a>: ReadParam<T>,
|
||||
T: TryInto<u64>
|
||||
+ Default
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ Copy
|
||||
+ std::fmt::Debug
|
||||
+ std::fmt::Display
|
||||
+ std::hash::Hash
|
||||
+ From<u8>
|
||||
+ std::ops::Neg<Output = T>
|
||||
+ std::ops::Add<Output = T>
|
||||
+ std::ops::Sub<Output = T>
|
||||
+ std::ops::Mul<Output = T>
|
||||
+ std::ops::Div<Output = T>
|
||||
+ std::ops::Rem<Output = T>
|
||||
+ std::ops::Not<Output = T>
|
||||
+ std::ops::BitAnd<Output = T>
|
||||
+ std::ops::BitOr<Output = T>
|
||||
+ std::ops::BitXor<Output = T>
|
||||
+ std::ops::Shr<Output = T>
|
||||
+ std::ops::Shl<Output = T>,
|
||||
anyhow::Error: From<<T as TryInto<u64>>::Error>;
|
||||
}
|
||||
2
src/scripts/escude/ops/mod.rs
Normal file
2
src/scripts/escude/ops/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod base;
|
||||
pub mod panicon;
|
||||
238
src/scripts/escude/ops/panicon.rs
Normal file
238
src/scripts/escude/ops/panicon.rs
Normal file
@@ -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<T: std::fmt::Debug + std::hash::Hash> {
|
||||
prev_name: Option<T>,
|
||||
menus: HashMap<T, T>,
|
||||
last_select: usize,
|
||||
}
|
||||
|
||||
impl<T: std::fmt::Debug + std::hash::Hash> PaniconOps<T> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
prev_name: None,
|
||||
menus: HashMap::new(),
|
||||
last_select: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use PaniconOp::*;
|
||||
|
||||
impl<T> CustomOps<T> for PaniconOps<T>
|
||||
where
|
||||
T: std::fmt::Debug + TryInto<u64> + std::hash::Hash,
|
||||
{
|
||||
fn run<'a>(&mut self, vm: &mut super::super::script::VM<'a, T>, op: u8) -> Result<bool>
|
||||
where
|
||||
MemReaderRef<'a>: ReadParam<T>,
|
||||
T: TryInto<u64>
|
||||
+ Default
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ Copy
|
||||
+ std::fmt::Debug
|
||||
+ std::fmt::Display
|
||||
+ std::hash::Hash
|
||||
+ From<u8>
|
||||
+ std::ops::Neg<Output = T>
|
||||
+ std::ops::Add<Output = T>
|
||||
+ std::ops::Sub<Output = T>
|
||||
+ std::ops::Mul<Output = T>
|
||||
+ std::ops::Div<Output = T>
|
||||
+ std::ops::Rem<Output = T>
|
||||
+ std::ops::Not<Output = T>
|
||||
+ std::ops::BitAnd<Output = T>
|
||||
+ std::ops::BitOr<Output = T>
|
||||
+ std::ops::BitXor<Output = T>
|
||||
+ std::ops::Shr<Output = T>
|
||||
+ std::ops::Shl<Output = T>,
|
||||
anyhow::Error: From<<T as TryInto<u64>>::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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<u8>,
|
||||
unk1: u32,
|
||||
strings: Vec<String>,
|
||||
names: Option<HashMap<usize, String>>,
|
||||
}
|
||||
|
||||
fn load_enum_script(
|
||||
filename: &str,
|
||||
encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<Vec<NameT>> {
|
||||
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<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
|
||||
pub fn new(data: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
|
||||
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<u8> = (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<T> {
|
||||
fn read_param(&mut self) -> Result<T>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VM<'a, T: std::fmt::Debug> {
|
||||
pub reader: MemReaderRef<'a>,
|
||||
pub data: Vec<T>,
|
||||
pub stack: Vec<u64>,
|
||||
pub strs: Vec<T>,
|
||||
pub vars: HashMap<T, T>,
|
||||
pub flags: HashMap<T, bool>,
|
||||
pub mess: BTreeSet<T>,
|
||||
pub names: HashMap<T, T>,
|
||||
}
|
||||
|
||||
impl ReadParam<i32> for MemReaderRef<'_> {
|
||||
fn read_param(&mut self) -> Result<i32> {
|
||||
Ok(self.read_i32()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> VM<'a, T>
|
||||
where
|
||||
MemReaderRef<'a>: ReadParam<T>,
|
||||
T: TryInto<u64>
|
||||
+ Default
|
||||
+ Eq
|
||||
+ Ord
|
||||
+ Copy
|
||||
+ std::fmt::Debug
|
||||
+ std::fmt::Display
|
||||
+ std::hash::Hash
|
||||
+ From<u8>
|
||||
+ std::ops::Neg<Output = T>
|
||||
+ std::ops::Add<Output = T>
|
||||
+ std::ops::Sub<Output = T>
|
||||
+ std::ops::Mul<Output = T>
|
||||
+ std::ops::Div<Output = T>
|
||||
+ std::ops::Rem<Output = T>
|
||||
+ std::ops::Not<Output = T>
|
||||
+ std::ops::BitAnd<Output = T>
|
||||
+ std::ops::BitOr<Output = T>
|
||||
+ std::ops::BitXor<Output = T>
|
||||
+ std::ops::Shr<Output = T>
|
||||
+ std::ops::Shl<Output = T>,
|
||||
anyhow::Error: From<<T as TryInto<u64>>::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<T> {
|
||||
self.data
|
||||
.pop()
|
||||
.ok_or_else(|| anyhow::anyhow!("No data to pop"))
|
||||
}
|
||||
|
||||
fn pop_stack(&mut self) -> Result<u64> {
|
||||
self.stack
|
||||
.pop()
|
||||
.ok_or_else(|| anyhow::anyhow!("No stack to pop"))
|
||||
}
|
||||
|
||||
pub fn run(&mut self, mut custom_ops: Option<Box<dyn CustomOps<T>>>) -> 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<bool> {
|
||||
for _ in 0..n {
|
||||
self.pop_data()?;
|
||||
}
|
||||
Ok(nbreak)
|
||||
}
|
||||
|
||||
pub fn skip_params(&mut self, nbreak: bool) -> Result<bool> {
|
||||
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<u64>) -> Result<Vec<T>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user