mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 04:48:54 +08:00
Add psb rl compressed image support
This commit is contained in:
@@ -2,3 +2,4 @@
|
||||
pub mod dref;
|
||||
pub mod pimg;
|
||||
pub mod psb;
|
||||
pub mod rle;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Basic Handle for all emote PSB files.
|
||||
use super::rle::*;
|
||||
use crate::ext::io::*;
|
||||
use crate::ext::psb::*;
|
||||
use crate::scripts::base::*;
|
||||
@@ -128,7 +129,11 @@ impl Psb {
|
||||
path: String,
|
||||
data: &[u8],
|
||||
) -> Result<Resource> {
|
||||
let mut res = Resource { path, tlg: None };
|
||||
let mut res = Resource {
|
||||
path,
|
||||
tlg: None,
|
||||
rle: None,
|
||||
};
|
||||
if self.config.psb_process_tlg && is_valid_tlg(&data) {
|
||||
let tlg = load_tlg(MemReaderRef::new(&data))?;
|
||||
res.tlg = Some(TlgInfo::from_tlg(&tlg, self.encoding));
|
||||
@@ -159,6 +164,39 @@ impl Psb {
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn output_rle_resource(
|
||||
&self,
|
||||
folder_path: &std::path::PathBuf,
|
||||
path: String,
|
||||
data: &[u8],
|
||||
width: i64,
|
||||
height: i64,
|
||||
) -> Result<Resource> {
|
||||
let mut res = Resource {
|
||||
path,
|
||||
tlg: None,
|
||||
rle: Some(RLPixelInfo { width, height }),
|
||||
};
|
||||
let decompressed = rl_decompress(MemReaderRef::new(data), 4, None)?;
|
||||
let outtype = self.config.image_type.unwrap_or(ImageOutputType::Png);
|
||||
res.path = {
|
||||
let mut pb = std::path::PathBuf::from(&res.path);
|
||||
pb.set_extension(outtype.as_ref());
|
||||
pb.to_string_lossy().to_string()
|
||||
};
|
||||
let path = folder_path.join(&res.path);
|
||||
make_sure_dir_exists(&path)?;
|
||||
let img = ImageData {
|
||||
width: width as u32,
|
||||
height: height as u32,
|
||||
color_type: ImageColorType::Bgra,
|
||||
depth: 8,
|
||||
data: decompressed,
|
||||
};
|
||||
encode_img(img, outtype, &path.to_string_lossy(), &self.config)?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
@@ -210,11 +248,19 @@ impl TlgInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct RLPixelInfo {
|
||||
width: i64,
|
||||
height: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct Resource {
|
||||
path: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
tlg: Option<TlgInfo>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
rle: Option<RLPixelInfo>,
|
||||
}
|
||||
|
||||
impl Script for Psb {
|
||||
@@ -245,10 +291,35 @@ impl Script for Psb {
|
||||
};
|
||||
for (i, data) in self.psb.resources().iter().enumerate() {
|
||||
let i = i as u64;
|
||||
let res_name = self
|
||||
.psb
|
||||
.root()
|
||||
.find_resource_key(i, vec![])
|
||||
let res_path = self.psb.root().find_resource_key(i, vec![]);
|
||||
if let Some(path) = &res_path {
|
||||
if path.len() >= 2 && *path.last().unwrap() == "pixel" {
|
||||
let pb_data = self.psb.root();
|
||||
let mut pb_data = &pb_data[*path.first().unwrap()];
|
||||
for p in path.iter().take(path.len() - 1).skip(1) {
|
||||
pb_data = &pb_data[*p];
|
||||
}
|
||||
let width = pb_data["width"].as_i64();
|
||||
let height = pb_data["height"].as_i64();
|
||||
let compress = pb_data["compress"].as_str();
|
||||
if let (Some(w), Some(h), Some(c)) = (width, height, compress) {
|
||||
if c == "RL" {
|
||||
let res_name: Vec<_> = path
|
||||
.iter()
|
||||
.take(path.len() - 1)
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
let res_name = res_name.join("/");
|
||||
let res_name = sanitize_path(&res_name);
|
||||
let res =
|
||||
self.output_rle_resource(&folder_path, res_name, data, w, h)?;
|
||||
resources.push(res);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let res_name = res_path
|
||||
.map(|s| s.join("/"))
|
||||
.unwrap_or(format!("res_{}", i));
|
||||
let res_name = sanitize_path(&res_name);
|
||||
@@ -325,6 +396,42 @@ fn read_resource(
|
||||
let mut writer = MemWriter::new();
|
||||
save_tlg(&tlg, &mut writer)?;
|
||||
Ok(writer.into_inner())
|
||||
} else if let Some(rle) = &res.rle {
|
||||
let path = folder_path.join(&res.path);
|
||||
let imgfmt = ImageOutputType::try_from(path.as_path())?;
|
||||
let mut img = decode_img(imgfmt, &path.to_string_lossy())?;
|
||||
if img.depth != 8 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Only 8-bit images are supported for RLE conversion"
|
||||
));
|
||||
}
|
||||
if img.color_type == ImageColorType::Rgba {
|
||||
convert_rgba_to_bgra(&mut img)?;
|
||||
} else if img.color_type == ImageColorType::Rgb {
|
||||
convert_rgb_to_bgr(&mut img)?;
|
||||
convert_bgr_to_bgra(&mut img)?;
|
||||
} else if img.color_type == ImageColorType::Bgr {
|
||||
convert_bgr_to_bgra(&mut img)?;
|
||||
}
|
||||
if img.color_type != ImageColorType::Bgra {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Only BGRA images are supported for RLE conversion"
|
||||
));
|
||||
}
|
||||
if img.width as i64 != rle.width {
|
||||
eprintln!(
|
||||
"Warning: Image width {} does not match RLE width {}",
|
||||
img.width, rle.width
|
||||
);
|
||||
}
|
||||
if img.height as i64 != rle.height {
|
||||
eprintln!(
|
||||
"Warning: Image height {} does not match RLE height {}",
|
||||
img.height, rle.height
|
||||
);
|
||||
}
|
||||
let compressed = rl_compress(MemReaderRef::new(&img.data), 4)?;
|
||||
Ok(compressed)
|
||||
} else {
|
||||
let path = folder_path.join(&res.path);
|
||||
Ok(std::fs::read(&path)?)
|
||||
|
||||
118
src/scripts/emote/rle.rs
Normal file
118
src/scripts/emote/rle.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
//! RL Encode used in mtn files
|
||||
use crate::ext::io::*;
|
||||
use anyhow::Result;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
const LZSS_LOOKAHED: usize = 1 << 7;
|
||||
|
||||
/// Decompress RL data
|
||||
/// * `align` - alignment. usually 4
|
||||
/// * `actual_size` - if known, set it to preallocate memory
|
||||
pub fn rl_decompress<T: Read + Seek>(
|
||||
mut input: T,
|
||||
align: usize,
|
||||
actual_size: Option<usize>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let mut output = if let Some(size) = actual_size {
|
||||
Vec::with_capacity(size)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut readed = input.stream_position()?;
|
||||
let len = input.stream_length()?;
|
||||
while readed < len {
|
||||
let current = input.read_u8()? as usize;
|
||||
readed += 1;
|
||||
let count;
|
||||
if (current & LZSS_LOOKAHED) != 0 {
|
||||
count = (current ^ LZSS_LOOKAHED) + 3;
|
||||
let buf = input.read_exact_vec(align)?;
|
||||
readed += align as u64;
|
||||
for _ in 0..count {
|
||||
output.extend_from_slice(&buf);
|
||||
}
|
||||
} else {
|
||||
count = (current + 1) * align;
|
||||
let buf = input.read_exact_vec(count)?;
|
||||
readed += count as u64;
|
||||
output.extend_from_slice(&buf);
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn compress_bound<T: Read + Seek>(input: &mut T, align: usize) -> Result<(usize, u8, Vec<u8>)> {
|
||||
let pos = input.stream_position()?;
|
||||
let mut curpos = pos;
|
||||
let len = input.stream_length()?;
|
||||
let mut buffer = vec![0u8; align];
|
||||
let mut tmp = vec![0u8; align];
|
||||
input.read_exact(&mut buffer)?;
|
||||
curpos += align as u64;
|
||||
let mut count = 1usize;
|
||||
for _ in 1..LZSS_LOOKAHED + 2 {
|
||||
if curpos >= len {
|
||||
break;
|
||||
}
|
||||
input.read_exact(&mut tmp)?;
|
||||
curpos += align as u64;
|
||||
if buffer == tmp {
|
||||
count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
input.seek(SeekFrom::Start(pos))?;
|
||||
if count >= 3 {
|
||||
return Ok((count, (count - 3) as u8 | LZSS_LOOKAHED as u8, buffer));
|
||||
}
|
||||
Ok((0, 0, buffer))
|
||||
}
|
||||
|
||||
fn compress_bound_np<T: Read + Seek>(input: &mut T, align: usize) -> Result<(usize, u8)> {
|
||||
let pos = input.stream_position()?;
|
||||
let mut curpos = pos;
|
||||
let len = input.stream_length()?;
|
||||
input.seek_relative(align as i64)?;
|
||||
curpos += align as u64;
|
||||
let mut count = 1;
|
||||
for _ in 1..LZSS_LOOKAHED {
|
||||
if curpos >= len {
|
||||
break;
|
||||
}
|
||||
let (ncount, _cmd, _buf) = compress_bound(input, align)?;
|
||||
if ncount == 0 {
|
||||
input.seek_relative(align as i64)?;
|
||||
count += 1;
|
||||
curpos += align as u64;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
input.seek(SeekFrom::Start(pos))?;
|
||||
Ok((count, (count - 1) as u8))
|
||||
}
|
||||
|
||||
/// Compress data using RL
|
||||
/// * `align` - alignment. usually 4
|
||||
pub fn rl_compress<T: Read + Seek>(mut input: T, align: usize) -> Result<Vec<u8>> {
|
||||
let mut output = Vec::new();
|
||||
let len = input.stream_length()?;
|
||||
let mut readed = input.stream_position()?;
|
||||
while readed < len {
|
||||
let (count, cmd, buf) = compress_bound(&mut input, align)?;
|
||||
if count > 0 {
|
||||
output.push(cmd);
|
||||
output.extend_from_slice(&buf);
|
||||
readed += (count * align) as u64;
|
||||
input.seek_relative((count * align) as i64)?;
|
||||
} else {
|
||||
let (ncount, ncmd) = compress_bound_np(&mut input, align)?;
|
||||
output.push(ncmd);
|
||||
let buf = input.read_exact_vec(ncount * align)?;
|
||||
output.extend_from_slice(&buf);
|
||||
readed += (ncount * align) as u64;
|
||||
}
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
Reference in New Issue
Block a user