use std::collections::HashMap; use std::iter::Peekable; use std::str::FromStr; use itertools::Itertools; use crate::framework::error::GameError::ParseError; use crate::framework::error::GameResult; use crate::scripting::tsc::bytecode_utils::{put_string, put_varint}; use crate::scripting::tsc::credit_script::CreditScript; use crate::scripting::tsc::opcodes::{CreditOpCode, TSCOpCode}; use crate::scripting::tsc::parse_utils::{expect_char, read_number, skip_until}; use crate::scripting::tsc::text_script::{TextScript, TextScriptEncoding}; impl TextScript { /// Compiles a decrypted text script data into internal bytecode. pub fn compile(data: &[u8], strict: bool, encoding: TextScriptEncoding) -> GameResult { let mut event_map = HashMap::new(); let mut iter = data.iter().copied().peekable(); let mut last_event = 0; while let Some(&chr) = iter.peek() { match chr { b'#' => { iter.next(); let event_num = read_number(&mut iter)? as u16; if iter.peek().is_some() { skip_until(b'\n', &mut iter)?; iter.next(); } last_event = event_num; if event_map.contains_key(&event_num) { if strict { return Err(ParseError(format!("Event {} has been defined twice.", event_num))); } match skip_until(b'#', &mut iter).ok() { Some(_) => { continue; } None => { break; } } } let bytecode = TextScript::compile_event(&mut iter, strict, encoding)?; log::info!("Successfully compiled event #{} ({} bytes generated).", event_num, bytecode.len()); event_map.insert(event_num, bytecode); } b'\r' | b'\n' | b' ' | b'\t' => { iter.next(); } n => { // CS+ boss rush is the buggiest shit ever. if !strict && last_event == 0 { iter.next(); continue; } return Err(ParseError(format!("Unexpected token in event {}: {}", last_event, n as char))); } } } Ok(TextScript { event_map }) } fn compile_event>( iter: &mut Peekable, strict: bool, encoding: TextScriptEncoding, ) -> GameResult> { let mut bytecode = Vec::new(); let mut char_buf = Vec::with_capacity(16); let mut allow_next_event = true; while let Some(&chr) = iter.peek() { match chr { b'#' if allow_next_event => { if !char_buf.is_empty() { put_varint(TSCOpCode::_STR as i32, &mut bytecode); put_string(&mut char_buf, &mut bytecode, encoding); } // some events end without { allow_next_event = false; if char_buf.len() > 2 { if let Some(&c) = char_buf.last() { if c == b'\n' { let _ = char_buf.pop(); } } } if !char_buf.is_empty() { put_varint(TSCOpCode::_STR as i32, &mut bytecode); put_string(&mut char_buf, &mut bytecode, encoding); } iter.next(); let n = iter .next_tuple::<(u8, u8, u8)>() .map(|t| [t.0, t.1, t.2]) .ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?; let code = String::from_utf8_lossy(&n); TextScript::compile_code(code.as_ref(), strict, iter, &mut bytecode)?; } b'\r' => { iter.next(); } b'\n' => { allow_next_event = true; char_buf.push(chr); iter.next(); } _ => { allow_next_event = false; char_buf.push(chr); iter.next(); } } } Ok(bytecode) } fn compile_code>( code: &str, strict: bool, iter: &mut Peekable, out: &mut Vec, ) -> GameResult { let instr = TSCOpCode::from_str(code).map_err(|_| ParseError(format!("Unknown opcode: {}", code)))?; match instr { // Zero operand codes TSCOpCode::AEp | TSCOpCode::CAT | TSCOpCode::CIL | TSCOpCode::CLO | TSCOpCode::CLR | TSCOpCode::CPS | TSCOpCode::CRE | TSCOpCode::CSS | TSCOpCode::END | TSCOpCode::ESC | TSCOpCode::FLA | TSCOpCode::FMU | TSCOpCode::FRE | TSCOpCode::HMC | TSCOpCode::INI | TSCOpCode::KEY | TSCOpCode::LDP | TSCOpCode::MLP | TSCOpCode::MM0 | TSCOpCode::MNA | TSCOpCode::MS2 | TSCOpCode::MS3 | TSCOpCode::MSG | TSCOpCode::NOD | TSCOpCode::PRI | TSCOpCode::RMU | TSCOpCode::SAT | TSCOpCode::SLP | TSCOpCode::SMC | TSCOpCode::SPS | TSCOpCode::STC | TSCOpCode::SVP | TSCOpCode::TUR | TSCOpCode::WAS | TSCOpCode::ZAM | TSCOpCode::HM2 | TSCOpCode::POP | TSCOpCode::KE2 | TSCOpCode::FR2 => { put_varint(instr as i32, out); } // One operand codes TSCOpCode::BOA | TSCOpCode::BSL | TSCOpCode::FOB | TSCOpCode::FOM | TSCOpCode::QUA | TSCOpCode::UNI | TSCOpCode::MYB | TSCOpCode::MYD | TSCOpCode::FAI | TSCOpCode::FAO | TSCOpCode::WAI | TSCOpCode::FAC | TSCOpCode::GIT | TSCOpCode::NUM | TSCOpCode::DNA | TSCOpCode::DNP | TSCOpCode::FLm | TSCOpCode::FLp | TSCOpCode::MPp | TSCOpCode::SKm | TSCOpCode::SKp | TSCOpCode::EQp | TSCOpCode::EQm | TSCOpCode::MLp | TSCOpCode::ITp | TSCOpCode::ITm | TSCOpCode::AMm | TSCOpCode::UNJ | TSCOpCode::MPJ | TSCOpCode::YNJ | TSCOpCode::EVE | TSCOpCode::XX1 | TSCOpCode::SIL | TSCOpCode::LIp | TSCOpCode::SOU | TSCOpCode::CMU | TSCOpCode::SSS | TSCOpCode::ACH | TSCOpCode::S2MV | TSCOpCode::S2PJ | TSCOpCode::PSH => { let operand = read_number(iter)?; put_varint(instr as i32, out); put_varint(operand as i32, out); } // Two operand codes TSCOpCode::FON | TSCOpCode::MOV | TSCOpCode::AMp | TSCOpCode::NCJ | TSCOpCode::ECJ | TSCOpCode::FLJ | TSCOpCode::ITJ | TSCOpCode::SKJ | TSCOpCode::AMJ | TSCOpCode::SMP | TSCOpCode::PSp | TSCOpCode::IpN | TSCOpCode::FFm => { let operand_a = read_number(iter)?; if strict { expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?; } let operand_b = read_number(iter)?; put_varint(instr as i32, out); put_varint(operand_a as i32, out); put_varint(operand_b as i32, out); } // Three operand codes TSCOpCode::ANP | TSCOpCode::CNP | TSCOpCode::INP | TSCOpCode::TAM | TSCOpCode::CMP | TSCOpCode::INJ => { let operand_a = read_number(iter)?; if strict { expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?; } let operand_b = read_number(iter)?; if strict { expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?; } let operand_c = read_number(iter)?; put_varint(instr as i32, out); put_varint(operand_a as i32, out); put_varint(operand_b as i32, out); put_varint(operand_c as i32, out); } // Four operand codes TSCOpCode::TRA | TSCOpCode::MNP | TSCOpCode::SNP => { let operand_a = read_number(iter)?; if strict { expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?; } let operand_b = read_number(iter)?; if strict { expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?; } let operand_c = read_number(iter)?; if strict { expect_char(b':', iter)?; } else { iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?; } let operand_d = read_number(iter)?; put_varint(instr as i32, out); put_varint(operand_a as i32, out); put_varint(operand_b as i32, out); put_varint(operand_c as i32, out); put_varint(operand_d as i32, out); } TSCOpCode::_NOP | TSCOpCode::_UNI | TSCOpCode::_STR | TSCOpCode::_END => { unreachable!() } } Ok(()) } } impl CreditScript { pub fn compile(data: &[u8], strict: bool, encoding: TextScriptEncoding) -> GameResult { let mut labels = HashMap::new(); let mut bytecode = Vec::new(); let mut iter = data.iter().copied().peekable(); while let Some(chr) = iter.next() { match chr { b'/' => { put_varint(CreditOpCode::StopCredits as i32, &mut bytecode); } b'[' => { let mut char_buf = Vec::new(); while let Some(&chr) = iter.peek() { if chr == b']' { iter.next(); break; } char_buf.push(chr); iter.next(); } if let Ok(cast_tile) = read_number(&mut iter) { put_varint(CreditOpCode::PushLine as i32, &mut bytecode); put_varint((cast_tile as u16) as i32, &mut bytecode); put_string(&mut char_buf, &mut bytecode, encoding); } } b'-' => { let ticks = read_number(&mut iter)? as u16; put_varint(CreditOpCode::Wait as i32, &mut bytecode); put_varint(ticks as i32, &mut bytecode); } b'+' => { let offset = read_number(&mut iter)?; put_varint(CreditOpCode::ChangeXOffset as i32, &mut bytecode); put_varint(offset, &mut bytecode); } b'!' => { let music = read_number(&mut iter)? as u16; put_varint(CreditOpCode::ChangeMusic as i32, &mut bytecode); put_varint(music as i32, &mut bytecode); } b'~' => { put_varint(CreditOpCode::FadeMusic as i32, &mut bytecode); } b'l' => { let label = read_number(&mut iter)? as u16; let pos = bytecode.len() as u32; labels.insert(label, pos); } b'j' => { let label = read_number(&mut iter)? as u16; put_varint(CreditOpCode::JumpLabel as i32, &mut bytecode); put_varint(label as i32, &mut bytecode); } b'f' => { let flag = read_number(&mut iter)? as u16; if strict { expect_char(b':', &mut iter)?; } else { iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?; } let label = read_number(&mut iter)? as u16; put_varint(CreditOpCode::JumpFlag as i32, &mut bytecode); put_varint(flag as i32, &mut bytecode); put_varint(label as i32, &mut bytecode); } b'p' => { iter.next(); // idfk what's that for, in cs+ Credits.tsc it's '2'. if strict { expect_char(b':', &mut iter)?; } else { iter.next().ok_or_else(|| ParseError("Script unexpectedly ended.".to_owned()))?; } let label = read_number(&mut iter)? as u16; put_varint(CreditOpCode::JumpPlayer2 as i32, &mut bytecode); put_varint(label as i32, &mut bytecode); } _ => (), } } Ok(CreditScript { labels, bytecode }) } }