diff --git a/Cargo.toml b/Cargo.toml index 700a30a..e19797e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,11 +39,12 @@ all-fmt = ["all-script", "all-img", "all-arc", "all-audio"] all-script = ["artemis", "bgi", "cat-system", "circus", "escude", "ex-hibit", "hexen-haus", "kirikiri", "will-plus", "yaneurao", "yaneurao-itufuru"] all-img = ["bgi-img", "cat-system-img", "circus-img", "kirikiri-img"] all-arc = ["artemis-arc", "bgi-arc", "cat-system-arc", "circus-arc", "escude-arc"] -all-audio = ["circus-audio"] +all-audio = ["bgi-audio", "circus-audio"] artemis = ["utils-escape"] artemis-arc = ["artemis", "msg_tool_macro/artemis-arc", "sha1"] bgi = ["fancy-regex"] bgi-arc = ["bgi", "rand", "utils-bit-stream"] +bgi-audio = ["bgi"] bgi-img = ["bgi", "image", "utils-bit-stream"] cat-system = ["fancy-regex", "flate2", "int-enum"] cat-system-arc = ["cat-system", "blowfish", "utils-crc32"] diff --git a/README.md b/README.md index 17a7f94..f6a9d58 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,10 @@ msg-tool create -t | `bgi-arc-v1`/`ethornell-arc-v1` | `bgi-arc` | Buriko General Interpreter/Ethornell Archive File Version 1 (.arc) | ✔️ | ✔️ | | | `bgi-arc`/`bgi-arc-v2`/`ethornell-arc`/`ethornell-arc-v2` | `bgi-arc` | Buriko General Interpreter/Ethornell Archive File Version 2 (.arc) | ✔️ | ✔️ | | +| Audio Type | Feature Name | Name | Export | Import | Create | Remarks | +|---|---|---|---|---|---|---| +| `bgi-audio`/`ethornell-audio` | `bgi-audio` | Buriko General Interpreter/Ethornell Audio File (Ogg/Vorbis) | ✔️ | ❌ | ❌ | | + | Image Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Create | Remarks | |---|---|---|---|---|---|---|---|---| | `bgi-img`/`ethornell-img` | `bgi-img` | Buriko General Interpreter/Ethornell Uncompressed Image File | ✔️ | ✔️ | ❌ | ❌ | ✔️ | Image files in `sysgrp.arc` | diff --git a/src/scripts/bgi/archive/v1.rs b/src/scripts/bgi/archive/v1.rs index ad7fc34..85947de 100644 --- a/src/scripts/bgi/archive/v1.rs +++ b/src/scripts/bgi/archive/v1.rs @@ -489,6 +489,10 @@ fn detect_script_type(buf: &[u8], buf_len: usize, filename: &str) -> Option<&'st if buf_len >= 16 && buf.starts_with(b"CompressedBG___") { return Some(&ScriptType::BGICbg); } + #[cfg(feature = "bgi-audio")] + if buf_len >= 8 && buf[4..].starts_with(b"bw ") { + return Some(&ScriptType::BGIAudio); + } let filename = filename.to_lowercase(); if filename.ends_with("._bp") { return Some(&ScriptType::BGIBp); diff --git a/src/scripts/bgi/archive/v2.rs b/src/scripts/bgi/archive/v2.rs index c26c041..7eef254 100644 --- a/src/scripts/bgi/archive/v2.rs +++ b/src/scripts/bgi/archive/v2.rs @@ -491,6 +491,10 @@ fn detect_script_type(buf: &[u8], buf_len: usize, filename: &str) -> Option<&'st if buf_len >= 16 && buf.starts_with(b"CompressedBG___") { return Some(&ScriptType::BGICbg); } + #[cfg(feature = "bgi-audio")] + if buf_len >= 8 && buf[4..].starts_with(b"bw ") { + return Some(&ScriptType::BGIAudio); + } let filename = filename.to_lowercase(); if filename.ends_with("._bp") { return Some(&ScriptType::BGIBp); diff --git a/src/scripts/bgi/audio/audio.rs b/src/scripts/bgi/audio/audio.rs new file mode 100644 index 0000000..10e8b18 --- /dev/null +++ b/src/scripts/bgi/audio/audio.rs @@ -0,0 +1,127 @@ +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use anyhow::Result; +use std::io::{Read, Seek, SeekFrom, Write}; + +#[derive(Debug)] +pub struct BgiAudioBuilder {} + +impl BgiAudioBuilder { + pub fn new() -> Self { + Self {} + } +} + +impl ScriptBuilder for BgiAudioBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Utf8 + } + + fn build_script( + &self, + buf: Vec, + _filename: &str, + _encoding: Encoding, + _archive_encoding: Encoding, + config: &ExtraConfig, + _archive: Option<&Box>, + ) -> Result> { + Ok(Box::new(BgiAudio::new(MemReader::new(buf), config)?)) + } + + fn build_script_from_file( + &self, + filename: &str, + _encoding: Encoding, + _archive_encoding: Encoding, + config: &ExtraConfig, + _archive: Option<&Box>, + ) -> Result> { + let file = std::fs::File::open(filename)?; + let f = std::io::BufReader::new(file); + Ok(Box::new(BgiAudio::new(f, config)?)) + } + + fn build_script_from_reader( + &self, + reader: Box, + _filename: &str, + _encoding: Encoding, + _archive_encoding: Encoding, + config: &ExtraConfig, + _archive: Option<&Box>, + ) -> Result> { + Ok(Box::new(BgiAudio::new(reader, config)?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &[] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::BGIAudio + } + + fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + if buf_len >= 8 && buf[4..].starts_with(b"bw ") { + Some(10) + } else { + None + } + } +} + +#[derive(Debug)] +pub struct BgiAudio { + data: MemReader, +} + +impl BgiAudio { + pub fn new(mut reader: R, _config: &ExtraConfig) -> Result { + let offset = reader.read_u32()?; + let len = reader.stream_length()?; + if (offset as u64) > len { + return Err(anyhow::anyhow!("Invalid offset in BGI audio file")); + } + let mut magic = [0; 4]; + reader.read_exact(&mut magic)?; + if magic != *b"bw " { + return Err(anyhow::anyhow!( + "Invalid magic in BGI audio file: {:?}", + magic + )); + } + reader.seek(SeekFrom::Start(offset as u64))?; + let mut data = Vec::new(); + reader.read_to_end(&mut data)?; + Ok(Self { + data: MemReader::new(data), + }) + } +} + +impl Script for BgiAudio { + fn default_output_script_type(&self) -> OutputScriptType { + OutputScriptType::Custom + } + + fn default_format_type(&self) -> FormatOptions { + FormatOptions::None + } + + fn is_output_supported(&self, output: OutputScriptType) -> bool { + matches!(output, OutputScriptType::Custom) + } + + fn custom_output_extension<'a>(&'a self) -> &'a str { + "ogg" + } + + fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> { + let mut writer = std::fs::File::create(filename)?; + writer.write_all(&self.data.data)?; + writer.flush()?; + Ok(()) + } +} diff --git a/src/scripts/bgi/audio/mod.rs b/src/scripts/bgi/audio/mod.rs new file mode 100644 index 0000000..fa3e4e4 --- /dev/null +++ b/src/scripts/bgi/audio/mod.rs @@ -0,0 +1 @@ +pub mod audio; diff --git a/src/scripts/bgi/mod.rs b/src/scripts/bgi/mod.rs index cf888dd..49ac4e0 100644 --- a/src/scripts/bgi/mod.rs +++ b/src/scripts/bgi/mod.rs @@ -1,5 +1,7 @@ #[cfg(feature = "bgi-arc")] pub mod archive; +#[cfg(feature = "bgi-audio")] +pub mod audio; pub mod bp; pub mod bsi; #[cfg(feature = "bgi-img")] diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index 4636d31..5a1ce98 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -98,6 +98,8 @@ lazy_static::lazy_static! { Box::new(circus::archive::crm::CrmArchiveBuilder::new()), #[cfg(feature = "circus-img")] Box::new(circus::image::crxd::CrxdImageBuilder::new()), + #[cfg(feature = "bgi-audio")] + Box::new(bgi::audio::audio::BgiAudioBuilder::new()), ]; pub static ref ALL_EXTS: Vec = BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect(); diff --git a/src/types.rs b/src/types.rs index 162e995..148a384 100644 --- a/src/types.rs +++ b/src/types.rs @@ -304,6 +304,10 @@ pub enum ScriptType { #[value(alias("ethornell-dsc"))] /// Buriko General Interpreter/Ethornell compressed file (DSC) BGIDsc, + #[cfg(feature = "bgi-audio")] + #[value(alias("ethornell-audio"))] + /// Buriko General Interpreter/Ethornell audio file (Ogg/Vorbis) + BGIAudio, #[cfg(feature = "bgi-img")] #[value(alias("ethornell-img"))] /// Buriko General Interpreter/Ethornell image (Image files in sysgrp.arc)