Files
msg-tool/src/scripts/qlie/image/abmp10.rs
2026-02-02 11:13:16 +08:00

1075 lines
32 KiB
Rust

//! Qlie Abmp10/11/12 image (.b)
use crate::ext::io::*;
use crate::scripts::base::*;
use crate::types::*;
use crate::utils::encoding::*;
use crate::utils::files::*;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::io::{Read, Seek, Write};
#[derive(Debug)]
/// Qlie Abmp10/11/12 image builder
pub struct Abmp10ImageBuilder {}
impl Abmp10ImageBuilder {
pub fn new() -> Self {
Self {}
}
}
impl ScriptBuilder for Abmp10ImageBuilder {
fn default_encoding(&self) -> Encoding {
Encoding::Cp932
}
fn build_script(
&self,
buf: Vec<u8>,
_filename: &str,
encoding: Encoding,
_archive_encoding: Encoding,
config: &ExtraConfig,
_archive: Option<&Box<dyn Script>>,
) -> Result<Box<dyn Script>> {
Ok(Box::new(Abmp10Image::new(
MemReader::new(buf),
encoding,
config,
)?))
}
fn extensions(&self) -> &'static [&'static str] {
&["b"]
}
fn script_type(&self) -> &'static ScriptType {
&ScriptType::QlieAbmp10
}
fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
if buf_len >= 6 && buf.starts_with(b"abmp1") {
let v = buf[5];
if v >= b'0' && v <= b'2' {
return Some(25);
}
}
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 {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> 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)]
struct ResourceRef {
index: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct AbData {
/// abdataxx xx = version
tag: String,
data: ResourceRef,
}
impl AbmpRes for AbData {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self>
where
Self: Sized,
{
let tag = data.read_fstring(0x10, encoding, true)?;
if !tag.starts_with("abdata") {
anyhow::bail!("Invalid AbData tag: {}", tag);
}
let size = data.read_u32()?;
let resource = data.read_exact_vec(size as usize)?;
img.resources.push(resource);
let index = img.resources.len() - 1;
img.resource_filenames.push(format!("{tag}_{index}"));
Ok(AbData {
tag,
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)]
/// tag: abimage10
struct AbImage10 {
datas: Vec<AbmpResource>,
}
impl AbmpRes for AbImage10 {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self>
where
Self: Sized,
{
let tag = data.read_fstring(0x10, encoding, true)?;
if tag != "abimage10" {
anyhow::bail!("Invalid AbImage10 tag: {}", tag);
}
let mut datas = Vec::new();
let count = data.read_u8()?;
for _ in 0..count {
let data = AbmpResource::read_from(data, encoding, img)?;
datas.push(data);
}
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)]
/// tag: absound10
struct AbSound10 {
datas: Vec<AbmpResource>,
}
impl AbmpRes for AbSound10 {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self>
where
Self: Sized,
{
let tag = data.read_fstring(0x10, encoding, true)?;
if tag != "absound10" {
anyhow::bail!("Invalid AbSound10 tag: {}", tag);
}
let mut datas = Vec::new();
let count = data.read_u8()?;
for _ in 0..count {
let data = AbmpResource::read_from(data, encoding, img)?;
datas.push(data);
}
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)]
/// tag: abimgdat15
struct AbImgData15 {
version: u32,
name: String,
internal_name: String,
typ: u8,
param: Vec<u8>,
data: ResourceRef,
}
impl AbmpRes for AbImgData15 {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self>
where
Self: Sized,
{
let tag = data.read_fstring(0x10, encoding, true)?;
if tag != "abimgdat15" {
anyhow::bail!("Invalid AbImgData15 tag: {}", tag);
}
let version = data.read_u32()?;
let name_length = data.read_u16()? as usize * 2;
let name = data.read_fstring(name_length, Encoding::Utf16LE, false)?;
let internal_name_length = data.read_u16()? as usize;
let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
let typ = data.read_u8()?;
let param_size = if version == 2 { 0x1d } else { 0x11 };
let param = data.read_exact_vec(param_size)?;
let size = data.read_u32()?;
let resource = data.read_exact_vec(size as usize)?;
img.resources.push(resource);
let index = img.resources.len() - 1;
let mut nname = if !name.is_empty() {
name.clone()
} else if !internal_name.is_empty() {
internal_name.clone()
} else {
format!("abimage15_{index}")
};
match typ {
0 => nname.push_str(".bmp"),
1 => nname.push_str(".jpg"),
2 | 3 => nname.push_str(".png"),
4 => nname.push_str(".m"),
5 => nname.push_str(".argb"),
6 => nname.push_str(".b"),
7 => nname.push_str(".ogv"),
8 => nname.push_str(".mdl"),
_ => {}
}
img.resource_filenames.push(nname);
Ok(AbImgData15 {
version,
name,
internal_name,
typ,
param,
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)]
/// tag: abimgdat14
struct AbImgData14 {
name: String,
internal_name: String,
typ: u8,
param: Vec<u8>,
data: ResourceRef,
}
impl AbmpRes for AbImgData14 {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self>
where
Self: Sized,
{
let tag = data.read_fstring(0x10, encoding, true)?;
if tag != "abimgdat14" {
anyhow::bail!("Invalid AbImgData14 tag: {}", tag);
}
let name_length = data.read_u16()? as usize;
let name = data.read_fstring(name_length, encoding, false)?;
let internal_name_length = data.read_u16()? as usize;
let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
let typ = data.read_u8()?;
let param = data.read_exact_vec(0x4C)?;
let size = data.read_u32()?;
let resource = data.read_exact_vec(size as usize)?;
img.resources.push(resource);
let index = img.resources.len() - 1;
let mut nname = if !name.is_empty() {
name.clone()
} else if !internal_name.is_empty() {
internal_name.clone()
} else {
format!("abimage14_{index}")
};
match typ {
0 => nname.push_str(".bmp"),
1 => nname.push_str(".jpg"),
2 | 3 => nname.push_str(".png"),
4 => nname.push_str(".m"),
5 => nname.push_str(".argb"),
6 => nname.push_str(".b"),
7 => nname.push_str(".ogv"),
8 => nname.push_str(".mdl"),
_ => {}
}
img.resource_filenames.push(nname);
Ok(AbImgData14 {
name,
internal_name,
typ,
param,
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)]
/// tag: abimgdat13
struct AbImgData13 {
name: String,
internal_name: String,
typ: u8,
param: Vec<u8>,
data: ResourceRef,
}
impl AbmpRes for AbImgData13 {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self>
where
Self: Sized,
{
let tag = data.read_fstring(0x10, encoding, true)?;
if tag != "abimgdat13" {
anyhow::bail!("Invalid AbImgData13 tag: {}", tag);
}
let name_length = data.read_u16()? as usize;
let name = data.read_fstring(name_length, encoding, false)?;
let internal_name_length = data.read_u16()? as usize;
let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
let typ = data.read_u8()?;
let param = data.read_exact_vec(0xC)?;
let size = data.read_u32()?;
let resource = data.read_exact_vec(size as usize)?;
img.resources.push(resource);
let index = img.resources.len() - 1;
let mut nname = if !name.is_empty() {
name.clone()
} else if !internal_name.is_empty() {
internal_name.clone()
} else {
format!("abimage13_{index}")
};
match typ {
0 => nname.push_str(".bmp"),
1 => nname.push_str(".jpg"),
2 | 3 => nname.push_str(".png"),
4 => nname.push_str(".m"),
5 => nname.push_str(".argb"),
6 => nname.push_str(".b"),
7 => nname.push_str(".ogv"),
8 => nname.push_str(".mdl"),
_ => {}
}
img.resource_filenames.push(nname);
Ok(AbImgData13 {
name,
internal_name,
typ,
param,
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)]
/// tag: abimgdat11
struct AbImgData11 {
name: String,
internal_name: String,
typ: u8,
data: ResourceRef,
}
impl AbmpRes for AbImgData11 {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self>
where
Self: Sized,
{
let tag = data.read_fstring(0x10, encoding, true)?;
if tag != "abimgdat11" {
anyhow::bail!("Invalid AbImgData11 tag: {}", tag);
}
let name_length = data.read_u16()? as usize;
let name = data.read_fstring(name_length, encoding, false)?;
let internal_name_length = data.read_u16()? as usize;
let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
let typ = data.read_u8()?;
let size = data.read_u32()?;
let resource = data.read_exact_vec(size as usize)?;
img.resources.push(resource);
let index = img.resources.len() - 1;
let mut nname = if !name.is_empty() {
name.clone()
} else if !internal_name.is_empty() {
internal_name.clone()
} else {
format!("abimage11_{index}")
};
match typ {
0 => nname.push_str(".bmp"),
1 => nname.push_str(".jpg"),
2 | 3 => nname.push_str(".png"),
4 => nname.push_str(".m"),
5 => nname.push_str(".argb"),
6 => nname.push_str(".b"),
7 => nname.push_str(".ogv"),
8 => nname.push_str(".mdl"),
_ => {}
}
img.resource_filenames.push(nname);
Ok(AbImgData11 {
name,
internal_name,
typ,
data: ResourceRef { index },
})
}
fn write_to<T: Write + Seek>(
&self,
data: &mut T,
encoding: Encoding,
img: &AbmpImage,
) -> Result<()> {
data.write_fstring("abimgdat11", 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)?;
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)]
/// tag: absnddat12
struct AbSndData12 {
version: u32,
name: String,
internal_name: String,
typ: u8,
data: ResourceRef,
}
impl AbmpRes for AbSndData12 {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self>
where
Self: Sized,
{
let tag = data.read_fstring(0x10, encoding, true)?;
if tag != "absnddat12" {
anyhow::bail!("Invalid AbSndData12 tag: {}", tag);
}
let version = data.read_u32()?;
let name_length = data.read_u16()? as usize * 2;
let name = data.read_fstring(name_length, Encoding::Utf16LE, false)?;
let internal_name_length = data.read_u16()? as usize;
let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
let typ = data.read_u8()?;
let size = data.read_u32()?;
let resource = data.read_exact_vec(size as usize)?;
img.resources.push(resource);
let index = img.resources.len() - 1;
let mut nname = if !name.is_empty() {
name.clone()
} else if !internal_name.is_empty() {
internal_name.clone()
} else {
format!("absnddat12_{index}")
};
match typ {
1 => nname.push_str(".ogg"),
_ => {}
}
img.resource_filenames.push(nname);
Ok(AbSndData12 {
version,
name,
internal_name,
typ,
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)?;
data.write_u8(self.typ)?;
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)]
/// tag: absnddat11
struct AbSndData11 {
name: String,
internal_name: String,
typ: u8,
data: ResourceRef,
}
impl AbmpRes for AbSndData11 {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self>
where
Self: Sized,
{
let tag = data.read_fstring(0x10, encoding, true)?;
if tag != "absnddat11" {
anyhow::bail!("Invalid AbSndData11 tag: {}", tag);
}
let name_length = data.read_u16()? as usize;
let name = data.read_fstring(name_length, encoding, false)?;
let internal_name_length = data.read_u16()? as usize;
let internal_name = data.read_fstring(internal_name_length, encoding, false)?;
let typ = data.read_u8()?;
let size = data.read_u32()?;
let resource = data.read_exact_vec(size as usize)?;
img.resources.push(resource);
let index = img.resources.len() - 1;
let mut nname = if !name.is_empty() {
name.clone()
} else if !internal_name.is_empty() {
internal_name.clone()
} else {
format!("absnddat11_{index}")
};
match typ {
1 => nname.push_str(".ogg"),
_ => {}
}
img.resource_filenames.push(nname);
Ok(AbSndData11 {
name,
internal_name,
typ,
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)?;
data.write_u8(self.typ)?;
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)]
#[serde(tag = "@type")]
enum AbmpResource {
Data(AbData),
Image10(AbImage10),
ImgData15(AbImgData15),
ImgData14(AbImgData14),
ImgData13(AbImgData13),
ImgData11(AbImgData11),
Sound10(AbSound10),
SndData12(AbSndData12),
SndData11(AbSndData11),
}
impl AbmpRes for AbmpResource {
fn read_from<T: Read + Seek>(
data: &mut T,
encoding: Encoding,
img: &mut AbmpImage,
) -> Result<Self> {
let tag = data.peek_fstring(0x10, encoding, true)?;
if tag.starts_with("abdata") {
return Ok(AbmpResource::Data(AbData::read_from(data, encoding, img)?));
}
match tag.as_str() {
"abimage10" => Ok(AbmpResource::Image10(AbImage10::read_from(
data, encoding, img,
)?)),
"abimgdat15" => Ok(AbmpResource::ImgData15(AbImgData15::read_from(
data, encoding, img,
)?)),
"abimgdat14" => Ok(AbmpResource::ImgData14(AbImgData14::read_from(
data, encoding, img,
)?)),
"abimgdat13" => Ok(AbmpResource::ImgData13(AbImgData13::read_from(
data, encoding, img,
)?)),
"abimgdat11" => Ok(AbmpResource::ImgData11(AbImgData11::read_from(
data, encoding, img,
)?)),
"absound10" => Ok(AbmpResource::Sound10(AbSound10::read_from(
data, encoding, img,
)?)),
"absnddat11" => Ok(AbmpResource::SndData11(AbSndData11::read_from(
data, encoding, img,
)?)),
"absnddat12" => Ok(AbmpResource::SndData12(AbSndData12::read_from(
data, encoding, img,
)?)),
_ => {
anyhow::bail!("Unknown Abmp resource tag: {}", tag);
}
}
}
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::ImgData11(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
#[derive(Clone, Debug, Serialize, Deserialize)]
struct AbmpImage {
/// Valid version: 10, 11, 12
version: u8,
datas: Vec<AbmpResource>,
extra: Vec<u8>,
#[serde(skip)]
resources: Vec<Vec<u8>>,
/// Just used for dump
#[serde(skip)]
resource_filenames: Vec<String>,
}
fn is_false(b: &bool) -> bool {
!*b
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct Resource {
path: String,
#[serde(skip_serializing_if = "is_false", default)]
ambp10: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct AbmpImage2 {
version: u8,
datas: Vec<AbmpResource>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
extra: Vec<u8>,
resources: Vec<Resource>,
}
impl AbmpImage {
pub fn new_from<T: Read + Seek>(reader: &mut T, encoding: Encoding) -> Result<Self> {
let magic = reader.read_fstring(16, encoding, true)?;
if !magic.starts_with("abmp1") {
anyhow::bail!("Not a valid Abmp image");
}
let version = magic.as_bytes()[5] - b'0' + 10;
let mut img = AbmpImage {
version,
datas: Vec::new(),
resources: Vec::new(),
resource_filenames: Vec::new(),
extra: Vec::new(),
};
let len = reader.stream_length()?;
let mut pos = reader.stream_position()?;
while pos < len - 16 {
let data = AbmpResource::read_from(reader, encoding, &mut img)?;
img.datas.push(data);
pos = reader.stream_position()?;
}
if pos < len {
img.extra = reader.read_exact_vec((len - pos) as usize)?;
}
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,
datas: self.datas.clone(),
resources: Vec::new(),
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)]
pub struct Abmp10Image {
img: AbmpImage,
encoding: Encoding,
config: ExtraConfig,
}
impl Abmp10Image {
pub fn new<T: Read + Seek>(
mut data: T,
encoding: Encoding,
config: &ExtraConfig,
) -> Result<Self> {
let img = AbmpImage::new_from(&mut data, encoding)?;
Ok(Abmp10Image {
img,
encoding,
config: config.clone(),
})
}
fn output_resource(
&self,
folder_path: &std::path::PathBuf,
path: String,
data: &[u8],
encoding: Encoding,
) -> Result<Resource> {
let mut res = Resource {
path,
ambp10: false,
};
if self.config.qlie_abmp10_process_abmp10
&& data.len() > 6
&& data.starts_with(b"abmp1")
&& data[5] >= b'0'
&& data[5] <= b'2'
{
res.ambp10 = true;
let another = Abmp10Image::new(MemReaderRef::new(data), self.encoding, &self.config)?;
let mut np = std::path::PathBuf::from(&res.path);
np.set_extension(another.custom_output_extension());
res.path = np.to_string_lossy().to_string();
let path = folder_path.join(&res.path);
make_sure_dir_exists(&path)?;
another.custom_export(&path, encoding)?;
} else {
let path = folder_path.join(&res.path);
make_sure_dir_exists(&path)?;
std::fs::write(&path, data)?;
}
Ok(res)
}
}
impl Script for Abmp10Image {
fn default_output_script_type(&self) -> OutputScriptType {
OutputScriptType::Custom
}
fn is_output_supported(&self, output: OutputScriptType) -> bool {
matches!(output, OutputScriptType::Custom)
}
fn default_format_type(&self) -> FormatOptions {
FormatOptions::None
}
fn custom_output_extension<'a>(&'a self) -> &'a str {
if self.config.custom_yaml {
"yaml"
} else {
"json"
}
}
fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
let file = std::fs::File::create(filename)?;
let mut file = std::io::BufWriter::new(file);
let mut img = self.img.to_image2();
let mut base_path = filename.to_path_buf();
base_path.set_extension("");
for (res, res_name) in self
.img
.resources
.iter()
.zip(self.img.resource_filenames.iter())
{
let res_name = sanitize_path(res_name);
let res = self.output_resource(&base_path, res_name, res, encoding)?;
img.resources.push(res);
}
let s = if self.config.custom_yaml {
serde_yaml_ng::to_string(&img)?
} else {
serde_json::to_string_pretty(&img)?
};
let s = encode_string(encoding, &s, false)?;
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(())
}