Add new game support for escude

This commit is contained in:
2025-12-26 17:29:05 +08:00
parent ce2d901523
commit d8ef082645
6 changed files with 391 additions and 23 deletions

View File

@@ -0,0 +1,294 @@
use super::super::script::ReadParam;
use super::base::CustomOps;
use crate::ext::io::*;
use anyhow::Result;
use int_enum::IntEnum;
use std::collections::HashMap;
use std::io::Seek;
#[repr(u8)]
#[derive(Debug, IntEnum)]
enum HanaouOp {
End = 0x22,
Jump,
Call,
AutoPlay,
Frame,
Text,
Clear,
Gap,
Mes,
Tlk,
Menu,
Select,
LsfInit,
LsfSet,
Cg,
Em,
Clr,
Disp,
Path,
Trans,
BgmPlay,
BgmStop,
BgmVolume,
BgmFx,
AmbPlay,
AmbStop,
AmbVolume,
AmbFx,
SePlay,
SeStop,
SeWait,
SeVolume,
SeFx,
VocPlay,
VocStop,
VocWait,
VocVolume,
VocFx,
Quake,
Flash,
Filter,
Effect,
Sync,
Wait,
Movie,
Credit,
Event,
Scene,
Title,
Notice,
Info,
SetPass,
IsPass,
AutoSave,
Place,
OpenName,
Name,
LogNew,
LogOut,
ElapsedDays,
Date,
TimeTable,
Lesson,
LessonExp,
QuestAdd,
QuestDel,
QuestMenu,
QuestExp,
MasterExp,
Battle,
Status,
Tutorial,
SetMaster,
GetMaster,
SetPlayer,
GetPlayer,
SetQuest,
GetQuest,
AddSkill,
HasSkill,
SetDesk,
GetLesson,
Impact,
}
#[derive(Debug)]
pub struct HanaouOps<T: std::fmt::Debug + std::hash::Hash> {
prev_name: Option<T>,
menus: HashMap<T, T>,
last_select: usize,
}
impl<T: std::fmt::Debug + std::hash::Hash> HanaouOps<T> {
pub fn new() -> Self {
Self {
prev_name: None,
menus: HashMap::new(),
last_select: 0,
}
}
}
use HanaouOp::*;
impl<T> CustomOps<T> for HanaouOps<T>
where
T: std::fmt::Debug + TryInto<u64> + std::hash::Hash,
{
fn run<'a>(&mut self, vm: &mut super::super::script::VM<'a, T>, op: u8) -> Result<bool>
where
MemReaderRef<'a>: ReadParam<T>,
T: TryInto<u64>
+ Default
+ Eq
+ Ord
+ Copy
+ std::fmt::Debug
+ std::fmt::Display
+ std::hash::Hash
+ From<u8>
+ std::ops::Neg<Output = T>
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ std::ops::Rem<Output = T>
+ std::ops::Not<Output = T>
+ std::ops::BitAnd<Output = T>
+ std::ops::BitOr<Output = T>
+ std::ops::BitXor<Output = T>
+ std::ops::Shr<Output = T>
+ std::ops::Shl<Output = T>,
anyhow::Error: From<<T as TryInto<u64>>::Error>,
{
if let Ok(op) = HanaouOp::try_from(op) {
match op {
End => vm.skip_n_params(1, false),
Jump => vm.skip_n_params(1, false),
Call => vm.skip_n_params(1, false),
AutoPlay => vm.skip_n_params(1, false),
Frame => vm.skip_n_params(1, false),
Text => vm.skip_n_params(2, false),
Clear => vm.skip_n_params(1, false),
Gap => vm.skip_n_params(2, false),
// Handle concat name
Mes => {
let params = vm.read_params(Some(1))?;
let mes = params[0];
vm.mess.insert(mes);
if let Some(name) = self.prev_name.take() {
vm.names.insert(mes, name);
}
Ok(false)
}
Tlk => {
let params = vm.read_params(None)?;
let name = params
.get(0)
.cloned()
.ok_or(anyhow::anyhow!("Missing name parameter"))?;
self.prev_name = Some(name);
Ok(false)
}
Menu => {
let params = vm.read_params(Some(3))?;
let id = params[0];
let mes = params[1];
vm.mess.insert(mes);
self.menus.insert(id, mes);
Ok(false)
}
Select => {
let param = vm.read_params(Some(1))?;
println!("Select param: {:?}", param);
if let Some(var) = vm.vars.get_mut(&T::from(131)) {
*var = *var + T::from(1);
return Ok(false);
}
let offset = vm.reader.stream_position()? - 1;
for _ in self.last_select + 1..self.menus.len() {
vm.stack.push(offset);
// println!("Pushing offset: {offset:#x} to stack");
}
vm.vars.insert(T::from(131), T::from(0));
Ok(false)
}
LsfInit => vm.skip_n_params(1, false),
LsfSet => vm.skip_params(false),
Cg => vm.skip_params(false),
Em => vm.skip_n_params(5, false),
Clr => vm.skip_n_params(1, false),
Disp => vm.skip_n_params(3, false),
Path => vm.skip_params(false),
Trans => Ok(false),
BgmPlay => vm.skip_n_params(3, false),
BgmStop => vm.skip_n_params(1, false),
BgmVolume => vm.skip_n_params(2, false),
BgmFx => vm.skip_n_params(1, false),
AmbPlay => vm.skip_n_params(3, false),
AmbStop => vm.skip_n_params(1, false),
AmbVolume => vm.skip_n_params(2, false),
AmbFx => vm.skip_n_params(1, false),
SePlay => vm.skip_n_params(5, false),
SeStop => vm.skip_n_params(2, false),
SeWait => vm.skip_n_params(1, false),
SeVolume => vm.skip_n_params(3, false),
SeFx => vm.skip_n_params(1, false),
VocPlay => vm.skip_n_params(4, false),
VocStop => vm.skip_n_params(2, false),
VocWait => vm.skip_n_params(1, false),
VocVolume => vm.skip_n_params(3, false),
VocFx => vm.skip_n_params(1, false),
Quake => vm.skip_n_params(4, false),
Flash => vm.skip_n_params(2, false),
Filter => vm.skip_n_params(2, false),
Effect => vm.skip_n_params(1, false),
Sync => vm.skip_n_params(2, false),
Wait => vm.skip_n_params(1, false),
Movie => vm.skip_n_params(1, false),
Credit => vm.skip_n_params(1, false),
Event => vm.skip_n_params(1, false),
Scene => vm.skip_n_params(1, false),
Title => {
let title = vm.read_params(Some(1))?;
vm.mess.insert(title[0]);
Ok(false)
}
Notice => {
let notices = vm.read_params(Some(3))?;
vm.mess.insert(notices[0]);
Ok(false)
}
Info => {
let infos = vm.read_params(Some(2))?;
vm.mess.insert(infos[0]);
Ok(false)
}
SetPass => vm.skip_n_params(2, false),
IsPass => vm.skip_n_params(1, false),
AutoSave => Ok(false),
Place => vm.skip_n_params(1, false),
OpenName => vm.skip_n_params(1, false),
Name => {
let params = vm.read_params(Some(2))?;
let name = params[0];
let mes = params[1];
vm.mess.insert(mes);
vm.names.insert(mes, name);
Ok(false)
}
LogNew => vm.skip_n_params(1, false),
LogOut => vm.skip_params(false),
ElapsedDays => vm.skip_n_params(1, false),
Date => Ok(false),
TimeTable => vm.skip_n_params(2, false),
Lesson => vm.skip_n_params(1, false),
LessonExp => vm.skip_n_params(2, false),
QuestAdd => vm.skip_n_params(1, false),
QuestDel => vm.skip_n_params(1, false),
QuestMenu => Ok(false),
QuestExp => vm.skip_n_params(2, false),
MasterExp => vm.skip_n_params(2, false),
Battle => vm.skip_n_params(3, false),
Status => Ok(false),
Tutorial => vm.skip_n_params(1, false),
SetMaster => vm.skip_n_params(3, false),
GetMaster => vm.skip_n_params(2, false),
SetPlayer => vm.skip_n_params(2, false),
GetPlayer => vm.skip_n_params(1, false),
SetQuest => vm.skip_n_params(3, false),
GetQuest => vm.skip_n_params(2, false),
AddSkill => vm.skip_n_params(2, false),
HasSkill => vm.skip_n_params(1, false),
SetDesk => vm.skip_n_params(2, false),
GetLesson => vm.skip_n_params(3, false),
Impact => Ok(false),
}
} else {
// return Err(anyhow::anyhow!("Unknown Panicon operation: {op:#02x}"));
Ok(false)
}
}
}

