mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-18 17:04:50 +08:00
Add escude script export support
This commit is contained in:
188
src/scripts/escude/archive.rs
Normal file
188
src/scripts/escude/archive.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
use super::crypto::*;
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::encoding::decode_to_string;
|
||||
use anyhow::Result;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EscudeBinArchiveBuilder {}
|
||||
|
||||
impl EscudeBinArchiveBuilder {
|
||||
pub const fn new() -> Self {
|
||||
EscudeBinArchiveBuilder {}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptBuilder for EscudeBinArchiveBuilder {
|
||||
fn default_encoding(&self) -> Encoding {
|
||||
Encoding::Cp932
|
||||
}
|
||||
|
||||
fn build_script(
|
||||
&self,
|
||||
data: Vec<u8>,
|
||||
encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
Ok(Box::new(EscudeBinArchive::new(data, encoding, config)?))
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &'static [&'static str] {
|
||||
&["bin"]
|
||||
}
|
||||
|
||||
fn script_type(&self) -> &'static ScriptType {
|
||||
&ScriptType::EscudeArc
|
||||
}
|
||||
|
||||
fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
|
||||
if buf_len > 8 && buf.starts_with(b"ESC-ARC2") {
|
||||
return Some(255);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BinEntry {
|
||||
name_offset: u32,
|
||||
data_offset: u32,
|
||||
length: u32,
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
name: String,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ArchiveContent for Entry {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn is_script(&self) -> bool {
|
||||
self.data.starts_with(b"ESCR1_00")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EscudeBinArchive {
|
||||
reader: MemReader,
|
||||
file_count: u32,
|
||||
name_tbl_len: u32,
|
||||
entries: Vec<BinEntry>,
|
||||
encoding: Encoding,
|
||||
}
|
||||
|
||||
impl EscudeBinArchive {
|
||||
pub fn new(data: Vec<u8>, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
|
||||
let mut reader = MemReader::new(data);
|
||||
let mut header = [0u8; 8];
|
||||
reader.read_exact(&mut header)?;
|
||||
if &header != b"ESC-ARC2" {
|
||||
return Err(anyhow::anyhow!("Invalid Escude binary script header"));
|
||||
}
|
||||
reader.seek(SeekFrom::Start(0xC))?;
|
||||
let mut crypto_reader = CryptoReader::new(&mut reader)?;
|
||||
let file_count = crypto_reader.read_u32()?;
|
||||
let name_tbl_len = crypto_reader.read_u32()?;
|
||||
let mut entries = Vec::with_capacity(file_count as usize);
|
||||
for _ in 0..file_count {
|
||||
let name_offset = crypto_reader.read_u32()?;
|
||||
let data_offset = crypto_reader.read_u32()?;
|
||||
let length = crypto_reader.read_u32()?;
|
||||
entries.push(BinEntry {
|
||||
name_offset,
|
||||
data_offset,
|
||||
length,
|
||||
});
|
||||
}
|
||||
Ok(EscudeBinArchive {
|
||||
reader,
|
||||
file_count,
|
||||
name_tbl_len,
|
||||
entries,
|
||||
encoding,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Script for EscudeBinArchive {
|
||||
fn default_output_script_type(&self) -> OutputScriptType {
|
||||
OutputScriptType::Json
|
||||
}
|
||||
|
||||
fn default_format_type(&self) -> FormatOptions {
|
||||
FormatOptions::None
|
||||
}
|
||||
|
||||
fn is_archive(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn iter_archive<'a>(
|
||||
&'a self,
|
||||
) -> Result<Box<dyn Iterator<Item = Result<Box<dyn ArchiveContent>>> + 'a>> {
|
||||
let reader = self.reader.to_ref();
|
||||
let encoding = self.encoding;
|
||||
Ok(Box::new(EscudeBinArchiveIterator {
|
||||
entries: self.entries.iter(),
|
||||
reader,
|
||||
encoding,
|
||||
file_count: self.file_count,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
struct EscudeBinArchiveIterator<'a, T: Iterator<Item = &'a BinEntry>> {
|
||||
entries: T,
|
||||
reader: MemReaderRef<'a>,
|
||||
encoding: Encoding,
|
||||
file_count: u32,
|
||||
}
|
||||
|
||||
impl<'a, T: Iterator<Item = &'a BinEntry>> Iterator for EscudeBinArchiveIterator<'a, T> {
|
||||
type Item = Result<Box<dyn ArchiveContent>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let entry = match self.entries.next() {
|
||||
Some(entry) => entry,
|
||||
None => return None,
|
||||
};
|
||||
let name = match self
|
||||
.reader
|
||||
.peek_cstring_at(entry.name_offset as usize + self.file_count as usize * 12 + 0x14)
|
||||
{
|
||||
Ok(name) => name,
|
||||
Err(e) => return Some(Err(e.into())),
|
||||
};
|
||||
let name = match decode_to_string(self.encoding, name.as_bytes()) {
|
||||
Ok(name) => name,
|
||||
Err(e) => return Some(Err(e.into())),
|
||||
};
|
||||
let mut data = match self
|
||||
.reader
|
||||
.peek_at_vec(entry.data_offset as usize, entry.length as usize)
|
||||
{
|
||||
Ok(data) => data,
|
||||
Err(e) => return Some(Err(e.into())),
|
||||
};
|
||||
if data.starts_with(b"acp") {
|
||||
let mut decoder = match super::lzw::LZWDecoder::new(&data) {
|
||||
Ok(decoder) => decoder,
|
||||
Err(e) => return Some(Err(anyhow::anyhow!("Failed to create LZW decoder: {}", e))),
|
||||
};
|
||||
data = match decoder.unpack() {
|
||||
Ok(unpacked_data) => unpacked_data,
|
||||
Err(e) => return Some(Err(anyhow::anyhow!("Failed to unpack LZW data: {}", e))),
|
||||
};
|
||||
}
|
||||
Some(Ok(Box::new(Entry { name, data })))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user