mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-07-08 01:31:53 +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)]
|
#[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.
|
/// 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>,
|
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(subcommand)]
|
||||||
/// Command
|
/// Command
|
||||||
pub command: Command,
|
pub command: Command,
|
||||||
|
|||||||
66
src/main.rs
66
src/main.rs
@@ -901,6 +901,35 @@ pub fn import_script(
|
|||||||
arch.write_header()?;
|
arch.write_header()?;
|
||||||
return Ok(types::ScriptResult::Ok);
|
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 {
|
let mut of = match &arg.output_type {
|
||||||
Some(t) => t.clone(),
|
Some(t) => t.clone(),
|
||||||
None => script.default_output_script_type(),
|
None => script.default_output_script_type(),
|
||||||
@@ -1131,7 +1160,12 @@ pub fn unpack_archive(
|
|||||||
Ok(types::ScriptResult::Ok)
|
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 {
|
let typ = match &arg.script_type {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => {
|
None => {
|
||||||
@@ -1143,6 +1177,32 @@ pub fn create_file(input: &str, output: Option<&str>, arg: &args::Arg) -> anyhow
|
|||||||
.find(|b| b.script_type() == typ)
|
.find(|b| b.script_type() == typ)
|
||||||
.ok_or_else(|| anyhow::anyhow!("Unsupported script type"))?;
|
.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() {
|
if !builder.can_create_file() {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
"Script type {:?} does not support file creation",
|
"Script type {:?} does not support file creation",
|
||||||
@@ -1196,6 +1256,8 @@ fn main() {
|
|||||||
image_type: arg.image_type.clone(),
|
image_type: arg.image_type.clone(),
|
||||||
#[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
|
#[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
|
||||||
bgi_is_sysgrp_arc: arg.bgi_is_sysgrp_arc.clone(),
|
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 {
|
match &arg.command {
|
||||||
args::Command::Export { input, output } => {
|
args::Command::Export { input, output } => {
|
||||||
@@ -1329,7 +1391,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
args::Command::Create { input, output } => {
|
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 {
|
if let Err(e) = re {
|
||||||
COUNTER.inc_error();
|
COUNTER.inc_error();
|
||||||
eprintln!("Error creating file: {}", e);
|
eprintln!("Error creating file: {}", e);
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ pub trait ScriptBuilder: std::fmt::Debug {
|
|||||||
&'a self,
|
&'a self,
|
||||||
_data: ImageData,
|
_data: ImageData,
|
||||||
_writer: Box<dyn WriteSeek + 'a>,
|
_writer: Box<dyn WriteSeek + 'a>,
|
||||||
|
_options: &ExtraConfig,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
Err(anyhow::anyhow!(
|
Err(anyhow::anyhow!(
|
||||||
"This script type does not support creating an image file."
|
"This script type does not support creating an image file."
|
||||||
@@ -131,10 +132,15 @@ pub trait ScriptBuilder: std::fmt::Debug {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[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::fs::File::create(filename)?;
|
||||||
let f = std::io::BufWriter::new(f);
|
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::ext::io::*;
|
||||||
use crate::scripts::base::*;
|
use crate::scripts::base::*;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
use crate::utils::img::*;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
fn try_parse(buf: &[u8]) -> Result<u8> {
|
fn try_parse(buf: &[u8]) -> Result<u8> {
|
||||||
@@ -72,6 +73,19 @@ impl ScriptBuilder for BgiImageBuilder {
|
|||||||
}
|
}
|
||||||
None
|
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)]
|
#[derive(Debug)]
|
||||||
@@ -81,10 +95,73 @@ pub struct BgiImage {
|
|||||||
height: u32,
|
height: u32,
|
||||||
color_type: ImageColorType,
|
color_type: ImageColorType,
|
||||||
is_scrambled: bool,
|
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 {
|
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 mut reader = MemReader::new(buf);
|
||||||
let width = reader.read_u16()? as u32;
|
let width = reader.read_u16()? as u32;
|
||||||
let height = reader.read_u16()? as u32;
|
let height = reader.read_u16()? as u32;
|
||||||
@@ -108,6 +185,7 @@ impl BgiImage {
|
|||||||
height,
|
height,
|
||||||
color_type,
|
color_type,
|
||||||
is_scrambled,
|
is_scrambled,
|
||||||
|
opt_is_scrambled: config.bgi_img_scramble,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,14 +204,41 @@ impl Script for BgiImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn export_image(&self) -> Result<ImageData> {
|
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;
|
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);
|
let mut data = Vec::with_capacity(buf_size);
|
||||||
data.resize(buf_size, 0);
|
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 {
|
Ok(ImageData {
|
||||||
width: self.width,
|
width: self.width,
|
||||||
height: self.height,
|
height: self.height,
|
||||||
@@ -142,4 +247,12 @@ impl Script for BgiImage {
|
|||||||
data,
|
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>,
|
pub image_type: Option<ImageOutputType>,
|
||||||
#[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
|
#[cfg(all(feature = "bgi-arc", feature = "bgi-img"))]
|
||||||
pub bgi_is_sysgrp_arc: Option<bool>,
|
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)]
|
#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@@ -320,7 +322,7 @@ pub enum ImageColorType {
|
|||||||
|
|
||||||
#[cfg(feature = "image")]
|
#[cfg(feature = "image")]
|
||||||
impl ImageColorType {
|
impl ImageColorType {
|
||||||
pub fn bbp(&self, depth: u8) -> u16 {
|
pub fn bpp(&self, depth: u8) -> u16 {
|
||||||
match self {
|
match self {
|
||||||
ImageColorType::Grayscale => depth as u16,
|
ImageColorType::Grayscale => depth as u16,
|
||||||
ImageColorType::Rgb => depth as u16 * 3,
|
ImageColorType::Rgb => depth as u16 * 3,
|
||||||
|
|||||||
@@ -52,6 +52,42 @@ pub fn convert_bgra_to_rgba(data: &mut ImageData) -> Result<()> {
|
|||||||
Ok(())
|
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<()> {
|
pub fn encode_img(mut data: ImageData, typ: ImageOutputType, filename: &str) -> Result<()> {
|
||||||
match typ {
|
match typ {
|
||||||
ImageOutputType::Png => {
|
ImageOutputType::Png => {
|
||||||
|
|||||||
Reference in New Issue
Block a user