Add silky engine mes support

This commit is contained in:
2025-09-05 20:35:54 +08:00
parent 4f862fd2a7
commit 900678f89a
8 changed files with 1000 additions and 1 deletions

View File

@@ -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
View 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
View 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
View File

@@ -0,0 +1,2 @@
mod disasm;
pub mod mes;