mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-07 21:38:58 +08:00
Add silky engine mes support
This commit is contained in:
@@ -20,6 +20,8 @@ pub mod ex_hibit;
|
||||
pub mod hexen_haus;
|
||||
#[cfg(feature = "kirikiri")]
|
||||
pub mod kirikiri;
|
||||
#[cfg(feature = "silky")]
|
||||
pub mod silky;
|
||||
#[cfg(feature = "softpal")]
|
||||
pub mod softpal;
|
||||
#[cfg(feature = "will-plus")]
|
||||
@@ -118,6 +120,8 @@ lazy_static::lazy_static! {
|
||||
Box::new(kirikiri::tjs_ns0::TjsNs0Builder::new()),
|
||||
#[cfg(feature = "kirikiri")]
|
||||
Box::new(kirikiri::tjs2::Tjs2Builder::new()),
|
||||
#[cfg(feature = "silky")]
|
||||
Box::new(silky::mes::MesBuilder::new()),
|
||||
];
|
||||
/// A list of all script extensions.
|
||||
pub static ref ALL_EXTS: Vec<String> =
|
||||
|
||||
457
src/scripts/silky/disasm.rs
Normal file
457
src/scripts/silky/disasm.rs
Normal file
@@ -0,0 +1,457 @@
|
||||
use crate::ext::io::*;
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Oper {
|
||||
/// Byte
|
||||
B,
|
||||
/// Integer
|
||||
I,
|
||||
/// Address
|
||||
A,
|
||||
/// String
|
||||
S,
|
||||
/// Text
|
||||
T,
|
||||
}
|
||||
|
||||
use Oper::*;
|
||||
|
||||
pub struct Opcodes {
|
||||
pub r#yield: u8,
|
||||
pub add: u8,
|
||||
pub escape_sequence: u8,
|
||||
pub message1: u8,
|
||||
pub message2: u8,
|
||||
pub push_int: u8,
|
||||
pub push_string: u8,
|
||||
pub syscall: u8,
|
||||
pub line_number: u8,
|
||||
pub nop1: u8,
|
||||
pub nop2: u8,
|
||||
pub is_message1_obfuscated: bool,
|
||||
}
|
||||
|
||||
pub struct Syscalls {
|
||||
pub exec: i32,
|
||||
pub exec_set_character_name: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SlikyStringType {
|
||||
Internal,
|
||||
Message,
|
||||
Name,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SlikyString {
|
||||
pub start: u64,
|
||||
pub len: u64,
|
||||
pub typ: SlikyStringType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Obj {
|
||||
Byte(u8),
|
||||
Int(i32),
|
||||
Str(SlikyString),
|
||||
}
|
||||
|
||||
pub trait Disasm: std::fmt::Debug {
|
||||
fn stream(&self) -> &MemReader;
|
||||
fn stream_mut(&mut self) -> &mut MemReader;
|
||||
fn opcodes(&self) -> &'static Opcodes;
|
||||
fn operands(&self) -> &'static [(u8, &'static [Oper])];
|
||||
fn syscalls(&self) -> &'static [Syscalls];
|
||||
fn code_offset(&self) -> u32;
|
||||
fn big_endian_addresses(&self) -> &[u32];
|
||||
fn push_big_endian_addresses(&mut self, addr: u32);
|
||||
fn little_endian_addresses(&self) -> &[u32];
|
||||
fn read_header(&mut self) -> Result<()>;
|
||||
fn read_instruction(&mut self) -> Result<(u8, Vec<Obj>)> {
|
||||
let opcode = self.stream_mut().read_u8()?;
|
||||
let mut operands = Vec::new();
|
||||
if let Some((_, ops)) = self.operands().iter().find(|(op, _)| *op == opcode) {
|
||||
for &oper in *ops {
|
||||
operands.push(self.read_operand(oper)?);
|
||||
}
|
||||
}
|
||||
Ok((opcode, operands))
|
||||
}
|
||||
fn read_operand(&mut self, oper: Oper) -> Result<Obj> {
|
||||
match oper {
|
||||
B => Ok(Obj::Byte(self.stream_mut().read_u8()?)),
|
||||
I => Ok(Obj::Int(self.stream_mut().read_i32_be()?)),
|
||||
A => {
|
||||
self.push_big_endian_addresses(self.stream().pos as u32);
|
||||
Ok(Obj::Int(self.stream_mut().read_i32_be()?))
|
||||
}
|
||||
S | T => {
|
||||
let start = self.stream().pos as u64;
|
||||
let s = self.stream_mut().read_cstring()?;
|
||||
Ok(Obj::Str(SlikyString {
|
||||
start,
|
||||
len: s.as_bytes_with_nul().len() as u64,
|
||||
typ: SlikyStringType::Internal,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
fn read_code(&mut self) -> Result<Vec<SlikyString>> {
|
||||
let mut stack: Vec<Obj> = Vec::new();
|
||||
let mut message_start_offset = None;
|
||||
let mut in_ruby = false;
|
||||
let mut texts = Vec::new();
|
||||
self.stream_mut().pos = self.code_offset() as usize;
|
||||
while !self.stream().is_eof() {
|
||||
let instr_offset = self.stream().pos as u64;
|
||||
let (opcode, operands) = self.read_instruction()?;
|
||||
// message instr
|
||||
let opcodes = self.opcodes();
|
||||
if opcode == opcodes.message1 || opcode == opcodes.message2 {
|
||||
if message_start_offset.is_none() {
|
||||
message_start_offset = Some(instr_offset);
|
||||
}
|
||||
} else if opcode == opcodes.escape_sequence {
|
||||
if let Some(Obj::Byte(b)) = operands.get(0) {
|
||||
if *b == 0x1 {
|
||||
in_ruby = true;
|
||||
}
|
||||
}
|
||||
} else if opcode == opcodes.r#yield && in_ruby {
|
||||
in_ruby = false;
|
||||
} else if opcode == opcodes.push_int
|
||||
&& self.stream().cpeek_u8_at(instr_offset + 5)? == opcodes.line_number
|
||||
{
|
||||
// Skip
|
||||
} else if opcode == opcodes.line_number
|
||||
|| opcode == opcodes.nop1
|
||||
|| opcode == opcodes.nop2
|
||||
{
|
||||
// Skip
|
||||
} else {
|
||||
if let Some(start) = message_start_offset {
|
||||
let start = start as u64;
|
||||
let text = SlikyString {
|
||||
start,
|
||||
len: instr_offset - start,
|
||||
typ: SlikyStringType::Message,
|
||||
};
|
||||
texts.push(text);
|
||||
}
|
||||
message_start_offset = None;
|
||||
in_ruby = false;
|
||||
}
|
||||
// name instr
|
||||
if opcode == opcodes.push_int || opcode == opcodes.push_string {
|
||||
if !operands.is_empty() {
|
||||
stack.push(operands[0].clone());
|
||||
}
|
||||
} else if opcode == opcodes.add && stack.len() >= 2 {
|
||||
let value1 = stack.pop().unwrap();
|
||||
let value2 = stack.pop().unwrap();
|
||||
if let (Obj::Int(i1), Obj::Int(i2)) = (value1, value2) {
|
||||
stack.push(Obj::Int(i1 + i2));
|
||||
}
|
||||
} else if opcode == opcodes.syscall && stack.len() >= 3 {
|
||||
let func_id = stack.pop().unwrap();
|
||||
let exec_id = stack.pop().unwrap();
|
||||
let name = stack.pop().unwrap();
|
||||
if let (Obj::Int(func_id), Obj::Int(exec_id), Obj::Str(name)) =
|
||||
(func_id, exec_id, name)
|
||||
{
|
||||
for syscall in self.syscalls() {
|
||||
if func_id == syscall.exec && exec_id == syscall.exec_set_character_name {
|
||||
texts.push(SlikyString {
|
||||
start: name.start - 1,
|
||||
len: name.len + 1,
|
||||
typ: SlikyStringType::Name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.clear();
|
||||
} else {
|
||||
stack.clear();
|
||||
}
|
||||
}
|
||||
Ok(texts)
|
||||
}
|
||||
}
|
||||
|
||||
pub const PLUS_OPCODES: Opcodes = Opcodes {
|
||||
r#yield: 0x00,
|
||||
add: 0x34,
|
||||
escape_sequence: 0x1c,
|
||||
message1: 0x0a,
|
||||
message2: 0x0b,
|
||||
push_int: 0x32,
|
||||
push_string: 0x33,
|
||||
syscall: 0x18,
|
||||
line_number: 0xff,
|
||||
nop1: 0xfc,
|
||||
nop2: 0xfd,
|
||||
is_message1_obfuscated: true,
|
||||
};
|
||||
|
||||
const PLUS_OPERANDS: [(u8, &[Oper]); 53] = [
|
||||
(0x00, &[]), // yield
|
||||
(0x01, &[]), // ret
|
||||
(0x02, &[]), // ldglob1.i8
|
||||
(0x03, &[]), // ldglob2.i16
|
||||
(0x04, &[]), // ldglob3.var
|
||||
(0x05, &[]), // ldglob4.var
|
||||
(0x06, &[]), // ldloc.var
|
||||
(0x07, &[]), // ldglob5.i8
|
||||
(0x08, &[]), // ldglob5.i16
|
||||
(0x09, &[]), // ldglob5.i32
|
||||
(0x0A, &[S]), // message
|
||||
(0x0B, &[T]), // message
|
||||
(0x0C, &[]), // stglob1.i8
|
||||
(0x0D, &[]), // stglob2.i16
|
||||
(0x0E, &[]), // stglob3.var
|
||||
(0x0F, &[]), // stglob4.var
|
||||
(0x10, &[]), // stloc.var
|
||||
(0x11, &[]), // stglob5.i8
|
||||
(0x12, &[]), // stglob5.i16
|
||||
(0x13, &[]), // stglob5.i32
|
||||
(0x14, &[A]), // jz
|
||||
(0x15, &[A]), // jmp
|
||||
(0x16, &[A]), // libreg
|
||||
(0x17, &[]), // libcall
|
||||
(0x18, &[]), // syscall
|
||||
(0x19, &[I]), // msgid
|
||||
(0x1A, &[I]), // msgid2
|
||||
(0x1B, &[A]), // choice
|
||||
(0x1C, &[B]), // escape sequence
|
||||
(0x32, &[I]), // ldc.i4
|
||||
(0x33, &[S]), // ldstr
|
||||
(0x34, &[]), // add
|
||||
(0x35, &[]), // sub
|
||||
(0x36, &[]), // mul
|
||||
(0x37, &[]), // div
|
||||
(0x38, &[]), // mod
|
||||
(0x39, &[]), // rand
|
||||
(0x3A, &[]), // logand
|
||||
(0x3B, &[]), // logor
|
||||
(0x3C, &[]), // binand
|
||||
(0x3D, &[]), // binor
|
||||
(0x3E, &[]), // lt
|
||||
(0x3F, &[]), // gt
|
||||
(0x40, &[]), // le
|
||||
(0x41, &[]), // ge
|
||||
(0x42, &[]), // eq
|
||||
(0x43, &[]), // neq
|
||||
(0xFA, &[]),
|
||||
(0xFB, &[]),
|
||||
(0xFC, &[]),
|
||||
(0xFD, &[]),
|
||||
(0xFE, &[]),
|
||||
(0xFF, &[]),
|
||||
];
|
||||
|
||||
const PLUS_SYSCALLS: [Syscalls; 2] = [
|
||||
Syscalls {
|
||||
exec: 29,
|
||||
exec_set_character_name: 11,
|
||||
},
|
||||
Syscalls {
|
||||
exec: 29,
|
||||
exec_set_character_name: 15,
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlusDisasm {
|
||||
stream: MemReader,
|
||||
num_messages: u32,
|
||||
num_special_messages: u32,
|
||||
code_offset: u32,
|
||||
big_endian_addresses: Vec<u32>,
|
||||
little_endian_addresses: Vec<u32>,
|
||||
}
|
||||
|
||||
impl PlusDisasm {
|
||||
pub fn new(mut stream: MemReader) -> Result<Self> {
|
||||
let num_messages = stream.read_u32()?;
|
||||
let num_special_messages = stream.read_u32()?;
|
||||
let code_offset = 8 + (num_messages + num_special_messages) * 4;
|
||||
Ok(Self {
|
||||
stream,
|
||||
num_messages,
|
||||
num_special_messages,
|
||||
code_offset,
|
||||
big_endian_addresses: Vec::new(),
|
||||
little_endian_addresses: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Disasm for PlusDisasm {
|
||||
fn stream(&self) -> &MemReader {
|
||||
&self.stream
|
||||
}
|
||||
fn stream_mut(&mut self) -> &mut MemReader {
|
||||
&mut self.stream
|
||||
}
|
||||
fn opcodes(&self) -> &'static Opcodes {
|
||||
&PLUS_OPCODES
|
||||
}
|
||||
fn operands(&self) -> &'static [(u8, &'static [Oper])] {
|
||||
&PLUS_OPERANDS
|
||||
}
|
||||
fn syscalls(&self) -> &'static [Syscalls] {
|
||||
&PLUS_SYSCALLS
|
||||
}
|
||||
fn code_offset(&self) -> u32 {
|
||||
self.code_offset
|
||||
}
|
||||
fn big_endian_addresses(&self) -> &[u32] {
|
||||
&self.big_endian_addresses
|
||||
}
|
||||
fn push_big_endian_addresses(&mut self, addr: u32) {
|
||||
self.big_endian_addresses.push(addr);
|
||||
}
|
||||
fn little_endian_addresses(&self) -> &[u32] {
|
||||
&self.little_endian_addresses
|
||||
}
|
||||
fn read_header(&mut self) -> Result<()> {
|
||||
for i in 0..self.num_messages + self.num_special_messages {
|
||||
self.little_endian_addresses.push(8 + i * 4);
|
||||
}
|
||||
self.stream.pos = self.code_offset as usize;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const AI6_WIN_OPCODES: Opcodes = Opcodes {
|
||||
r#yield: 0x00,
|
||||
add: 0x34,
|
||||
escape_sequence: 0x1b,
|
||||
message1: 0x0a,
|
||||
message2: 0x0b,
|
||||
push_int: 0x32,
|
||||
push_string: 0x33,
|
||||
syscall: 0x18,
|
||||
line_number: 0xff,
|
||||
nop1: 0xfc,
|
||||
nop2: 0xfd,
|
||||
is_message1_obfuscated: false,
|
||||
};
|
||||
|
||||
const AI6_WIN_OPERANDS: [(u8, &[Oper]); 48] = [
|
||||
(0x00, &[]), // yield
|
||||
(0x01, &[]), // ret
|
||||
(0x02, &[]), // ldglob1.i8
|
||||
(0x03, &[]), // ldglob2.i16
|
||||
(0x04, &[]), // ldglob3.var
|
||||
(0x05, &[]), // ldglob4.var
|
||||
(0x06, &[]), // ldloc.var
|
||||
(0x07, &[]), // ldglob5.i8
|
||||
(0x08, &[]), // ldglob5.i16
|
||||
(0x09, &[]), // ldglob5.i32
|
||||
(0x0A, &[S]), // message
|
||||
(0x0B, &[S]), // message
|
||||
(0x0C, &[]), // stglob1.i8
|
||||
(0x0D, &[]), // stglob2.i16
|
||||
(0x0E, &[]), // stglob3.var
|
||||
(0x0F, &[]), // stglob4.var
|
||||
(0x10, &[]), // stloc.var
|
||||
(0x11, &[]), // stglob5.i8
|
||||
(0x12, &[]), // stglob5.i16
|
||||
(0x13, &[]), // stglob5.i32
|
||||
(0x14, &[A]), // jz
|
||||
(0x15, &[A]), // jmp
|
||||
(0x16, &[A]), // libreg
|
||||
(0x17, &[]), // libcall
|
||||
(0x18, &[]), // syscall
|
||||
(0x19, &[I]), // msgid
|
||||
(0x1A, &[A]), // choice
|
||||
(0x1B, &[B]), // escape sequence
|
||||
(0x32, &[I]), // ldc.i4
|
||||
(0x33, &[S]), // ldstr
|
||||
(0x34, &[]), // add
|
||||
(0x35, &[]), // sub
|
||||
(0x36, &[]), // mul
|
||||
(0x37, &[]), // div
|
||||
(0x38, &[]), // mod
|
||||
(0x39, &[]), // rand
|
||||
(0x3A, &[]), // logand
|
||||
(0x3B, &[]), // logor
|
||||
(0x3C, &[]), // binand
|
||||
(0x3D, &[]), // binor
|
||||
(0x3E, &[]), // lt
|
||||
(0x3F, &[]), // gt
|
||||
(0x40, &[]), // le
|
||||
(0x41, &[]), // ge
|
||||
(0x42, &[]), // eq
|
||||
(0x43, &[]), // neq
|
||||
(0xFE, &[]),
|
||||
(0xFF, &[]),
|
||||
];
|
||||
|
||||
const AI6_WIN_SYSCALLS: [Syscalls; 1] = [Syscalls {
|
||||
exec: 31,
|
||||
exec_set_character_name: 15,
|
||||
}];
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Ai6WinDisasm {
|
||||
stream: MemReader,
|
||||
num_messages: u32,
|
||||
code_offset: u32,
|
||||
big_endian_addresses: Vec<u32>,
|
||||
little_endian_addresses: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Ai6WinDisasm {
|
||||
pub fn new(mut stream: MemReader) -> Result<Self> {
|
||||
let num_messages = stream.read_u32()?;
|
||||
let code_offset = 4 + num_messages * 4;
|
||||
Ok(Self {
|
||||
stream,
|
||||
num_messages,
|
||||
code_offset,
|
||||
big_endian_addresses: Vec::new(),
|
||||
little_endian_addresses: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Disasm for Ai6WinDisasm {
|
||||
fn stream(&self) -> &MemReader {
|
||||
&self.stream
|
||||
}
|
||||
fn stream_mut(&mut self) -> &mut MemReader {
|
||||
&mut self.stream
|
||||
}
|
||||
fn opcodes(&self) -> &'static Opcodes {
|
||||
&AI6_WIN_OPCODES
|
||||
}
|
||||
fn operands(&self) -> &'static [(u8, &'static [Oper])] {
|
||||
&AI6_WIN_OPERANDS
|
||||
}
|
||||
fn syscalls(&self) -> &'static [Syscalls] {
|
||||
&AI6_WIN_SYSCALLS
|
||||
}
|
||||
fn code_offset(&self) -> u32 {
|
||||
self.code_offset
|
||||
}
|
||||
fn big_endian_addresses(&self) -> &[u32] {
|
||||
&self.big_endian_addresses
|
||||
}
|
||||
fn push_big_endian_addresses(&mut self, addr: u32) {
|
||||
self.big_endian_addresses.push(addr);
|
||||
}
|
||||
fn little_endian_addresses(&self) -> &[u32] {
|
||||
&self.little_endian_addresses
|
||||
}
|
||||
fn read_header(&mut self) -> Result<()> {
|
||||
for i in 0..self.num_messages {
|
||||
self.little_endian_addresses.push(4 + i * 4);
|
||||
}
|
||||
self.stream.pos = self.code_offset as usize;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
426
src/scripts/silky/mes.rs
Normal file
426
src/scripts/silky/mes.rs
Normal file
@@ -0,0 +1,426 @@
|
||||
use super::disasm::*;
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::encoding::*;
|
||||
use anyhow::Result;
|
||||
use std::cell::RefCell;
|
||||
use std::io::Write;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Sliky mes script builder
|
||||
pub struct MesBuilder {}
|
||||
|
||||
impl MesBuilder {
|
||||
/// Create a new Sliky mes script builder
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptBuilder for MesBuilder {
|
||||
fn default_encoding(&self) -> Encoding {
|
||||
Encoding::Cp932
|
||||
}
|
||||
|
||||
fn build_script(
|
||||
&self,
|
||||
buf: Vec<u8>,
|
||||
_filename: &str,
|
||||
encoding: Encoding,
|
||||
_archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
_archive: Option<&Box<dyn Script>>,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
Ok(Box::new(Mes::new(buf, encoding, config)?))
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["mes"]
|
||||
}
|
||||
|
||||
fn script_type(&self) -> &'static ScriptType {
|
||||
&ScriptType::Silky
|
||||
}
|
||||
}
|
||||
|
||||
struct TextParser<'a> {
|
||||
data: Vec<&'a str>,
|
||||
typ: SlikyStringType,
|
||||
opcodes: &'static Opcodes,
|
||||
encoding: Encoding,
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> TextParser<'a> {
|
||||
fn new(
|
||||
s: &'a str,
|
||||
typ: SlikyStringType,
|
||||
opcodes: &'static Opcodes,
|
||||
encoding: Encoding,
|
||||
) -> Self {
|
||||
let data = s.graphemes(true).collect();
|
||||
Self {
|
||||
data,
|
||||
typ,
|
||||
opcodes,
|
||||
encoding,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(mut self) -> Result<Vec<u8>> {
|
||||
match self.typ {
|
||||
SlikyStringType::Internal => Err(anyhow::anyhow!(
|
||||
"Internal strings cannot be parsed from text."
|
||||
)),
|
||||
SlikyStringType::Name => {
|
||||
let mut m = MemWriter::new();
|
||||
m.write_u8(self.opcodes.push_string)?;
|
||||
let s = encode_string(self.encoding, &self.data.join(""), false)?;
|
||||
m.write_all(&s)?;
|
||||
m.write_u8(0)?;
|
||||
Ok(m.into_inner())
|
||||
}
|
||||
SlikyStringType::Message => {
|
||||
let mut m = MemWriter::new();
|
||||
let mut in_ruby = false;
|
||||
let mut in_normal_text = false;
|
||||
while let Some(c) = self.next() {
|
||||
match c {
|
||||
"[" => {
|
||||
if in_ruby {
|
||||
return Err(anyhow::anyhow!("Nested ruby tags are not allowed."));
|
||||
}
|
||||
if in_normal_text {
|
||||
m.write_u8(0)?;
|
||||
in_normal_text = false;
|
||||
}
|
||||
in_ruby = true;
|
||||
m.write_u8(self.opcodes.escape_sequence)?;
|
||||
m.write_u8(1)?; // ruby start
|
||||
m.write_u8(self.opcodes.message2)?;
|
||||
}
|
||||
"]" => {
|
||||
if !in_ruby {
|
||||
return Err(anyhow::anyhow!("Unmatched closing ruby tag."));
|
||||
}
|
||||
in_ruby = false;
|
||||
m.write_u8(0)?;
|
||||
m.write_u8(self.opcodes.r#yield)?;
|
||||
}
|
||||
"\n" => {
|
||||
if in_ruby {
|
||||
return Err(anyhow::anyhow!("Newline inside ruby is not allowed."));
|
||||
}
|
||||
if in_normal_text {
|
||||
m.write_u8(0)?;
|
||||
in_normal_text = false;
|
||||
}
|
||||
m.write_u8(self.opcodes.escape_sequence)?;
|
||||
m.write_u8(0)?; // new line
|
||||
}
|
||||
_ => {
|
||||
if !in_ruby && !in_normal_text {
|
||||
in_normal_text = true;
|
||||
m.write_u8(self.opcodes.message2)?;
|
||||
}
|
||||
let s = encode_string(self.encoding, c, false)?;
|
||||
m.write_all(&s)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if in_ruby {
|
||||
m.write_u8(0)?;
|
||||
m.write_u8(self.opcodes.r#yield)?;
|
||||
}
|
||||
if in_normal_text {
|
||||
m.write_u8(0)?;
|
||||
}
|
||||
Ok(m.into_inner())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) -> Option<&'a str> {
|
||||
if self.pos < self.data.len() {
|
||||
let c = self.data[self.pos];
|
||||
self.pos += 1;
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mes {
|
||||
disasm: RefCell<Box<dyn Disasm>>,
|
||||
encoding: Encoding,
|
||||
texts: Vec<SlikyString>,
|
||||
}
|
||||
|
||||
impl Mes {
|
||||
pub fn new(buf: Vec<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
|
||||
let reader = MemReader::new(buf);
|
||||
let num_message = reader.cpeek_u32()?;
|
||||
let code_offset = 4 + num_message as u64 * 4;
|
||||
let first_line_offset = reader.cpeek_u32_at(4)? as u64 + code_offset;
|
||||
let mut disasm: Box<dyn Disasm> = if reader.cpeek_u8_at(first_line_offset)? == 0x19
|
||||
&& reader.cpeek_u32_at(first_line_offset + 1)? == 0
|
||||
{
|
||||
Box::new(Ai6WinDisasm::new(reader)?)
|
||||
} else {
|
||||
Box::new(PlusDisasm::new(reader)?)
|
||||
};
|
||||
disasm.read_header()?;
|
||||
let texts = disasm.read_code()?;
|
||||
Ok(Self {
|
||||
disasm: RefCell::new(disasm),
|
||||
encoding,
|
||||
texts,
|
||||
})
|
||||
}
|
||||
|
||||
fn code_to_text(&self, str: &SlikyString) -> Result<String> {
|
||||
let mut disasm = self.disasm.try_borrow_mut()?;
|
||||
let mut result = String::new();
|
||||
disasm.stream_mut().pos = str.start as usize;
|
||||
let end = str.start as usize + str.len as usize;
|
||||
let opcodes = disasm.opcodes();
|
||||
while disasm.stream().pos < end {
|
||||
let (opcode, operands) = disasm.read_instruction()?;
|
||||
if opcode == opcodes.push_string
|
||||
|| (opcode == opcodes.message1 && !opcodes.is_message1_obfuscated)
|
||||
|| opcode == opcodes.message2
|
||||
{
|
||||
if let Some(Obj::Str(s)) = operands.get(0) {
|
||||
let s = disasm.stream().cpeek_fstring_at(
|
||||
s.start,
|
||||
s.len as usize,
|
||||
self.encoding,
|
||||
true,
|
||||
)?;
|
||||
result.push_str(&s);
|
||||
}
|
||||
} else if opcode == opcodes.message1 && opcodes.is_message1_obfuscated {
|
||||
if let Some(Obj::Str(s)) = operands.get(0) {
|
||||
let mut deobfuscated = vec![0u8; (s.len as usize - 1) * 2];
|
||||
let mut input_idx = 0;
|
||||
let mut output_idx = 0;
|
||||
let tlen = s.len - 1;
|
||||
while input_idx < tlen {
|
||||
let b = disasm.stream().cpeek_u8_at(s.start + input_idx)?;
|
||||
input_idx += 1;
|
||||
if matches!(b, 0x81..0xA0 | 0xE0..0xF0) {
|
||||
deobfuscated[output_idx] = b;
|
||||
output_idx += 1;
|
||||
deobfuscated[output_idx] =
|
||||
disasm.stream().cpeek_u8_at(s.start + input_idx)?;
|
||||
input_idx += 1;
|
||||
output_idx += 1;
|
||||
} else {
|
||||
let c = b as i32 - 0x7D62;
|
||||
deobfuscated[output_idx] = (c >> 8) as u8;
|
||||
output_idx += 1;
|
||||
deobfuscated[output_idx] = (c & 0xFF) as u8;
|
||||
output_idx += 1;
|
||||
}
|
||||
}
|
||||
deobfuscated.truncate(output_idx);
|
||||
let s = decode_to_string(self.encoding, &deobfuscated, true)?;
|
||||
result.push_str(&s);
|
||||
}
|
||||
} else if opcode == opcodes.escape_sequence {
|
||||
if let Some(Obj::Byte(e)) = operands.get(0) {
|
||||
match e {
|
||||
// new line
|
||||
0 => result.push('\n'),
|
||||
// ruby
|
||||
1 => result.push_str("["),
|
||||
_ => {
|
||||
return Err(anyhow::anyhow!("Unknown escape sequence: {}", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if opcode == opcodes.r#yield {
|
||||
result.push_str("]");
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Script for Mes {
|
||||
fn default_output_script_type(&self) -> OutputScriptType {
|
||||
OutputScriptType::Json
|
||||
}
|
||||
|
||||
fn default_format_type(&self) -> FormatOptions {
|
||||
FormatOptions::None
|
||||
}
|
||||
|
||||
fn extract_messages(&self) -> Result<Vec<Message>> {
|
||||
let mut messages = Vec::new();
|
||||
let mut name = None;
|
||||
for t in self.texts.iter() {
|
||||
match t.typ {
|
||||
SlikyStringType::Internal => {}
|
||||
SlikyStringType::Name => {
|
||||
name = Some(self.code_to_text(t)?);
|
||||
}
|
||||
SlikyStringType::Message => {
|
||||
let message = self.code_to_text(t)?;
|
||||
messages.push(Message {
|
||||
name: name.take(),
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
fn import_messages<'a>(
|
||||
&'a self,
|
||||
messages: Vec<Message>,
|
||||
file: Box<dyn WriteSeek + 'a>,
|
||||
_filename: &str,
|
||||
encoding: Encoding,
|
||||
replacement: Option<&'a ReplacementTable>,
|
||||
) -> Result<()> {
|
||||
let opcodes = self.disasm.try_borrow()?.opcodes();
|
||||
let mut inp = self.disasm.try_borrow()?.stream().clone();
|
||||
inp.pos = 0;
|
||||
let mut patcher = BinaryPatcher::new(inp.to_ref(), file, |add| Ok(add), |add| Ok(add))?;
|
||||
let mut mess = messages.iter();
|
||||
let mut mes = mess.next();
|
||||
for text in &self.texts {
|
||||
patcher.copy_up_to(text.start)?;
|
||||
match text.typ {
|
||||
// Ignore internal strings
|
||||
SlikyStringType::Internal => {}
|
||||
SlikyStringType::Name => {
|
||||
let m = match mes {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!("Not enough messages"));
|
||||
}
|
||||
};
|
||||
let mut name = match &m.name {
|
||||
Some(n) => n.to_string(),
|
||||
None => {
|
||||
return Err(anyhow::anyhow!("Message name is missing"));
|
||||
}
|
||||
};
|
||||
if let Some(repl) = replacement {
|
||||
for (k, v) in &repl.map {
|
||||
name = name.replace(k, v);
|
||||
}
|
||||
}
|
||||
let data =
|
||||
TextParser::new(&name, SlikyStringType::Name, opcodes, encoding).parse()?;
|
||||
patcher.replace_bytes(text.len, &data)?;
|
||||
}
|
||||
SlikyStringType::Message => {
|
||||
let m = match mes {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
return Err(anyhow::anyhow!("Not enough messages"));
|
||||
}
|
||||
};
|
||||
let mut message = m.message.to_string();
|
||||
if let Some(repl) = replacement {
|
||||
for (k, v) in &repl.map {
|
||||
message = message.replace(k, v);
|
||||
}
|
||||
}
|
||||
let data =
|
||||
TextParser::new(&message, SlikyStringType::Message, opcodes, encoding)
|
||||
.parse()?;
|
||||
patcher.replace_bytes(text.len, &data)?;
|
||||
mes = mess.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
if mes.is_some() || mess.next().is_some() {
|
||||
return Err(anyhow::anyhow!("Too many messages"));
|
||||
}
|
||||
patcher.copy_up_to(inp.data.len() as u64)?;
|
||||
let code_offset = self.disasm.try_borrow()?.code_offset();
|
||||
for &address_offset in self.disasm.try_borrow()?.little_endian_addresses() {
|
||||
let orig_address = inp.cpeek_u32_at(address_offset as u64)? as u64;
|
||||
let orig_offset = orig_address + code_offset as u64;
|
||||
let new_offset = patcher.map_offset(orig_offset)?;
|
||||
let new_address = new_offset - code_offset as u64;
|
||||
patcher.patch_u32(address_offset as u64, new_address as u32)?;
|
||||
}
|
||||
for &address_offset in self.disasm.try_borrow()?.big_endian_addresses() {
|
||||
let orig_address = inp.cpeek_u32_be_at(address_offset as u64)? as u64;
|
||||
let orig_offset = orig_address + code_offset as u64;
|
||||
let new_offset = patcher.map_offset(orig_offset)?;
|
||||
let new_address = new_offset - code_offset as u64;
|
||||
patcher.patch_u32_be(address_offset as u64, new_address as u32)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_parser() {
|
||||
let opcodes = &PLUS_OPCODES;
|
||||
let parser = TextParser::new(
|
||||
"Hello, [world]s\nThis is a test.",
|
||||
SlikyStringType::Message,
|
||||
opcodes,
|
||||
Encoding::Utf8,
|
||||
);
|
||||
let data = parser.parse().unwrap();
|
||||
assert_eq!(
|
||||
data,
|
||||
vec![
|
||||
opcodes.message2,
|
||||
b'H',
|
||||
b'e',
|
||||
b'l',
|
||||
b'l',
|
||||
b'o',
|
||||
b',',
|
||||
b' ',
|
||||
0,
|
||||
opcodes.escape_sequence,
|
||||
1,
|
||||
opcodes.message2,
|
||||
b'w',
|
||||
b'o',
|
||||
b'r',
|
||||
b'l',
|
||||
b'd',
|
||||
0,
|
||||
opcodes.r#yield,
|
||||
opcodes.message2,
|
||||
b's',
|
||||
0,
|
||||
opcodes.escape_sequence,
|
||||
0,
|
||||
opcodes.message2,
|
||||
b'T',
|
||||
b'h',
|
||||
b'i',
|
||||
b's',
|
||||
b' ',
|
||||
b'i',
|
||||
b's',
|
||||
b' ',
|
||||
b'a',
|
||||
b' ',
|
||||
b't',
|
||||
b'e',
|
||||
b's',
|
||||
b't',
|
||||
b'.',
|
||||
0
|
||||
]
|
||||
);
|
||||
}
|
||||
2
src/scripts/silky/mod.rs
Normal file
2
src/scripts/silky/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod disasm;
|
||||
pub mod mes;
|
||||
Reference in New Issue
Block a user