View File

@@ -1,2 +1,3 @@
pub mod base;
pub mod hanaou;
pub mod panicon;

View File

@@ -1,5 +1,5 @@
//! Escu:de Script File (.bin)
use super::list::{EnumScr, EscudeBinList, ListData, NameT};
use super::list::{EnumScr, EscudeBinList, ListData, NameT, VarT};
use super::ops::base::CustomOps;
use crate::ext::io::*;
use crate::scripts::base::*;
@@ -7,12 +7,22 @@ use crate::types::*;
use crate::utils::encoding::{decode_to_string, encode_string};
use crate::utils::struct_pack::StructPack;
use anyhow::Result;
use clap::ValueEnum;
use int_enum::IntEnum;
use std::collections::{BTreeSet, HashMap};
use std::ffi::CString;
use std::io::{Read, Seek, SeekFrom};
use unicode_segmentation::UnicodeSegmentation;
#[derive(Debug, ValueEnum, Clone, Copy)]
/// Game title
pub enum EscudeOp {
/// パニカルコンフュージョン
Panicon,
/// 花嫁と魔王 ~王室のハーレムは下克上~
Hanaou,
}
#[derive(Debug)]
/// Builder for Escu:de binary script files
pub struct EscudeBinScriptBuilder {}
@@ -70,23 +80,47 @@ fn load_enum_script(
filename: &str,
encoding: Encoding,
config: &ExtraConfig,
) -> Result<Vec<NameT>> {
) -> Result<(Vec<NameT>, Vec<VarT>)> {
let buf = crate::utils::files::read_file(filename)?;
let scr = EscudeBinList::new(buf, filename, encoding, config)?;
let mut names = None;
let mut vars = None;
for scr in scr.entries {
match scr.data {
ListData::Scr(scr) => match scr {
EnumScr::Names(names) => return Ok(names),
EnumScr::Names(name) => {
names = Some(name);
}
EnumScr::Vars(var) => {
vars = Some(var);
}
_ => {}
},
_ => {}
}
}
Err(anyhow::anyhow!(
"Failed to find name table in Escude enum script",
Ok((
names.ok_or_else(|| anyhow::anyhow!("No names data in enum script"))?,
vars.ok_or_else(|| anyhow::anyhow!("No vars data in enum script"))?,
))
}
// fn check_messages_in_vms<'a, T: TryInto<usize> + Copy + 'a, I>(messages: &[String], mes: I)
// where
// I: Iterator<Item = &'a T>,
// {
// let mess = mes.filter_map(|m| (*m).try_into().ok()).collect::<HashSet<usize>>();
// for (i, str) in messages.iter().enumerate() {
// if str.is_empty() {
// continue;
// }
// if !mess.contains(&i) {
// eprintln!("WARN: Message at index {i} not referenced in VMs: {}", str);
// crate::COUNTER.inc_warning();
// }
// }
// }
impl EscudeBinScript {
/// Creates a new `EscudeBinScript`
///
@@ -129,26 +163,53 @@ impl EscudeBinScript {
}
let names = match &config.escude_enum_scr {
Some(loc) => match load_enum_script(loc, encoding, config) {
Ok(list) => {
let mut names = HashMap::new();
let mut vm = VM::new(&vms);
vm.vars.insert(1, 1);
vm.vars.insert(132, 0);
vm.vars.insert(133, 0);
vm.vars.insert(134, 0);
vm.vars.insert(1001, 0);
vm.vars.insert(1003, 0);
for i in 135..140 {
vm.vars.insert(i, 1);
}
let _ = vm.run(Some(Box::new(super::ops::panicon::PaniconOps::new())));
for (index, name) in vm.names.iter() {
if let Some(name) = list.get(*name as usize) {
names.insert(*index as usize, name.text.clone());
Ok((list, vars)) => match config.escude_op {
Some(EscudeOp::Panicon) => {
let mut names = HashMap::new();
let mut vm = VM::new(&vms);
for var in vars {
vm.vars.insert(var.value as i32, var.flag as i32);
}
let _ = vm.run(Some(Box::new(super::ops::panicon::PaniconOps::new())));
for (index, name) in vm.names.iter() {
if let Some(name) = list.get(*name as usize) {
names.insert(*index as usize, name.text.clone());
}
}
// check_messages_in_vms(&strings, vm.mess.iter());
Some(names)
}
Some(names)
}
Some(EscudeOp::Hanaou) => {
let mut names = HashMap::new();
let mut vm = VM::new(&vms);
for var in vars {
vm.vars.insert(var.value as i32, var.flag as i32);
}
let _ = vm.run(Some(Box::new(super::ops::hanaou::HanaouOps::new())));
for (index, name) in vm.names.iter() {
if let Some(name) = list.get(*name as usize) {
names.insert(*index as usize, name.text.clone());
}
}
// check_messages_in_vms(&strings, vm.mess.iter());
Some(names)
}
None => {
let mut names = HashMap::new();
let mut vm = VM::new(&vms);
for var in vars {
vm.vars.insert(var.value as i32, var.flag as i32);
}
let _ = vm.run(None);
for (index, name) in vm.names.iter() {
if let Some(name) = list.get(*name as usize) {
names.insert(*index as usize, name.text.clone());
}
}
// check_messages_in_vms(&strings, vm.mess.iter());
Some(names)
}
},
Err(e) => {
eprintln!(
"WARN: Failed to load Escude enum script from {}: {}",
@@ -636,6 +697,9 @@ where
}
pub fn skip_n_params(&mut self, n: u64, nbreak: bool) -> Result<bool> {
if (self.data.len() as u64) < n {
println!("{:?}", self.data);
}
for _ in 0..n {
self.pop_data()?;
}