mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-07 21:38:58 +08:00
Add support to dump name for panicon
This commit is contained in:
@@ -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