mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-18 00:45:08 +08:00
Add import support for Qlie Abmp10/11/12 image (.b)
This commit is contained in:
@@ -216,7 +216,7 @@ msg-tool create -t <script-type> <input> <output>
|
||||
| Script Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Custom Export | Custom Import | Create | Remarks |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| `qlie` | `qlie` | Qlie Engine Scenario script (.s) | ✔️ | ✔️ | ❌ | ❌ | ❌ | ❌ | ❌ | |
|
||||
| `qlie-abmp10` / `qlie-abmp11` / `qlie-abmp12` | `qlie-img` | Qlie Abmp10/11/12 image (.b) | ❌ | ❌ | ❌ | ❌ | ✔️ | ❌ | ❌ | |
|
||||
| `qlie-abmp10` / `qlie-abmp11` / `qlie-abmp12` | `qlie-img` | Qlie Abmp10/11/12 image (.b) | ❌ | ❌ | ❌ | ❌ | ✔️ | ✔️ | ✔️ | |
|
||||
|
||||
| Archive Type | Feature Name | Name | Unpack | Pack | Remarks |
|
||||
|---|---|---|---|---|---|
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//!Extensions for IO operations.
|
||||
use crate::scripts::base::ReadSeek;
|
||||
use crate::types::Encoding;
|
||||
use crate::utils::encoding::decode_to_string;
|
||||
use crate::utils::encoding::{decode_to_string, encode_string};
|
||||
use crate::utils::struct_pack::{StructPack, StructUnpack};
|
||||
use std::ffi::CString;
|
||||
use std::io::*;
|
||||
@@ -1180,6 +1180,20 @@ pub trait WriteExt {
|
||||
|
||||
/// Writes a C-style string (null-terminated) to the writer.
|
||||
fn write_cstring(&mut self, value: &CString) -> Result<()>;
|
||||
/// Writes a C-style string (null-terminated) from the reader with maximum length.
|
||||
/// * `data` is the string data to write.
|
||||
/// * `len` is the maximum length of the string to write. If the string is longer, it will be truncated if `truncate` is true otherwise an error is returned.
|
||||
/// * `encoding` specifies the encoding to use for the string.
|
||||
/// * `padding` indicates how to pad the string if it's shorter than `len`.
|
||||
/// * `truncate` indicates whether to truncate the string if it's longer than `len`.
|
||||
fn write_fstring(
|
||||
&mut self,
|
||||
data: &str,
|
||||
len: usize,
|
||||
encoding: Encoding,
|
||||
padding: u8,
|
||||
truncate: bool,
|
||||
) -> Result<()>;
|
||||
/// Write a struct to the writer.
|
||||
fn write_struct<V: StructPack>(
|
||||
&mut self,
|
||||
@@ -1264,6 +1278,42 @@ impl<T: Write> WriteExt for T {
|
||||
self.write_all(value.as_bytes_with_nul())
|
||||
}
|
||||
|
||||
fn write_fstring(
|
||||
&mut self,
|
||||
data: &str,
|
||||
len: usize,
|
||||
encoding: Encoding,
|
||||
padding: u8,
|
||||
truncate: bool,
|
||||
) -> Result<()> {
|
||||
let encoded = encode_string(encoding, data, true).map_err(|e| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("Encoding error: {}", e),
|
||||
)
|
||||
})?;
|
||||
let final_data = if encoded.len() > len {
|
||||
if truncate {
|
||||
&encoded[..len]
|
||||
} else {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"String length exceeds the specified length and truncation is disabled",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
&encoded
|
||||
};
|
||||
self.write_all(final_data)?;
|
||||
let padding_len = len.saturating_sub(final_data.len());
|
||||
if padding_len > 0 {
|
||||
for _ in 0..padding_len {
|
||||
self.write_u8(padding)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_struct<V: StructPack>(
|
||||
&mut self,
|
||||
value: &V,
|
||||
|
||||
@@ -56,6 +56,21 @@ impl ScriptBuilder for Abmp10ImageBuilder {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn can_create_file(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn create_file<'a>(
|
||||
&'a self,
|
||||
filename: &'a str,
|
||||
writer: Box<dyn WriteSeek + 'a>,
|
||||
encoding: Encoding,
|
||||
file_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<()> {
|
||||
create_file(filename, writer, encoding, file_encoding, config)
|
||||
}
|
||||
}
|
||||
|
||||
trait AbmpRes {
|
||||
@@ -66,6 +81,12 @@ trait AbmpRes {
|
||||
) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -103,6 +124,22 @@ impl AbmpRes for AbData {
|
||||
data: ResourceRef { index },
|
||||
})
|
||||
}
|
||||
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()> {
|
||||
data.write_fstring(&self.tag, 0x10, encoding, 0, false)?;
|
||||
let res = img
|
||||
.resources
|
||||
.get(self.data.index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
|
||||
data.write_u32(res.len() as u32)?;
|
||||
data.write_all(res)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -132,6 +169,20 @@ impl AbmpRes for AbImage10 {
|
||||
}
|
||||
Ok(AbImage10 { datas })
|
||||
}
|
||||
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()> {
|
||||
data.write_fstring("abimage10", 0x10, encoding, 0, false)?;
|
||||
data.write_u8(self.datas.len() as u8)?;
|
||||
for res in &self.datas {
|
||||
res.write_to(data, encoding, img)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -161,6 +212,20 @@ impl AbmpRes for AbSound10 {
|
||||
}
|
||||
Ok(AbSound10 { datas })
|
||||
}
|
||||
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()> {
|
||||
data.write_fstring("absound10", 0x10, encoding, 0, false)?;
|
||||
data.write_u8(self.datas.len() as u8)?;
|
||||
for res in &self.datas {
|
||||
res.write_to(data, encoding, img)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -227,6 +292,39 @@ impl AbmpRes for AbImgData15 {
|
||||
data: ResourceRef { index },
|
||||
})
|
||||
}
|
||||
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()> {
|
||||
data.write_fstring("abimgdat15", 0x10, encoding, 0, false)?;
|
||||
data.write_u32(self.version)?;
|
||||
let name_length = self.name.encode_utf16().count() as u16;
|
||||
let name = encode_string(Encoding::Utf16LE, &self.name, true)?;
|
||||
if name.len() != (name_length as usize) * 2 {
|
||||
anyhow::bail!("Name length mismatch when writing AbImgData15");
|
||||
}
|
||||
data.write_u16(name_length)?;
|
||||
data.write_all(&name)?;
|
||||
let internal_name = encode_string(encoding, &self.internal_name, true)?;
|
||||
data.write_u16(internal_name.len() as u16)?;
|
||||
data.write_all(&internal_name)?;
|
||||
data.write_u8(self.typ)?;
|
||||
let param_size = if self.version == 2 { 0x1d } else { 0x11 };
|
||||
if self.param.len() != param_size {
|
||||
anyhow::bail!("Param size mismatch when writing AbImgData15");
|
||||
}
|
||||
data.write_all(&self.param)?;
|
||||
let res = img
|
||||
.resources
|
||||
.get(self.data.index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
|
||||
data.write_u32(res.len() as u32)?;
|
||||
data.write_all(res)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -289,6 +387,33 @@ impl AbmpRes for AbImgData14 {
|
||||
data: ResourceRef { index },
|
||||
})
|
||||
}
|
||||
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()> {
|
||||
data.write_fstring("abimgdat14", 0x10, encoding, 0, false)?;
|
||||
let name = encode_string(encoding, &self.name, true)?;
|
||||
data.write_u16(name.len() as u16)?;
|
||||
data.write_all(&name)?;
|
||||
let internal_name = encode_string(encoding, &self.internal_name, true)?;
|
||||
data.write_u16(internal_name.len() as u16)?;
|
||||
data.write_all(&internal_name)?;
|
||||
data.write_u8(self.typ)?;
|
||||
if self.param.len() != 0x4C {
|
||||
anyhow::bail!("Param size mismatch when writing AbImgData14");
|
||||
}
|
||||
data.write_all(&self.param)?;
|
||||
let res = img
|
||||
.resources
|
||||
.get(self.data.index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
|
||||
data.write_u32(res.len() as u32)?;
|
||||
data.write_all(res)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -351,6 +476,33 @@ impl AbmpRes for AbImgData13 {
|
||||
data: ResourceRef { index },
|
||||
})
|
||||
}
|
||||
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()> {
|
||||
data.write_fstring("abimgdat13", 0x10, encoding, 0, false)?;
|
||||
let name = encode_string(encoding, &self.name, true)?;
|
||||
data.write_u16(name.len() as u16)?;
|
||||
data.write_all(&name)?;
|
||||
let internal_name = encode_string(encoding, &self.internal_name, true)?;
|
||||
data.write_u16(internal_name.len() as u16)?;
|
||||
data.write_all(&internal_name)?;
|
||||
data.write_u8(self.typ)?;
|
||||
if self.param.len() != 0xC {
|
||||
anyhow::bail!("Param size mismatch when writing AbImgData13");
|
||||
}
|
||||
data.write_all(&self.param)?;
|
||||
let res = img
|
||||
.resources
|
||||
.get(self.data.index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
|
||||
data.write_u32(res.len() as u32)?;
|
||||
data.write_all(res)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -399,6 +551,33 @@ impl AbmpRes for AbSndData12 {
|
||||
data: ResourceRef { index },
|
||||
})
|
||||
}
|
||||
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()> {
|
||||
data.write_fstring("absnddat12", 0x10, encoding, 0, false)?;
|
||||
data.write_u32(self.version)?;
|
||||
let name_length = self.name.encode_utf16().count() as u16;
|
||||
let name = encode_string(Encoding::Utf16LE, &self.name, true)?;
|
||||
if name.len() != (name_length as usize) * 2 {
|
||||
anyhow::bail!("Name length mismatch when writing AbSndData12");
|
||||
}
|
||||
data.write_u16(name_length)?;
|
||||
data.write_all(&name)?;
|
||||
let internal_name = encode_string(encoding, &self.internal_name, true)?;
|
||||
data.write_u16(internal_name.len() as u16)?;
|
||||
data.write_all(&internal_name)?;
|
||||
let res = img
|
||||
.resources
|
||||
.get(self.data.index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
|
||||
data.write_u32(res.len() as u32)?;
|
||||
data.write_all(res)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -444,6 +623,28 @@ impl AbmpRes for AbSndData11 {
|
||||
data: ResourceRef { index },
|
||||
})
|
||||
}
|
||||
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()> {
|
||||
data.write_fstring("absnddat11", 0x10, encoding, 0, false)?;
|
||||
let name = encode_string(encoding, &self.name, true)?;
|
||||
data.write_u16(name.len() as u16)?;
|
||||
data.write_all(&name)?;
|
||||
let internal_name = encode_string(encoding, &self.internal_name, true)?;
|
||||
data.write_u16(internal_name.len() as u16)?;
|
||||
data.write_all(&internal_name)?;
|
||||
let res = img
|
||||
.resources
|
||||
.get(self.data.index)
|
||||
.ok_or_else(|| anyhow::anyhow!("Resource index {} out of bounds", self.data.index))?;
|
||||
data.write_u32(res.len() as u32)?;
|
||||
data.write_all(res)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -496,6 +697,24 @@ impl AbmpRes for AbmpResource {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to<T: Write + Seek>(
|
||||
&self,
|
||||
data: &mut T,
|
||||
encoding: Encoding,
|
||||
img: &AbmpImage,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
AbmpResource::Data(res) => res.write_to(data, encoding, img),
|
||||
AbmpResource::Image10(res) => res.write_to(data, encoding, img),
|
||||
AbmpResource::ImgData15(res) => res.write_to(data, encoding, img),
|
||||
AbmpResource::ImgData14(res) => res.write_to(data, encoding, img),
|
||||
AbmpResource::ImgData13(res) => res.write_to(data, encoding, img),
|
||||
AbmpResource::Sound10(res) => res.write_to(data, encoding, img),
|
||||
AbmpResource::SndData12(res) => res.write_to(data, encoding, img),
|
||||
AbmpResource::SndData11(res) => res.write_to(data, encoding, img),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Qlie Abmp10/11/12 image
|
||||
@@ -559,6 +778,21 @@ impl AbmpImage {
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
pub fn dump_to<T: Write + Seek>(&self, mut writer: T, encoding: Encoding) -> Result<()> {
|
||||
writer.write_fstring(
|
||||
&format!("abmp1{}", (self.version - 10 + b'0') as char),
|
||||
16,
|
||||
encoding,
|
||||
0,
|
||||
false,
|
||||
)?;
|
||||
for data in &self.datas {
|
||||
data.write_to(&mut writer, encoding, self)?;
|
||||
}
|
||||
writer.write_all(&self.extra)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn to_image2(&self) -> AbmpImage2 {
|
||||
AbmpImage2 {
|
||||
version: self.version,
|
||||
@@ -567,6 +801,16 @@ impl AbmpImage {
|
||||
extra: self.extra.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_image2(img: &AbmpImage2) -> Self {
|
||||
AbmpImage {
|
||||
version: img.version,
|
||||
datas: img.datas.clone(),
|
||||
resources: Vec::new(),
|
||||
resource_filenames: Vec::new(),
|
||||
extra: img.extra.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -670,4 +914,58 @@ impl Script for Abmp10Image {
|
||||
file.write_all(&s)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn custom_import<'a>(
|
||||
&'a self,
|
||||
custom_filename: &'a str,
|
||||
file: Box<dyn WriteSeek + 'a>,
|
||||
encoding: Encoding,
|
||||
output_encoding: Encoding,
|
||||
) -> Result<()> {
|
||||
create_file(
|
||||
custom_filename,
|
||||
file,
|
||||
encoding,
|
||||
output_encoding,
|
||||
&self.config,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_file<'a>(
|
||||
filename: &str,
|
||||
mut writer: Box<dyn WriteSeek + 'a>,
|
||||
encoding: Encoding,
|
||||
file_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
) -> Result<()> {
|
||||
let data = crate::utils::files::read_file(filename)?;
|
||||
let s = decode_to_string(file_encoding, &data, true)?;
|
||||
let img2: AbmpImage2 = if config.custom_yaml {
|
||||
serde_yaml_ng::from_str(&s)?
|
||||
} else {
|
||||
serde_json::from_str(&s)?
|
||||
};
|
||||
let mut img = AbmpImage::from_image2(&img2);
|
||||
let mut base_path = std::path::PathBuf::from(filename);
|
||||
base_path.set_extension("");
|
||||
for res in &img2.resources {
|
||||
let path = base_path.join(&res.path);
|
||||
let buf = if res.ambp10 {
|
||||
let mut mem = MemWriter::new();
|
||||
create_file(
|
||||
&path.to_string_lossy(),
|
||||
Box::new(&mut mem),
|
||||
encoding,
|
||||
file_encoding,
|
||||
config,
|
||||
)?;
|
||||
mem.into_inner()
|
||||
} else {
|
||||
crate::utils::files::read_file(&path)?
|
||||
};
|
||||
img.resources.push(buf);
|
||||
}
|
||||
img.dump_to(&mut writer, encoding)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user