mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 12:58:45 +08:00
Add support to create BGI sysgrp image
This commit is contained in:
@@ -97,6 +97,11 @@ pub struct Arg {
|
||||
#[arg(long, global = true)]
|
||||
/// Detect all files in BGI archive as SysGrp Images. By default, only files which name is `sysgrp.arc` will enabled this.
|
||||
pub bgi_is_sysgrp_arc: Option<bool>,
|
||||
#[cfg(feature = "bgi-img")]
|
||||
#[arg(long, global = true)]
|
||||
/// Whether to create scrambled SysGrp images. When in import mode, the default value depends on the original image.
|
||||
/// When in creation mode, it is not enabled by default.
|
||||
pub bgi_img_scramble: Option<bool>,
|
||||
#[command(subcommand)]
|
||||
/// Command
|
||||
pub command: Command,
|
||||
|
||||
66
src/main.rs
66
src/main.rs
@@ -901,6 +901,35 @@ pub fn import_script(
|
||||
arch.write_header()?;
|
||||
return Ok(types::ScriptResult::Ok);
|
||||
}
|
||||
#[cfg(feature = "image")]
|
||||
if script.is_image() {
|
||||
let out_type = arg.image_type.unwrap_or(types::ImageOutputType::Png);
|
||||
let out_f = if is_dir {
|
||||
let f = std::path::PathBuf::from(filename);
|
||||
let mut pb = std::path::PathBuf::from(&imp_cfg.output);
|
||||
if let Some(fname) = f.file_name() {
|
||||
pb.push(fname);
|
||||
}
|
||||
pb.set_extension(out_type.as_ref());
|
||||
pb.to_string_lossy().into_owned()
|
||||
} else {
|
||||
imp_cfg.output.clone()
|
||||
};
|
||||
let data = utils::img::decode_img(out_type, &out_f)?;
|
||||
let patched_f = if is_dir {
|
||||
let f = std::path::PathBuf::from(filename);
|
||||
let mut pb = std::path::PathBuf::from(&imp_cfg.patched);
|
||||
if let Some(fname) = f.file_name() {
|
||||
pb.push(fname);
|
||||
}
|
||||
pb.set_extension(builder.extensions().first().unwrap_or(&""));
|
||||
pb.to_string_lossy().into_owned()
|
||||
} else {
|
||||
imp_cfg.patched.clone()
|
||||
};
|
||||
script.import_image_filename(data, &patched_f)?;
|
||||
return Ok(types::ScriptResult::Ok);
|
||||
}
|
||||
let mut of = match &arg.output_type {
|
||||
Some(t) => t.clone(),
|
||||
None => script.default_output_script_type(),
|
||||
@@ -1131,7 +1160,12 @@ pub fn unpack_archive(
|
||||
Ok(types::ScriptResult::Ok)
|
||||
}
|
||||
|
||||
pub fn create_file(input: &str, output: Option<&str>, arg: &args::Arg) -> anyhow::Result<()> {
|
||||
pub fn create_file(
|
||||
input: &str,
|
||||
output: Option<&str>,
|
||||
arg: &args::Arg,
|
||||
_config: &types::ExtraConfig,
|
||||
) -> anyhow::Result<()> {
|
||||
let typ = match &arg.script_type {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
@@ -1143,6 +1177,32 @@ pub fn create_file(input: &str, output: Option<&str>, arg: &args::Arg) -> anyhow
|
||||
.find(|b| b.script_type() == typ)
|
||||
.ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
if builder.is_image() {
|
||||
if !builder.can_create_image_file() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Script type {:?} does not support image file creation",
|
||||
typ
|
||||
));
|
||||
}
|
||||
let data =
|
||||
utils::img::decode_img(arg.image_type.unwrap_or(types::ImageOutputType::Png), input)?;
|
||||
let output = match output {
|
||||
Some(output) => output.to_string(),
|
||||
None => {
|
||||
let mut pb = std::path::PathBuf::from(input);
|
||||
let ext = builder.extensions().first().unwrap_or(&"unk");
|
||||
pb.set_extension(ext);
|
||||
if pb.to_string_lossy() == input {
|
||||
pb.set_extension(format!("{}.{}", ext, ext));
|
||||
}
|
||||
pb.to_string_lossy().into_owned()
|
||||
}
|
||||
};
|
||||
builder.create_image_file_filename(data, &output, _config)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !builder.can_create_file() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Script type {:?} does not support file creation",
|
||||
@@ -1196,6 +1256,8 @@ fn main() {
|
||||
image_type: arg.image_type.clone(),
|
||||
#[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
|
||||
bgi_is_sysgrp_arc: arg.bgi_is_sysgrp_arc.clone(),
|
||||
#[cfg(feature = "bgi-img")]
|
||||
bgi_img_scramble: arg.bgi_img_scramble.clone(),
|
||||
};
|
||||
match &arg.command {
|
||||
args::Command::Export { input, output } => {
|
||||
@@ -1329,7 +1391,7 @@ fn main() {
|
||||
}
|
||||
}
|
||||
args::Command::Create { input, output } => {
|
||||
let re = create_file(input, output.as_ref().map(|s| s.as_str()), &arg);
|
||||
let re = create_file(input, output.as_ref().map(|s| s.as_str()), &arg, &cfg);
|
||||
if let Err(e) = re {
|
||||
COUNTER.inc_error();
|
||||
eprintln!("Error creating file: {}", e);
|
||||
|
||||
@@ -124,6 +124,7 @@ pub trait ScriptBuilder: std::fmt::Debug {
|
||||
&'a self,
|
||||
_data: ImageData,
|
||||
_writer: Box<dyn WriteSeek + 'a>,
|
||||
_options: &ExtraConfig,
|
||||
) -> Result<()> {
|
||||
Err(anyhow::anyhow!(
|
||||
"This script type does not support creating an image file."
|
||||
@@ -131,10 +132,15 @@ pub trait ScriptBuilder: std::fmt::Debug {
|
||||
}
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
fn create_image_file_filename(&self, data: ImageData, filename: &str) -> Result<()> {
|
||||
fn create_image_file_filename(
|
||||
&self,
|
||||
data: ImageData,
|
||||
filename: &str,
|
||||
options: &ExtraConfig,
|
||||
) -> Result<()> {
|
||||
let f = std::fs::File::create(filename)?;
|
||||
let f = std::io::BufWriter::new(f);
|
||||
self.create_image_file(data, Box::new(f))
|
||||
self.create_image_file(data, Box::new(f), options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::ext::io::*;
|
||||
use crate::scripts::base::*;
|
||||
use crate::types::*;
|
||||
use crate::utils::img::*;
|
||||
use anyhow::Result;
|
||||
|
||||
fn try_parse(buf: &[u8]) -> Result<u8> {
|
||||
@@ -72,6 +73,19 @@ impl ScriptBuilder for BgiImageBuilder {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn can_create_image_file(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn create_image_file<'a>(
|
||||
&'a self,
|
||||
data: ImageData,
|
||||
writer: Box<dyn WriteSeek + 'a>,
|
||||
options: &ExtraConfig,
|
||||
) -> Result<()> {
|
||||
create_image(data, writer, options.bgi_img_scramble.unwrap_or(false))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -81,10 +95,73 @@ pub struct BgiImage {
|
||||
height: u32,
|
||||
color_type: ImageColorType,
|
||||
is_scrambled: bool,
|
||||
opt_is_scrambled: Option<bool>,
|
||||
}
|
||||
|
||||
fn create_image<'a>(
|
||||
mut data: ImageData,
|
||||
mut writer: Box<dyn WriteSeek + 'a>,
|
||||
scrambled: bool,
|
||||
) -> Result<()> {
|
||||
writer.write_u16(data.width as u16)?;
|
||||
writer.write_u16(data.height as u16)?;
|
||||
if data.depth != 8 {
|
||||
return Err(anyhow::anyhow!("Unsupported image depth: {}", data.depth));
|
||||
}
|
||||
match data.color_type {
|
||||
ImageColorType::Bgr => {}
|
||||
ImageColorType::Bgra => {}
|
||||
ImageColorType::Grayscale => {}
|
||||
ImageColorType::Rgb => {
|
||||
convert_rgb_to_bgr(&mut data)?;
|
||||
}
|
||||
ImageColorType::Rgba => {
|
||||
convert_rgba_to_bgra(&mut data)?;
|
||||
}
|
||||
}
|
||||
let bpp = data.color_type.bpp(8);
|
||||
writer.write_u16(bpp)?;
|
||||
let flag = if scrambled { 1 } else { 0 };
|
||||
writer.write_u16(flag)?;
|
||||
writer.write_u64(0)?; // Padding
|
||||
let stride = data.width as usize * ((data.color_type.bpp(8) as usize + 7) / 8);
|
||||
let buf_size = stride * data.height as usize;
|
||||
if scrambled {
|
||||
let bpp = data.color_type.bpp(1) as usize;
|
||||
for i in 0..bpp {
|
||||
let mut dst = i;
|
||||
let mut incr = 0u8;
|
||||
let mut h = data.height;
|
||||
while h > 0 {
|
||||
for _ in 0..data.width {
|
||||
writer.write_u8(data.data[dst].wrapping_sub(incr))?;
|
||||
incr = data.data[dst];
|
||||
dst += bpp;
|
||||
}
|
||||
h -= 1;
|
||||
if h == 0 {
|
||||
break;
|
||||
}
|
||||
dst += stride;
|
||||
let mut pos = dst;
|
||||
for _ in 0..data.width {
|
||||
pos -= bpp;
|
||||
writer.write_u8(data.data[pos].wrapping_sub(incr))?;
|
||||
incr = data.data[pos];
|
||||
}
|
||||
h -= 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// PNG sometimes return more padding data than expected
|
||||
// We will write only the required size
|
||||
writer.write_all(&data.data[..buf_size])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl BgiImage {
|
||||
pub fn new(buf: Vec<u8>, _config: &ExtraConfig) -> Result<Self> {
|
||||
pub fn new(buf: Vec<u8>, config: &ExtraConfig) -> Result<Self> {
|
||||
let mut reader = MemReader::new(buf);
|
||||
let width = reader.read_u16()? as u32;
|
||||
let height = reader.read_u16()? as u32;
|
||||
@@ -108,6 +185,7 @@ impl BgiImage {
|
||||
height,
|
||||
color_type,
|
||||
is_scrambled,
|
||||
opt_is_scrambled: config.bgi_img_scramble,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -126,14 +204,41 @@ impl Script for BgiImage {
|
||||
}
|
||||
|
||||
fn export_image(&self) -> Result<ImageData> {
|
||||
let stride = self.width as usize * ((self.color_type.bbp(8) as usize + 7) / 8);
|
||||
let stride = self.width as usize * ((self.color_type.bpp(8) as usize + 7) / 8);
|
||||
let buf_size = stride * self.height as usize;
|
||||
if self.is_scrambled {
|
||||
return Err(anyhow::anyhow!("Scrambled images are not supported"));
|
||||
}
|
||||
let mut data = Vec::with_capacity(buf_size);
|
||||
data.resize(buf_size, 0);
|
||||
self.data.cpeek_extract_at(0x10, &mut data)?;
|
||||
if self.is_scrambled {
|
||||
let mut reader = self.data.to_ref();
|
||||
reader.pos = 0x10;
|
||||
let bpp = self.color_type.bpp(1) as usize;
|
||||
for i in 0..bpp {
|
||||
let mut dst = i;
|
||||
let mut incr = 0u8;
|
||||
let mut h = self.height;
|
||||
while h > 0 {
|
||||
for _ in 0..self.width {
|
||||
incr = incr.wrapping_add(reader.read_u8()?);
|
||||
data[dst] = incr;
|
||||
dst += bpp;
|
||||
}
|
||||
h -= 1;
|
||||
if h == 0 {
|
||||
break;
|
||||
}
|
||||
dst += stride;
|
||||
let mut pos = dst;
|
||||
for _ in 0..self.width {
|
||||
pos -= bpp;
|
||||
incr = incr.wrapping_add(reader.read_u8()?);
|
||||
data[pos] = incr;
|
||||
}
|
||||
h -= 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.data.cpeek_extract_at(0x10, &mut data)?;
|
||||
}
|
||||
Ok(ImageData {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
@@ -142,4 +247,12 @@ impl Script for BgiImage {
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
fn import_image<'a>(&'a self, data: ImageData, file: Box<dyn WriteSeek + 'a>) -> Result<()> {
|
||||
create_image(
|
||||
data,
|
||||
file,
|
||||
self.opt_is_scrambled.unwrap_or(self.is_scrambled),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +201,8 @@ pub struct ExtraConfig {
|
||||
pub image_type: Option<ImageOutputType>,
|
||||
#[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
|
||||
pub bgi_is_sysgrp_arc: Option<bool>,
|
||||
#[cfg(feature = "bgi-img")]
|
||||
pub bgi_img_scramble: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -320,7 +322,7 @@ pub enum ImageColorType {
|
||||
|
||||
#[cfg(feature = "image")]
|
||||
impl ImageColorType {
|
||||
pub fn bbp(&self, depth: u8) -> u16 {
|
||||
pub fn bpp(&self, depth: u8) -> u16 {
|
||||
match self {
|
||||
ImageColorType::Grayscale => depth as u16,
|
||||
ImageColorType::Rgb => depth as u16 * 3,
|
||||
|
||||
@@ -52,6 +52,42 @@ pub fn convert_bgra_to_rgba(data: &mut ImageData) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn convert_rgb_to_bgr(data: &mut ImageData) -> Result<()> {
|
||||
if data.color_type != ImageColorType::Rgb {
|
||||
return Err(anyhow::anyhow!("Image is not RGB"));
|
||||
}
|
||||
if data.depth != 8 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"RGB to BGR conversion only supports 8-bit depth"
|
||||
));
|
||||
}
|
||||
for i in (0..data.data.len()).step_by(3) {
|
||||
let r = data.data[i];
|
||||
data.data[i] = data.data[i + 2];
|
||||
data.data[i + 2] = r;
|
||||
}
|
||||
data.color_type = ImageColorType::Bgr;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn convert_rgba_to_bgra(data: &mut ImageData) -> Result<()> {
|
||||
if data.color_type != ImageColorType::Rgba {
|
||||
return Err(anyhow::anyhow!("Image is not RGBA"));
|
||||
}
|
||||
if data.depth != 8 {
|
||||
return Err(anyhow::anyhow!(
|
||||
"RGBA to BGRA conversion only supports 8-bit depth"
|
||||
));
|
||||
}
|
||||
for i in (0..data.data.len()).step_by(4) {
|
||||
let r = data.data[i];
|
||||
data.data[i] = data.data[i + 2];
|
||||
data.data[i + 2] = r;
|
||||
}
|
||||
data.color_type = ImageColorType::Bgra;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn encode_img(mut data: ImageData, typ: ImageOutputType, filename: &str) -> Result<()> {
|
||||
match typ {
|
||||
ImageOutputType::Png => {
|
||||
|
||||
Reference in New Issue
Block a user