Add new script hexen_haus support

This commit is contained in:
2025-07-25 09:24:01 +08:00
parent a77e107872
commit 968abeec6d
6 changed files with 264 additions and 3 deletions

5
Cargo.lock generated
View File

@@ -605,9 +605,9 @@ checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "miniz_oxide"
@@ -635,6 +635,7 @@ dependencies = [
"json",
"lazy_static",
"libtlg-rs",
"memchr",
"msg_tool_macro",
"overf",
"png",

View File

@@ -16,6 +16,7 @@ int-enum = { version = "1.2", optional = true }
json = { version = "0.12", optional = true }
lazy_static = "1.5.0"
libtlg-rs = { version = "0.1", optional = true }
memchr = { version = "2.7", optional = true }
msg_tool_macro = { path = "./msg_tool_macro" }
overf = "0.1"
png = { version = "0.17", optional = true }
@@ -28,7 +29,7 @@ url = { version = "2.5", optional = true }
utf16string = "0.2"
[features]
default = ["artemis", "artemis-arc", "bgi", "bgi-arc", "bgi-img", "cat-system", "cat-system-arc", "cat-system-img", "circus", "escude", "escude-arc", "kirikiri", "kirikiri-img", "will-plus", "yaneurao", "yaneurao-itufuru"]
default = ["artemis", "artemis-arc", "bgi", "bgi-arc", "bgi-img", "cat-system", "cat-system-arc", "cat-system-img", "circus", "escude", "escude-arc", "hexen-haus", "kirikiri", "kirikiri-img", "will-plus", "yaneurao", "yaneurao-itufuru"]
artemis = ["utils-escape"]
artemis-arc = ["artemis", "msg_tool_macro/artemis-arc", "sha1"]
bgi = []
@@ -40,6 +41,7 @@ cat-system-img = ["cat-system", "flate2", "image", "utils-bit-stream"]
circus = []
escude = ["int-enum"]
escude-arc = ["escude", "rand", "utils-bit-stream"]
hexen-haus = ["memchr", "utils-str"]
kirikiri = ["emote-psb", "fancy-regex", "flate2", "json", "utils-escape"]
kirikiri-img = ["kirikiri", "emote-psb", "image", "libtlg-rs", "url"]
will-plus = ["utils-str"]

View File

@@ -0,0 +1,250 @@
use crate::ext::io::*;
use crate::scripts::base::*;
use crate::types::*;
use crate::utils::encoding::*;
use crate::utils::str::*;
use anyhow::Result;
use std::io::Read;
#[derive(Debug)]
pub struct BinScriptBuilder {}
impl BinScriptBuilder {
pub fn new() -> Self {
BinScriptBuilder {}
}
}
impl ScriptBuilder for BinScriptBuilder {
fn default_encoding(&self) -> Encoding {
Encoding::Cp932
}
fn build_script(
&self,
buf: Vec<u8>,
_filename: &str,
encoding: Encoding,
_archive_encoding: Encoding,
config: &ExtraConfig,
) -> Result<Box<dyn Script>> {
Ok(Box::new(BinScript::new(buf, encoding, config)?))
}
fn extensions(&self) -> &'static [&'static str] {
&["bin"]
}
fn script_type(&self) -> &'static ScriptType {
&ScriptType::HexenHaus
}
fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
if buf_len >= 4 && buf.starts_with(b"NORI") {
return Some(10);
}
None
}
}
#[derive(Debug)]
struct BinString {
str: String,
pos: usize,
len: usize,
}
#[derive(Debug)]
pub struct BinScript {
data: MemReader,
strs: Vec<BinString>,
}
impl BinScript {
pub fn new(buf: Vec<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
let mut data = MemReader::new(buf);
let mut header = [0; 4];
data.read_exact(&mut header)?;
if header != *b"NORI" {
return Err(anyhow::anyhow!("Invalid HexenHaus bin script header"));
}
for c in data.data.iter_mut() {
*c ^= 0x53;
}
data.pos = memchr::memmem::find(&data.data, b"_beginrp")
.ok_or(anyhow::anyhow!("Failed to find _beginrp"))?;
data.pos += 16;
let mut p = [0; 2];
let mut s = Vec::new();
let data_len = data.data.len();
let mut start_pos = data.pos;
let mut strs = Vec::new();
while data.pos < data_len {
data.read_exact(&mut p)?;
if p[0] == 0x53 {
if s.len() > 2 {
if let Ok(c) = decode_to_string(encoding, &s[s.len() - 2..], true) {
if c != "" && c != "" && c != "" {
s.pop();
s.pop();
}
} else {
s.pop();
s.pop();
}
}
if s.len() > 2 {
let d = decode_to_string(encoding, &s, true)?;
strs.push(BinString {
str: d,
pos: start_pos,
len: s.len(),
});
}
start_pos = data.pos;
s.clear();
} else if p[1] == 0x53 {
if s.len() > 2 {
let d = decode_to_string(encoding, &s, true)?;
strs.push(BinString {
str: d,
pos: start_pos,
len: s.len(),
});
}
start_pos = data.pos;
s.clear();
} else {
s.extend_from_slice(&p);
}
}
if s.len() > 2 {
s.pop();
s.pop();
if s.len() > 2 {
let d = decode_to_string(encoding, &s, true)?;
strs.push(BinString {
str: d,
pos: start_pos,
len: s.len(),
});
}
}
Ok(BinScript { data, strs })
}
}
impl Script for BinScript {
fn default_output_script_type(&self) -> OutputScriptType {
OutputScriptType::Json
}
fn default_format_type(&self) -> FormatOptions {
FormatOptions::None
}
fn extract_messages(&self) -> Result<Vec<Message>> {
let mut messages: Vec<Message> = Vec::new();
for str in &self.strs {
let message = if let Some(ind) = str.str.find("") {
let (name, mes) = str.str.split_at(ind);
let mut name = name.to_string();
if name.is_empty() {
if let Some(m) = messages.pop() {
name = m.message;
}
}
Message {
name: Some(name.to_string()),
message: mes.to_string(),
}
} else {
Message {
name: None,
message: str.str.clone(),
}
};
messages.push(message);
}
Ok(messages)
}
fn import_messages<'a>(
&'a self,
mut messages: Vec<Message>,
mut file: Box<dyn WriteSeek + 'a>,
encoding: Encoding,
replacement: Option<&'a ReplacementTable>,
) -> Result<()> {
let mut data = MemWriter::from_vec(self.data.data.clone());
let mut i = 0;
for str in self.strs.iter() {
if i >= messages.len() {
return Err(anyhow::anyhow!("Not enough messages."));
}
if let Some(ind) = str.str.find("") {
let (name, _) = str.str.split_at(ind);
let mut target = String::new();
if !name.is_empty() {
let mut name = match &messages[i].name {
Some(n) => n.to_owned(),
None => return Err(anyhow::anyhow!("Missing name for message.")),
};
if let Some(repl) = replacement {
for (k, v) in &repl.map {
name = name.replace(k, v);
}
};
target.push_str(&name);
}
let mut mes = messages[i].message.clone();
if let Some(repl) = replacement {
for (k, v) in &repl.map {
mes = mes.replace(k, v);
}
}
target.push_str(&mes);
let mut encoded = encode_string(encoding, &target, false)?;
if encoded.len() > str.len {
eprintln!("Warning: Message '{}' is too long, truncating.", target);
crate::COUNTER.inc_warning();
encoded = truncate_string(&target, str.len, encoding, false)?;
}
while encoded.len() < str.len {
encoded.push(32); // Fill with spaces
}
data.write_all_at(str.pos, &encoded)?;
i += 1;
} else {
let mut target = if let Some(name) = messages[i].name.take() {
name
} else {
let s = messages[i].message.clone();
i += 1;
s
};
if let Some(repl) = replacement {
for (k, v) in &repl.map {
target = target.replace(k, v);
}
}
let mut encoded = encode_string(encoding, &target, false)?;
if encoded.len() > str.len {
eprintln!("Warning: Message '{}' is too long, truncating.", target);
crate::COUNTER.inc_warning();
encoded = truncate_string(&target, str.len, encoding, false)?;
}
while encoded.len() < str.len {
encoded.push(32); // Fill with spaces
}
data.write_all_at(str.pos, &encoded)?;
}
}
let mut data = data.into_inner();
for d in data.iter_mut() {
*d ^= 0x53;
}
file.write_all(&data)?;
Ok(())
}
}

View File

@@ -0,0 +1 @@
pub mod bin;

View File

@@ -9,6 +9,8 @@ pub mod cat_system;
pub mod circus;
#[cfg(feature = "escude")]
pub mod escude;
#[cfg(feature = "hexen-haus")]
pub mod hexen_haus;
#[cfg(feature = "kirikiri")]
pub mod kirikiri;
#[cfg(feature = "will-plus")]
@@ -76,6 +78,8 @@ lazy_static::lazy_static! {
Box::new(artemis::ast::AstScriptBuilder::new()),
#[cfg(feature = "artemis")]
Box::new(artemis::asb::ArtemisAsbBuilder::new()),
#[cfg(feature = "hexen-haus")]
Box::new(hexen_haus::bin::BinScriptBuilder::new()),
];
pub static ref ALL_EXTS: Vec<String> =
BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect();

View File

@@ -301,6 +301,9 @@ pub enum ScriptType {
#[cfg(feature = "escude")]
/// Escude list script
EscudeList,
#[cfg(feature = "hexen-haus")]
/// HexenHaus bin script
HexenHaus,
#[cfg(feature = "kirikiri")]
#[value(alias("kr-scn"))]
/// Kirikiri SCN script