1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-06-13 03:52:12 +00:00

tsc: third line and non-strict mode

This commit is contained in:
Alula 2020-08-28 01:34:28 +02:00
parent fb5c5d8329
commit c703203506
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
6 changed files with 60 additions and 42 deletions

View file

@ -4,11 +4,11 @@ pub use core::convert::Into;
pub use core::fmt;
#[doc(hidden)]
pub use core::mem::size_of;
use std::io::{Cursor, Error};
use std::cell::RefCell;
use std::io::Cursor;
use byteorder::ReadBytesExt;
use num_traits::Num;
use std::cell::RefCell;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {

View file

@ -1,5 +1,3 @@
use std::rc::Rc;
use imgui::{Condition, im_str, ImStr, ImString, Window};
use itertools::Itertools;
@ -154,10 +152,6 @@ impl LiveDebugger {
let events: Vec<&ImStr> = self.events.iter().map(|e| e.as_ref()).collect();
ui.text_wrapped(&ImString::new(format!("Execution state: {:?}", state.textscript_vm.state)));
let line1: String = state.textscript_vm.line_1.iter().collect();
let line2: String = state.textscript_vm.line_2.iter().collect();
ui.text_wrapped(&ImString::new(&line1));
ui.text_wrapped(&ImString::new(&line2));
ui.push_item_width(-1.0);
ui.list_box(im_str!(""), &mut self.selected_event, &events, 10);

View file

@ -11,7 +11,7 @@ use crate::scene::Scene;
use crate::SharedGameState;
use crate::stage::{BackgroundType, Stage};
use crate::str;
use crate::text_script::{TextScript, TextScriptVM};
use crate::text_script::TextScriptVM;
use crate::ui::Components;
pub struct GameScene {
@ -46,7 +46,7 @@ pub enum Alignment {
impl GameScene {
pub fn new(state: &mut SharedGameState, ctx: &mut Context, id: usize) -> GameResult<Self> {
let stage = Stage::load(ctx, &state.base_path, &state.stages[id])?;
let stage = Stage::load(&state.base_path, &state.stages[id], ctx)?;
info!("Loaded stage: {}", stage.data.name);
info!("Map size: {}x{}", stage.map.width, stage.map.height);
@ -256,6 +256,13 @@ impl GameScene {
if !state.textscript_vm.line_2.is_empty() {
let line2: String = state.textscript_vm.line_2.iter().collect();
Text::new(TextFragment::from(line2.as_str())).draw(ctx, DrawParam::new()
.dest(nalgebra::Point2::new((left_pos + text_offset) * 2.0 + 32.0, (top_pos + 12.0) * 2.0 + 32.0))
.scale(nalgebra::Vector2::new(0.5, 0.5)))?;
}
if !state.textscript_vm.line_3.is_empty() {
let line3: String = state.textscript_vm.line_3.iter().collect();
Text::new(TextFragment::from(line3.as_str())).draw(ctx, DrawParam::new()
.dest(nalgebra::Point2::new((left_pos + text_offset) * 2.0 + 32.0, (top_pos + 24.0) * 2.0 + 32.0))
.scale(nalgebra::Vector2::new(0.5, 0.5)))?;
}
@ -330,13 +337,12 @@ impl GameScene {
impl Scene for GameScene {
fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
state.textscript_vm.set_scene_script(self.stage.text_script.clone());
self.stage.text_script = TextScript::new();
state.textscript_vm.set_scene_script(self.stage.load_text_script(&state.base_path, ctx)?);
state.textscript_vm.suspend = false;
//self.player.equip.set_booster_2_0(true);
state.control_flags.set_flag_x01(true);
state.control_flags.set_control_enabled(true);
//state.control_flags.set_flag_x01(true);
//state.control_flags.set_control_enabled(true);
Ok(())
}

View file

@ -29,8 +29,9 @@ impl Scene for LoadingScene {
let mut next_scene = GameScene::new(state, ctx, 13)?;
next_scene.player.x = 10 * 16 * 0x200;
next_scene.player.y = 8 * 16 * 0x200;
state.next_scene = Some(Box::new(next_scene));
state.textscript_vm.state = TextScriptExecutionState::Running(200, 0);
state.next_scene = Some(Box::new(next_scene));
}
self.tick += 1;

View file

@ -372,24 +372,27 @@ impl StageData {
pub struct Stage {
pub map: Map,
pub data: StageData,
pub text_script: TextScript,
}
impl Stage {
pub fn load(ctx: &mut Context, root: &str, data: &StageData) -> GameResult<Self> {
pub fn load(root: &str, data: &StageData, ctx: &mut Context) -> GameResult<Self> {
let map_file = filesystem::open(ctx, [root, "Stage/", &data.map, ".pxm"].join(""))?;
let tsc_file = filesystem::open(ctx, [root, "Stage/", &data.map, ".tsc"].join(""))?;
let attrib_file = filesystem::open(ctx, [root, "Stage/", &data.tileset.name, ".pxa"].join(""))?;
let map = Map::load_from(map_file, attrib_file)?;
let text_script = TextScript::load_from(tsc_file)?;
let stage = Self {
map,
data: data.clone(),
text_script,
};
Ok(stage)
}
pub fn load_text_script(&mut self, root: &str, ctx: &mut Context) -> GameResult<TextScript> {
let tsc_file = filesystem::open(ctx, [root, "Stage/", &self.data.map, ".tsc"].join(""))?;
let text_script = TextScript::load_from(tsc_file)?;
Ok(text_script)
}
}

View file

@ -183,6 +183,7 @@ pub enum TextScriptEncoding {
pub enum TextScriptLine {
Line1 = 0,
Line2,
Line3,
}
#[derive(Debug, PartialEq, Copy, Clone)]
@ -198,11 +199,16 @@ pub struct TextScriptVM {
pub scripts: TextScriptVMScripts,
pub state: TextScriptExecutionState,
pub flags: TextScriptFlags,
/// Toggle for non-strict TSC parsing because English versions of CS+ (both AG and Nicalis release)
/// modified the events carelessly and since original Pixel's engine hasn't enforced constraints
/// while parsing no one noticed them.
pub strict_mode: bool,
pub suspend: bool,
pub face: u16,
pub current_line: TextScriptLine,
pub line_1: Vec<char>,
pub line_2: Vec<char>,
pub line_3: Vec<char>,
}
impl Default for TextScriptVM {
@ -246,8 +252,8 @@ fn read_cur_varint(cursor: &mut Cursor<&Vec<u8>>) -> GameResult<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) {
let mut result = 0u32;
let mut consumed = 0u32;
let result: u32;
let consumed: u32;
if max_bytes == 0 {
return (0, '\u{fffd}');
@ -282,10 +288,6 @@ fn read_cur_wtf8(cursor: &mut Cursor<&Vec<u8>>, max_bytes: u32) -> (u32, char) {
_ => { 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}'))
}
@ -297,12 +299,14 @@ impl TextScriptVM {
scene_script: TextScript::new(),
},
state: TextScriptExecutionState::Ended,
strict_mode: false,
suspend: true,
flags: TextScriptFlags(0),
face: 0,
current_line: TextScriptLine::Line1,
line_1: Vec::with_capacity(24),
line_2: Vec::with_capacity(24),
line_3: Vec::with_capacity(24),
}
}
@ -327,6 +331,7 @@ impl TextScriptVM {
self.current_line = TextScriptLine::Line1;
self.line_1.clear();
self.line_2.clear();
self.line_3.clear();
}
pub fn start_script(&mut self, event_num: u16) {
@ -369,9 +374,13 @@ impl TextScriptVM {
'\n' if state.textscript_vm.current_line == TextScriptLine::Line1 => {
state.textscript_vm.current_line = TextScriptLine::Line2;
}
'\n' if state.textscript_vm.current_line == TextScriptLine::Line2 => {
state.textscript_vm.current_line = TextScriptLine::Line3;
}
'\n' => {
state.textscript_vm.line_1.clear();
state.textscript_vm.line_1.append(&mut state.textscript_vm.line_2);
state.textscript_vm.line_2.append(&mut state.textscript_vm.line_3);
}
'\r' => {}
_ if state.textscript_vm.current_line == TextScriptLine::Line1 => {
@ -380,6 +389,9 @@ impl TextScriptVM {
_ if state.textscript_vm.current_line == TextScriptLine::Line2 => {
state.textscript_vm.line_2.push(chr);
}
_ if state.textscript_vm.current_line == TextScriptLine::Line3 => {
state.textscript_vm.line_3.push(chr);
}
_ => {}
}
@ -523,6 +535,7 @@ impl TextScriptVM {
state.textscript_vm.current_line = TextScriptLine::Line1;
state.textscript_vm.line_1.clear();
state.textscript_vm.line_2.clear();
state.textscript_vm.line_3.clear();
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
}
@ -531,6 +544,7 @@ impl TextScriptVM {
state.textscript_vm.current_line = TextScriptLine::Line1;
state.textscript_vm.line_1.clear();
state.textscript_vm.line_2.clear();
state.textscript_vm.line_3.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());
@ -661,7 +675,7 @@ impl TextScript {
*byte = byte.wrapping_add(key);
}
TextScript::compile(&buf)
TextScript::compile(&buf, false)
}
pub fn get_event_ids(&self) -> Vec<u16> {
@ -669,7 +683,7 @@ impl TextScript {
}
/// Compiles a decrypted text script data into internal bytecode.
pub fn compile(data: &[u8]) -> GameResult<TextScript> {
pub fn compile(data: &[u8], strict: bool) -> GameResult<TextScript> {
let code = unsafe { std::str::from_utf8_unchecked(data) };
println!("data: {}", code);
@ -686,7 +700,7 @@ impl TextScript {
return Err(ParseError(format!("Event {} has been defined twice.", event_num)));
}
let bytecode = TextScript::compile_event(&mut iter)?;
let bytecode = TextScript::compile_event(&mut iter, strict)?;
log::info!("Successfully compiled event #{} ({} bytes generated).", event_num, bytecode.len());
println!("{:x?}", &bytecode);
event_map.insert(event_num, bytecode);
@ -705,7 +719,7 @@ impl TextScript {
})
}
fn compile_event<I: Iterator<Item=u8>>(iter: &mut Peekable<I>) -> GameResult<Vec<u8>> {
fn compile_event<I: Iterator<Item=u8>>(iter: &mut Peekable<I>, strict: bool) -> GameResult<Vec<u8>> {
let mut bytecode = Vec::new();
let mut char_buf = Vec::with_capacity(16);
@ -732,7 +746,7 @@ impl TextScript {
let code = unsafe { std::str::from_utf8_unchecked(&n) };
TextScript::compile_code(code, iter, &mut bytecode)?;
TextScript::compile_code(code, strict, iter, &mut bytecode)?;
}
_ => {
char_buf.push(chr);
@ -777,7 +791,7 @@ impl TextScript {
Ok(((result << 31) ^ (result >> 1)) as i32)
}
fn compile_code<I: Iterator<Item=u8>>(code: &str, iter: &mut Peekable<I>, out: &mut Vec<u8>) -> GameResult {
fn compile_code<I: Iterator<Item=u8>>(code: &str, strict: bool, iter: &mut Peekable<I>, out: &mut Vec<u8>) -> GameResult {
let instr = OpCode::from_str(code).map_err(|_| ParseError(format!("Unknown opcode: {}", code)))?;
match instr {
@ -806,7 +820,7 @@ impl TextScript {
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)?;
if strict { TextScript::expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; }
let operand_b = TextScript::read_number(iter)?;
TextScript::put_varint(instr as i32, out);
@ -816,9 +830,9 @@ impl TextScript {
// 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)?;
if strict { TextScript::expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; }
let operand_b = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
if strict { TextScript::expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; }
let operand_c = TextScript::read_number(iter)?;
TextScript::put_varint(instr as i32, out);
@ -829,11 +843,11 @@ impl TextScript {
// Four operand codes
OpCode::TRA | OpCode::MNP | OpCode::SNP => {
let operand_a = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
if strict { TextScript::expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; }
let operand_b = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
if strict { TextScript::expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; }
let operand_c = TextScript::read_number(iter)?;
TextScript::expect_char(b':', iter)?;
if strict { TextScript::expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))?; }
let operand_d = TextScript::read_number(iter)?;
TextScript::put_varint(instr as i32, out);
@ -889,10 +903,10 @@ impl TextScript {
/// Intentionally does no '0'..'9' range checking, since it was often exploited by modders.
fn read_number<I: Iterator<Item=u8>>(iter: &mut Peekable<I>) -> GameResult<i32> {
Some(0)
.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))
.and_then(|result| iter.next().map(|v| result + 1000 * v.wrapping_sub(b'0') as i32))
.and_then(|result| iter.next().map(|v| result + 100 * v.wrapping_sub(b'0') as i32))
.and_then(|result| iter.next().map(|v| result + 10 * v.wrapping_sub(b'0') as i32))
.and_then(|result| iter.next().map(|v| result + v.wrapping_sub(b'0') as i32))
.ok_or_else(|| ParseError(str!("Script unexpectedly ended.")))
}