doukutsu-rs/src/text_script.rs

882 lines
31 KiB
Rust
Raw Normal View History

2020-08-27 02:43:21 +00:00
use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::collections::HashMap;
2020-08-23 02:17:45 +00:00
use std::io;
2020-08-27 02:43:21 +00:00
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::iter::Peekable;
use std::str::FromStr;
2020-08-18 16:46:07 +00:00
2020-08-27 02:43:21 +00:00
use byteorder::ReadBytesExt;
use itertools::Itertools;
2020-08-27 02:43:21 +00:00
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
2020-08-27 02:43:21 +00:00
use crate::{SharedGameState, str};
use crate::bitfield;
use crate::ggez::GameError::ParseError;
2020-08-23 02:17:45 +00:00
use crate::ggez::GameResult;
2020-08-27 02:43:21 +00:00
use crate::scene::game_scene::GameScene;
2020-08-18 16:46:07 +00:00
/// Engine's text script VM operation codes.
2020-08-27 02:43:21 +00:00
#[derive(EnumString, Debug, FromPrimitive, PartialEq)]
#[repr(i32)]
2020-08-23 02:17:45 +00:00
pub enum OpCode {
// ---- Internal opcodes (used by bytecode, no TSC representation)
/// internal: no operation
2020-08-27 02:43:21 +00:00
_NOP = 0,
/// internal: unimplemented
_UNI,
2020-08-27 02:43:21 +00:00
/// internal: string marker
_STR,
/// internal: implicit END marker
_END,
2020-08-23 02:17:45 +00:00
// ---- Official opcodes ----
/// <BOAxxxx, start boss animation
BOA,
/// <BSLxxxx, start boss fight
BSL,
/// <FOBxxxx, Focus on boss
FOB,
FOM,
FON,
FLA,
QUA,
UNI,
HMC,
SMC,
MM0,
MOV,
MYB,
MYD,
TRA,
END,
FRE,
FAI,
FAO,
WAI,
WAS,
KEY,
PRI,
NOD,
CAT,
SAT,
TUR,
CLO,
CLR,
FAC,
GIT,
MS2,
MS3,
MSG,
NUM,
ANP,
CNP,
INP,
MNP,
DNA,
DNP,
SNP,
#[strum(serialize = "FL-")]
2020-08-23 02:17:45 +00:00
FLm,
#[strum(serialize = "FL+")]
2020-08-23 02:17:45 +00:00
FLp,
#[strum(serialize = "MP+")]
2020-08-23 02:17:45 +00:00
MPp,
#[strum(serialize = "SK-")]
2020-08-23 02:17:45 +00:00
SKm,
#[strum(serialize = "SK+")]
2020-08-23 02:17:45 +00:00
SKp,
#[strum(serialize = "EQ+")]
2020-08-23 02:17:45 +00:00
EQp,
#[strum(serialize = "EQ-")]
2020-08-23 02:17:45 +00:00
EQm,
#[strum(serialize = "ML+")]
2020-08-23 02:17:45 +00:00
MLp,
#[strum(serialize = "IT+")]
2020-08-23 02:17:45 +00:00
ITp,
#[strum(serialize = "IT-")]
2020-08-23 02:17:45 +00:00
ITm,
#[strum(serialize = "AM+")]
2020-08-23 02:17:45 +00:00
AMp,
#[strum(serialize = "AM-")]
2020-08-23 02:17:45 +00:00
AMm,
TAM,
UNJ,
NCJ,
ECJ,
FLJ,
ITJ,
MPJ,
YNJ,
SKJ,
EVE,
AMJ,
MLP,
MNA,
CMP,
SMP,
CRE,
XX1,
CIL,
SIL,
ESC,
INI,
LDP,
#[strum(serialize = "PS+")]
2020-08-23 02:17:45 +00:00
PSp,
SLP,
ZAM,
#[strum(serialize = "AE+")]
2020-08-23 02:17:45 +00:00
AEp,
#[strum(serialize = "LI+")]
2020-08-23 02:17:45 +00:00
LIp,
SVP,
STC,
SOU,
CMU,
FMU,
RMU,
CPS,
SPS,
CSS,
SSS,
// ---- Cave Story+ specific opcodes ----
/// <ACHXXXX, triggers a Steam achievement.
ACH,
2020-08-23 02:17:45 +00:00
// ---- Custom opcodes, for use by modders ----
2020-08-18 16:46:07 +00:00
}
bitfield! {
pub struct TextScriptFlags(u16);
impl Debug;
pub render, set_render: 0;
pub background_visible, set_background_visible: 1;
pub flag_x10, set_flag_x10: 4;
pub position_top, set_position_top: 5;
pub flag_x40, set_flag_x40: 6;
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
pub enum TextScriptEncoding {
UTF8 = 0,
ShiftJIS,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(u8)]
pub enum TextScriptLine {
Line1 = 0,
Line2,
}
2020-08-27 02:43:21 +00:00
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum TextScriptExecutionState {
Ended,
2020-08-27 02:43:21 +00:00
Running(u16, u32),
Msg(u16, u32, u32, u8),
2020-08-27 02:43:21 +00:00
WaitTicks(u16, u32, u32),
WaitInput(u16, u32),
}
2020-08-27 02:43:21 +00:00
pub struct TextScriptVM {
pub scripts: TextScriptVMScripts,
2020-08-27 02:43:21 +00:00
pub state: TextScriptExecutionState,
pub flags: TextScriptFlags,
pub face: u16,
pub current_line: TextScriptLine,
pub line_1: Vec<char>,
pub line_2: Vec<char>,
2020-08-27 02:43:21 +00:00
}
impl Default for TextScriptVM {
fn default() -> Self {
TextScriptVM::new()
}
}
pub struct TextScriptVMScripts {
pub global_script: TextScript,
pub scene_script: TextScript,
}
impl TextScriptVMScripts {
pub fn find_script(&self, event_num: u16) -> Option<&Vec<u8>> {
if let Some(tsc) = self.scene_script.event_map.get(&event_num) {
return Some(tsc);
} else if let Some(tsc) = self.global_script.event_map.get(&event_num) {
return Some(tsc);
}
None
}
}
2020-08-27 02:43:21 +00:00
fn read_cur_varint(cursor: &mut Cursor<&Vec<u8>>) -> GameResult<i32> {
let mut result = 0u32;
for o in 0..5 {
let n = cursor.read_u8()?;
result |= (n as u32 & 0x7f) << (o * 7);
if n & 0x80 == 0 {
break;
}
}
Ok(((result << 31) ^ (result >> 1)) as i32)
}
/// Decodes UTF-8 character in a less strict way.
/// http://simonsapin.github.io/wtf-8/#decoding-wtf-8
fn read_cur_wtf8(cursor: &mut Cursor<&Vec<u8>>, max_bytes: u32) -> (u32, char) {
2020-08-27 02:43:21 +00:00
let mut result = 0u32;
let mut consumed = 0u32;
2020-08-27 02:43:21 +00:00
if max_bytes == 0 {
return (0, '\u{fffd}');
}
match cursor.read_u8() {
Ok(byte @ 0x00..=0x7f) => {
consumed = 1;
result = byte as u32;
}
Ok(byte @ 0xc2..=0xdf) if max_bytes >= 2 => {
let byte2 = { if let Ok(n) = cursor.read_u8() { n } else { return (1, '\u{fffd}'); } };
consumed = 2;
result = (byte as u32 & 0x1f) << 6 | (byte2 as u32 & 0x3f);
}
Ok(byte @ 0xe0..=0xef) if max_bytes >= 3 => {
let byte2 = { if let Ok(n) = cursor.read_u8() { n } else { return (1, '\u{fffd}'); } };
let byte3 = { if let Ok(n) = cursor.read_u8() { n } else { return (2, '\u{fffd}'); } };
consumed = 3;
result = (byte as u32 & 0x0f) << 12 | (byte2 as u32 & 0x3f) << 6 | (byte3 as u32 & 0x3f);
}
Ok(byte @ 0xf0..=0xf4) if max_bytes >= 4 => {
let byte2 = { if let Ok(n) = cursor.read_u8() { n } else { return (1, '\u{fffd}'); } };
let byte3 = { if let Ok(n) = cursor.read_u8() { n } else { return (2, '\u{fffd}'); } };
let byte4 = { if let Ok(n) = cursor.read_u8() { n } else { return (3, '\u{fffd}'); } };
consumed = 4;
result = (byte as u32 & 0x07) << 18 | (byte2 as u32 & 0x3f) << 12 | (byte3 as u32 & 0x3f) << 6 | (byte4 as u32 & 0x3f);
}
_ => { return (1, '\u{fffd}'); }
}
if let Ok(byte) = cursor.read_u8() {} else {
return (consumed, '\u{fffd}');
}
(consumed, std::char::from_u32(result).unwrap_or('\u{fffd}'))
}
impl TextScriptVM {
pub fn new() -> Self {
Self {
scripts: TextScriptVMScripts {
global_script: TextScript::new(),
scene_script: TextScript::new(),
},
2020-08-27 02:43:21 +00:00
state: TextScriptExecutionState::Ended,
flags: TextScriptFlags(0),
2020-08-27 02:43:21 +00:00
face: 0,
current_line: TextScriptLine::Line1,
2020-08-27 02:43:21 +00:00
line_1: Vec::with_capacity(24),
line_2: Vec::with_capacity(24),
}
}
pub fn set_global_script(&mut self, script: TextScript) {
self.scripts.global_script = script;
2020-08-27 02:43:21 +00:00
self.reset();
}
pub fn set_scene_script(&mut self, script: TextScript) {
self.scripts.scene_script = script;
2020-08-27 02:43:21 +00:00
self.reset();
}
pub fn reset(&mut self) {
self.state = TextScriptExecutionState::Ended;
self.clear_text_box();
}
pub fn clear_text_box(&mut self) {
self.flags.0 = 0;
2020-08-27 02:43:21 +00:00
self.face = 0;
self.current_line = TextScriptLine::Line1;
2020-08-27 02:43:21 +00:00
self.line_1.clear();
self.line_2.clear();
}
pub fn start_script(&mut self, event_num: u16) {
self.reset();
self.state = TextScriptExecutionState::Running(event_num, 0);
log::info!("Started script: #{:04}", event_num);
}
pub fn run(state: &mut SharedGameState, game_scene: &mut GameScene) -> GameResult {
loop {
match state.textscript_vm.state {
TextScriptExecutionState::Ended => {
state.control_flags.set_flag_x04(false);
2020-08-27 02:43:21 +00:00
break;
}
TextScriptExecutionState::Running(event, ip) => {
state.control_flags.set_flag_x04(true);
2020-08-27 02:43:21 +00:00
state.textscript_vm.state = TextScriptVM::execute(event, ip, state, game_scene)?;
if state.textscript_vm.state == TextScriptExecutionState::Ended {
state.textscript_vm.reset();
}
2020-08-27 02:43:21 +00:00
}
TextScriptExecutionState::Msg(event, ip, remaining, timer) => {
if timer > 0 {
state.textscript_vm.state = TextScriptExecutionState::Msg(event, ip, remaining, timer - 1);
break;
}
if let Some(bytecode) = state.textscript_vm.scripts.find_script(event) {
let mut cursor = Cursor::new(bytecode);
cursor.seek(SeekFrom::Start(ip as u64))?;
let (consumed, chr) = read_cur_wtf8(&mut cursor, remaining);
println!("char: {} {} {}", chr, remaining, consumed);
match chr {
'\n' => {
state.textscript_vm.line_1.clear();
state.textscript_vm.line_2.append(&mut state.textscript_vm.line_1);
}
'\r' => {}
_ if state.textscript_vm.current_line == TextScriptLine::Line1 => {
state.textscript_vm.line_1.push(chr);
}
_ if state.textscript_vm.current_line == TextScriptLine::Line2 => {
state.textscript_vm.line_2.push(chr);
}
_ => {}
}
if (remaining - consumed) > 0 {
let ticks = if state.key_state.jump() || state.key_state.fire() { 1 } else { 4 };
state.textscript_vm.state = TextScriptExecutionState::Msg(event, ip + consumed, remaining - consumed, ticks);
} else {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip + consumed);
}
} else {
2020-08-27 02:43:21 +00:00
state.textscript_vm.reset();
}
}
TextScriptExecutionState::WaitTicks(event, ip, ticks) => {
if ticks == 0 {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
} else {
state.textscript_vm.state = TextScriptExecutionState::WaitTicks(event, ip, ticks - 1);
break;
}
}
TextScriptExecutionState::WaitInput(event, ip) => {
if state.key_trigger.jump() || state.key_trigger.fire() {
state.textscript_vm.state = TextScriptExecutionState::Running(event, ip);
}
break;
}
}
}
Ok(())
}
pub fn execute(event: u16, ip: u32, state: &mut SharedGameState, game_scene: &mut GameScene) -> GameResult<TextScriptExecutionState> {
let mut exec_state = state.textscript_vm.state;
if let Some(bytecode) = state.textscript_vm.scripts.find_script(event) {
2020-08-27 02:43:21 +00:00
let mut cursor = Cursor::new(bytecode);
cursor.seek(SeekFrom::Start(ip as u64))?;
let op_maybe: Option<OpCode> = FromPrimitive::from_i32(read_cur_varint(&mut cursor)?);
if let Some(op) = op_maybe {
println!("opcode: {:?}", op);
2020-08-27 02:43:21 +00:00
match op {
OpCode::_NOP => {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::_UNI => {}
OpCode::_STR => {
let len = read_cur_varint(&mut cursor)? as u32;
if state.textscript_vm.flags.render() {
exec_state = TextScriptExecutionState::Msg(event, cursor.position() as u32, len, 4);
} else {
// simply skip the text if we aren't in message mode.
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32 + len);
}
2020-08-27 02:43:21 +00:00
}
OpCode::_END | OpCode::END => {
state.control_flags.set_flag_x01(true);
state.control_flags.set_control_enabled(true);
2020-08-27 02:43:21 +00:00
exec_state = TextScriptExecutionState::Ended;
}
OpCode::PRI => {
state.control_flags.set_flag_x01(false);
state.control_flags.set_control_enabled(false);
2020-08-27 02:43:21 +00:00
game_scene.player.shock_counter = 0;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::KEY => {
state.control_flags.set_flag_x01(true);
state.control_flags.set_control_enabled(false);
game_scene.player.up = false;
game_scene.player.down = false;
game_scene.player.shock_counter = 0;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::FRE => {
state.control_flags.set_flag_x01(true);
state.control_flags.set_control_enabled(true);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::WAI => {
let ticks = read_cur_varint(&mut cursor)? as u32;
exec_state = TextScriptExecutionState::WaitTicks(event, cursor.position() as u32, ticks);
}
2020-08-27 02:43:21 +00:00
OpCode::NOD => {
exec_state = TextScriptExecutionState::WaitInput(event, cursor.position() as u32);
}
OpCode::FLp | OpCode::FLm => {
let flag_num = read_cur_varint(&mut cursor)? as usize;
state.game_flags.set(flag_num, op == OpCode::FLp);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::FLJ => {
let flag_num = read_cur_varint(&mut cursor)? as usize;
let event_num = read_cur_varint(&mut cursor)? as u16;
if let Some(true) = state.game_flags.get(flag_num) {
exec_state = TextScriptExecutionState::Running(event_num, 0);
} else {
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
2020-08-27 02:43:21 +00:00
}
}
OpCode::EVE => {
let event_num = read_cur_varint(&mut cursor)? as u16;
exec_state = TextScriptExecutionState::Running(event_num, 0);
}
OpCode::MM0 => {
game_scene.player.vel_x = 0;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
2020-08-27 02:43:21 +00:00
}
OpCode::CMP => {
let pos_x = read_cur_varint(&mut cursor)? as usize;
let pos_y = read_cur_varint(&mut cursor)? as usize;
let tile_type = read_cur_varint(&mut cursor)? as u8;
if let Some(ptr) = game_scene.stage.map.tiles.get_mut(pos_y * game_scene.stage.map.width + pos_x) {
*ptr = tile_type;
}
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::MLp => {
let life = read_cur_varint(&mut cursor)? as usize;
game_scene.player.max_life += life;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::FAC => {
let face = read_cur_varint(&mut cursor)? as u16;
state.textscript_vm.face = face;
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
OpCode::MSG => {
state.textscript_vm.face = 0;
state.textscript_vm.current_line = TextScriptLine::Line1;
state.textscript_vm.line_1.clear();
state.textscript_vm.line_2.clear();
state.textscript_vm.flags.set_render(true);
state.textscript_vm.flags.set_background_visible(true);
state.textscript_vm.flags.set_flag_x10(state.textscript_vm.flags.flag_x40());
state.textscript_vm.flags.set_position_top(false);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
2020-08-27 02:43:21 +00:00
// unimplemented opcodes
// Zero operands
OpCode::AEp | OpCode::CAT | OpCode::CIL | OpCode::CLO | OpCode::CLR | OpCode::CPS |
OpCode::CRE | OpCode::CSS | OpCode::ESC | OpCode::FLA | OpCode::FMU |
OpCode::HMC | OpCode::INI | OpCode::LDP | OpCode::MLP |
OpCode::MNA | OpCode::MS2 | OpCode::MS3 |
OpCode::RMU | OpCode::SAT | OpCode::SLP | OpCode::SMC | OpCode::SPS |
2020-08-27 02:43:21 +00:00
OpCode::STC | OpCode::SVP | OpCode::TUR | OpCode::WAS | OpCode::ZAM => {
println!("unimplemented opcode: {:?}", op);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
// One operand codes
OpCode::BOA | OpCode::BSL | OpCode::FOB | OpCode::FOM | OpCode::QUA | OpCode::UNI |
OpCode::MYB | OpCode::MYD | OpCode::FAI | OpCode::FAO | OpCode::FAC |
2020-08-27 02:43:21 +00:00
OpCode::GIT | OpCode::NUM | OpCode::DNA | OpCode::DNP |
OpCode::MPp | OpCode::SKm | OpCode::SKp | OpCode::EQp | OpCode::EQm |
2020-08-27 02:43:21 +00:00
OpCode::ITp | OpCode::ITm | OpCode::AMm | OpCode::UNJ | OpCode::MPJ | OpCode::YNJ |
OpCode::XX1 | OpCode::SIL | OpCode::LIp | OpCode::SOU | OpCode::CMU |
OpCode::SSS | OpCode::ACH => {
let par_a = read_cur_varint(&mut cursor)?;
println!("unimplemented opcode: {:?} {}", op, par_a);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
// Two operand codes
OpCode::FON | OpCode::MOV | OpCode::AMp | OpCode::NCJ | OpCode::ECJ |
OpCode::ITJ | OpCode::SKJ | OpCode::AMJ | OpCode::SMP | OpCode::PSp => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
println!("unimplemented opcode: {:?} {} {}", op, par_a, par_b);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
// Three operand codes
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
let par_c = read_cur_varint(&mut cursor)?;
println!("unimplemented opcode: {:?} {} {} {}", op, par_a, par_b, par_c);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
// Four operand codes
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
let par_a = read_cur_varint(&mut cursor)?;
let par_b = read_cur_varint(&mut cursor)?;
let par_c = read_cur_varint(&mut cursor)?;
let par_d = read_cur_varint(&mut cursor)?;
println!("unimplemented opcode: {:?} {} {} {} {}", op, par_a, par_b, par_c, par_d);
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
}
} else {
exec_state = TextScriptExecutionState::Ended;
2020-08-27 02:43:21 +00:00
}
} else {
return Ok(TextScriptExecutionState::Ended);
}
Ok(exec_state)
}
}
pub struct TextScript {
event_map: HashMap<u16, Vec<u8>>,
}
2020-08-23 02:17:45 +00:00
2020-08-27 02:43:21 +00:00
impl Clone for TextScript {
fn clone(&self) -> Self {
Self {
event_map: self.event_map.clone(),
}
}
}
impl Default for TextScript {
fn default() -> Self {
TextScript::new()
}
}
2020-08-18 16:46:07 +00:00
impl TextScript {
2020-08-27 02:43:21 +00:00
pub fn new() -> TextScript {
Self {
event_map: HashMap::new(),
}
}
2020-08-23 02:17:45 +00:00
/// Loads, decrypts and compiles a text script from specified stream.
pub fn load_from<R: io::Read>(mut data: R) -> GameResult<TextScript> {
let mut buf = Vec::new();
data.read_to_end(&mut buf)?;
let half = buf.len() / 2;
let key = if let Some(0) = buf.get(half) {
0xf9
} else {
(-(*buf.get(half).unwrap() as isize)) as u8
};
for (idx, byte) in buf.iter_mut().enumerate() {
if idx == half {
continue;
}
*byte = byte.wrapping_add(key);
}
TextScript::compile(&buf)
}
2020-08-27 02:43:21 +00:00
pub fn get_event_ids(&self) -> Vec<u16> {
self.event_map.keys().copied().sorted().collect_vec()
}
2020-08-23 02:17:45 +00:00
/// Compiles a decrypted text script data into internal bytecode.
pub fn compile(data: &[u8]) -> GameResult<TextScript> {
2020-08-27 02:43:21 +00:00
let code = unsafe { std::str::from_utf8_unchecked(data) };
println!("data: {}", code);
2020-08-23 02:17:45 +00:00
let mut event_map = HashMap::new();
2020-08-27 02:43:21 +00:00
let mut iter = data.iter().copied().peekable();
while let Some(&chr) = iter.peek() {
match chr {
b'#' => {
iter.next();
let event_num = TextScript::read_number(&mut iter)? as u16;
TextScript::skip_until(b'\n', &mut iter)?;
if event_map.contains_key(&event_num) {
return Err(ParseError(format!("Event {} has been defined twice.", event_num)));
}
let bytecode = TextScript::compile_event(&mut iter)?;
log::info!("Successfully compiled event #{} ({} bytes generated).", event_num, bytecode.len());
println!("{:x?}", &bytecode);
event_map.insert(event_num, bytecode);
}
b'\r' | b'\n' | b' ' => {
iter.next();
}
n => {
return Err(ParseError(format!("Unexpected token: {}", n as char)));
}
}
}
Ok(TextScript {
event_map
})
}
2020-08-27 02:43:21 +00:00
fn compile_event<I: Iterator<Item=u8>>(iter: &mut Peekable<I>) -> GameResult<Vec<u8>> {
let mut bytecode = Vec::new();
let mut char_buf = Vec::with_capacity(16);
2020-08-27 02:43:21 +00:00
while let Some(&chr) = iter.peek() {
match chr {
b'#' => {
if !char_buf.is_empty() {
2020-08-27 02:43:21 +00:00
TextScript::put_string(&mut char_buf, &mut bytecode);
}
// some events end without <END marker.
2020-08-27 02:43:21 +00:00
TextScript::put_varint(OpCode::_END as i32, &mut bytecode);
break;
}
b'<' => {
if !char_buf.is_empty() {
2020-08-27 02:43:21 +00:00
TextScript::put_string(&mut char_buf, &mut bytecode);
}
iter.next();
2020-08-27 02:43:21 +00:00
let n = iter.next_tuple::<(u8, u8, u8)>()
.map(|t| [t.0, t.1, t.2])
.ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
let code = unsafe { std::str::from_utf8_unchecked(&n) };
TextScript::compile_code(code, iter, &mut bytecode)?;
}
_ => {
char_buf.push(chr);
iter.next();
}
}
}
Ok(bytecode)
}
2020-08-27 02:43:21 +00:00
fn put_string(buffer: &mut Vec<u8>, out: &mut Vec<u8>) {
TextScript::put_varint(OpCode::_STR as i32, out);
TextScript::put_varint(buffer.len() as i32, out);
out.append(buffer);
}
fn put_varint(val: i32, out: &mut Vec<u8>) {
let mut x = ((val as u32) >> 31) ^ ((val as u32) << 1);
while x > 0x80 {
out.push((x & 0x7f) as u8 | 0x80);
x >>= 7;
}
out.push(x as u8);
}
2020-08-27 02:43:21 +00:00
fn read_varint<I: Iterator<Item=u8>>(iter: &mut I) -> GameResult<i32> {
let mut result = 0u32;
for o in 0..5 {
2020-08-27 02:43:21 +00:00
let n = iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?;
result |= (n as u32 & 0x7f) << (o * 7);
if n & 0x80 == 0 {
break;
}
}
Ok(((result << 31) ^ (result >> 1)) as i32)
}
2020-08-27 02:43:21 +00:00
fn compile_code<I: Iterator<Item=u8>>(code: &str, iter: &mut Peekable<I>, out: &mut Vec<u8>) -> GameResult {
let instr = OpCode::from_str(code).map_err(|_| ParseError(format!("Unknown opcode: {}", code)))?;
match instr {
// Zero operand codes
OpCode::AEp | OpCode::CAT | OpCode::CIL | OpCode::CLO | OpCode::CLR | OpCode::CPS |
OpCode::CRE | OpCode::CSS | OpCode::END | OpCode::ESC | OpCode::FLA | OpCode::FMU |
OpCode::FRE | OpCode::HMC | OpCode::INI | OpCode::KEY | OpCode::LDP | OpCode::MLP |
OpCode::MM0 | OpCode::MNA | OpCode::MS2 | OpCode::MS3 | OpCode::MSG | OpCode::NOD |
OpCode::PRI | OpCode::RMU | OpCode::SAT | OpCode::SLP | OpCode::SMC | OpCode::SPS |
OpCode::STC | OpCode::SVP | OpCode::TUR | OpCode::WAS | OpCode::ZAM => {
TextScript::put_varint(instr as i32, out);
}
// One operand codes
OpCode::BOA | OpCode::BSL | OpCode::FOB | OpCode::FOM | OpCode::QUA | OpCode::UNI |
OpCode::MYB | OpCode::MYD | OpCode::FAI | OpCode::FAO | OpCode::WAI | OpCode::FAC |
OpCode::GIT | OpCode::NUM | OpCode::DNA | OpCode::DNP | OpCode::FLm | OpCode::FLp |
OpCode::MPp | OpCode::SKm | OpCode::SKp | OpCode::EQp | OpCode::EQm | OpCode::MLp |
OpCode::ITp | OpCode::ITm | OpCode::AMm | OpCode::UNJ | OpCode::MPJ | OpCode::YNJ |
OpCode::EVE | OpCode::XX1 | OpCode::SIL | OpCode::LIp | OpCode::SOU | OpCode::CMU |
OpCode::SSS | OpCode::ACH => {
let operand = TextScript::read_number(iter)?;
TextScript::put_varint(instr as i32, out);
TextScript::put_varint(operand as i32, out);
}
// Two operand codes
OpCode::FON | OpCode::MOV | OpCode::AMp | OpCode::NCJ | OpCode::ECJ | OpCode::FLJ |
OpCode::ITJ | OpCode::SKJ | OpCode::AMJ | OpCode::SMP | OpCode::PSp => {
let operand_a = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
let operand_b = TextScript::read_number(iter)?;
TextScript::put_varint(instr as i32, out);
TextScript::put_varint(operand_a as i32, out);
TextScript::put_varint(operand_b as i32, out);
}
// Three operand codes
OpCode::ANP | OpCode::CNP | OpCode::INP | OpCode::TAM | OpCode::CMP => {
let operand_a = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
let operand_b = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
let operand_c = TextScript::read_number(iter)?;
TextScript::put_varint(instr as i32, out);
TextScript::put_varint(operand_a as i32, out);
TextScript::put_varint(operand_b as i32, out);
TextScript::put_varint(operand_c as i32, out);
}
// Four operand codes
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
let operand_a = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
let operand_b = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
let operand_c = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
let operand_d = TextScript::read_number(iter)?;
TextScript::put_varint(instr as i32, out);
TextScript::put_varint(operand_a as i32, out);
TextScript::put_varint(operand_b as i32, out);
TextScript::put_varint(operand_c as i32, out);
TextScript::put_varint(operand_d as i32, out);
}
_ => {
TextScript::put_varint(OpCode::_UNI as i32, out);
log::warn!("Unimplemented opcode: {:?}", instr);
}
}
Ok(())
}
2020-08-27 02:43:21 +00:00
fn expect_newline<I: Iterator<Item=u8>>(iter: &mut Peekable<I>) -> GameResult {
if let Some(b'\r') = iter.peek() {
iter.next();
}
TextScript::expect_char(b'\n', iter)
}
2020-08-27 02:43:21 +00:00
fn expect_char<I: Iterator<Item=u8>>(expect: u8, iter: &mut I) -> GameResult {
let res = iter.next();
match res {
2020-08-27 02:43:21 +00:00
Some(n) if n == expect => {
Ok(())
}
2020-08-27 02:43:21 +00:00
Some(n) => {
Err(ParseError(format!("Expected {}, found {}", expect as char, n as char)))
}
None => {
Err(ParseError(str!("Script unexpectedly ended.")))
}
}
}
2020-08-27 02:43:21 +00:00
fn skip_until<I: Iterator<Item=u8>>(expect: u8, iter: &mut I) -> GameResult {
for chr in iter {
if chr == expect {
return Ok(());
}
}
Err(ParseError(str!("Script unexpectedly ended.")))
}
/// Reads a 4 digit TSC formatted number from iterator.
/// Intentionally does no '0'..'9' range checking, since it was often exploited by modders.
2020-08-27 02:43:21 +00:00
fn read_number<I: Iterator<Item=u8>>(iter: &mut Peekable<I>) -> GameResult<i32> {
Some(0)
2020-08-27 02:43:21 +00:00
.and_then(|result| iter.next().map(|v| result + 1000 * (v - b'0') as i32))
.and_then(|result| iter.next().map(|v| result + 100 * (v - b'0') as i32))
.and_then(|result| iter.next().map(|v| result + 10 * (v - b'0') as i32))
.and_then(|result| iter.next().map(|v| result + (v - b'0') as i32))
.ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))
}
pub fn has_event(&self, id: u16) -> bool {
self.event_map.contains_key(&id)
}
}
#[test]
fn test_varint() {
for &n in [1_i32, 23, 456, 7890, 12345, -1, -23, -456].iter() {
let mut out = Vec::new();
TextScript::put_varint(n, &mut out);
2020-08-27 02:43:21 +00:00
let result = TextScript::read_varint(&mut out.iter().copied()).unwrap();
assert_eq!(result, n);
2020-08-18 16:46:07 +00:00
}
}