Add support for BGI ._bp script

This commit is contained in:
2025-06-08 09:25:54 +08:00
parent f7554f5969
commit a6e230eb7d
4 changed files with 177 additions and 0 deletions

170
src/scripts/bgi/bp.rs Normal file
View File

@@ -0,0 +1,170 @@
use crate::ext::io::*;
use crate::scripts::base::*;
use crate::types::*;
use crate::utils::encoding::{decode_to_string, encode_string};
use anyhow::Result;
use std::io::{Seek, SeekFrom};
#[derive(Debug)]
pub struct BGIBpScriptBuilder {}
impl BGIBpScriptBuilder {
pub fn new() -> Self {
BGIBpScriptBuilder {}
}
}
impl ScriptBuilder for BGIBpScriptBuilder {
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(BGIBpScript::new(buf, encoding, config)?))
}
fn extensions(&self) -> &'static [&'static str] {
&["_bp"]
}
fn script_type(&self) -> &'static ScriptType {
&ScriptType::BGIBp
}
}
#[derive(Debug)]
struct BpString {
offset_pos: usize,
text_offset: u16,
}
#[derive(Debug)]
pub struct BGIBpScript {
data: MemReader,
header_size: u32,
strings: Vec<BpString>,
encoding: Encoding,
}
impl BGIBpScript {
pub fn new(buf: Vec<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
let mut reader = MemReader::new(buf);
let header_size = reader.read_u32()?;
let instr_size = reader.read_u32()?;
if header_size as usize + instr_size as usize != reader.data.len() {
return Err(anyhow::anyhow!("Invalid bp script file size"));
}
let mut last_instr_pos = 0;
reader.seek(SeekFrom::Start(header_size as u64))?;
for _ in 0..instr_size / 4 {
let instr = reader.read_u32()?;
if instr == 0x17 {
last_instr_pos = reader.pos;
}
}
if last_instr_pos == 0 {
return Err(anyhow::anyhow!("No end instruction found in bp script"));
}
reader.seek(SeekFrom::Start(header_size as u64))?;
let mut strings = Vec::new();
while reader.pos < last_instr_pos {
let ins = reader.read_u8()?;
if ins == 5 {
let text_offset = reader.peek_u16()?;
let text_address = reader.pos + text_offset as usize - 1;
if text_address >= last_instr_pos
&& text_address < reader.data.len()
&& (text_address == last_instr_pos || reader.data[text_address - 1] == 0)
{
strings.push(BpString {
offset_pos: reader.pos,
text_offset,
});
reader.pos += 2;
}
}
}
return Ok(BGIBpScript {
data: reader,
header_size,
strings,
encoding,
});
}
}
impl Script for BGIBpScript {
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::new();
for i in self.strings.iter() {
let text_address = i.offset_pos + i.text_offset as usize - 1;
// println!("offset: {}, text address: {}, text_offset: {}", i.offset_pos, text_address, i.text_offset);
let str = self.data.cpeek_cstring_at(text_address)?;
let str = decode_to_string(self.encoding, str.as_bytes())?;
messages.push(Message {
name: None,
message: str,
});
}
Ok(messages)
}
fn import_messages<'a>(
&'a self,
messages: Vec<Message>,
mut file: Box<dyn WriteSeek + 'a>,
encoding: Encoding,
replacement: Option<&'a ReplacementTable>,
) -> Result<()> {
if messages.len() != self.strings.len() {
return Err(anyhow::anyhow!(
"Number of messages does not match the number of strings in the script"
));
}
file.write_all(&self.data.data)?;
let mut new_pos = self.data.data.len();
for (i, mes) in self.strings.iter().zip(messages) {
let text_address = i.offset_pos + i.text_offset as usize - 1;
let old_str_len = self
.data
.cpeek_cstring_at(text_address)?
.as_bytes_with_nul()
.len();
let mut str = mes.message;
if let Some(replacement) = replacement {
for (key, value) in replacement.map.iter() {
str = str.replace(key, value);
}
}
let mut str = encode_string(encoding, &str, false)?;
str.push(0); // Null terminator
let new_str_len = str.len();
if new_str_len > old_str_len {
file.write_all(&str)?;
let new_text_offset = (new_pos - i.offset_pos + 1) as u16;
file.write_u16_at(i.offset_pos, new_text_offset)?;
new_pos += new_str_len;
} else {
file.write_all_at(text_address, &str)?;
}
}
let new_instr_size = (new_pos - self.header_size as usize) as u32;
file.write_u32_at(4, new_instr_size)?;
Ok(())
}
}

View File

@@ -1,3 +1,4 @@
pub mod bp;
pub mod bsi;
mod parser;
pub mod script;

View File

@@ -16,6 +16,8 @@ lazy_static::lazy_static! {
Box::new(bgi::script::BGIScriptBuilder::new()),
#[cfg(feature = "bgi")]
Box::new(bgi::bsi::BGIBsiScriptBuilder::new()),
#[cfg(feature = "bgi")]
Box::new(bgi::bp::BGIBpScriptBuilder::new()),
#[cfg(feature = "escude-arc")]
Box::new(escude::archive::EscudeBinArchiveBuilder::new()),
#[cfg(feature = "escude")]

View File

@@ -213,6 +213,10 @@ pub enum ScriptType {
#[value(alias("ethornell-bsi"))]
/// Buriko General Interpreter/Ethornell bsi script (._bsi)
BGIBsi,
#[cfg(feature = "bgi")]
#[value(alias("ethornell-bp"))]
/// Buriko General Interpreter/Ethornell bp script (._bp)
BGIBp,
#[cfg(feature = "escude-arc")]
/// Escude bin archive
EscudeArc,