mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-07 05:18:44 +08:00
Add new game support for escude
This commit is contained in:
294
src/scripts/escude/ops/hanaou.rs
Normal file
294
src/scripts/escude/ops/hanaou.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod base;
|
||||
pub mod hanaou;
|
||||
pub mod panicon;
|
||||
|
||||
@@ -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()?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user