use std::fs;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use clap::{Args, Parser, Subcommand};
use glacier_texture::convert;
use glacier_texture::mipblock::MipblockData;
use glacier_texture::pack::TextureMapBuilder;
use glacier_texture::texture_map::TextureMap;
use glacier_texture::GlacierGame;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[clap(flatten)]
global_opts: GlobalOpts,
#[clap(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
ConvertTextureMapToDDS(ConvertTextureMap),
ConvertTextureMapToTga(ConvertTextureMap),
ConvertTgaToTextureMap(GenerateTextureMap),
PortTextureMap(PortTextureMap),
}
#[derive(Debug, Args)]
struct GlobalOpts {
#[clap(short, long, global = true)]
output_path: Option<String>,
#[clap(short, long, global = true)]
silent: bool,
}
#[derive(Debug, Args)]
#[command(author, version, about, long_about = None)]
struct ConvertTextureMap {
#[clap(short, long)]
game_version: GlacierGame,
#[arg(short, long)]
input_path: String,
#[clap(short = 'd', long)]
texd_path: Option<String>,
}
#[derive(Debug, Args)]
#[command(author, version, about, long_about = None)]
struct GenerateTextureMap {
#[clap(short, long)]
game_version: GlacierGame,
#[arg(short, long)]
input_path: String,
#[arg(long)]
no_texd: bool,
}
#[derive(Debug, Args)]
#[command(author, version, about, long_about = None)]
struct PortTextureMap {
#[clap(short, long)]
from_version: GlacierGame,
#[clap(short, long)]
to_version: GlacierGame,
#[arg(short, long)]
input_path: String,
#[clap(short = 'd', long)]
texd_path: Option<String>,
#[arg(long)]
no_texd: bool,
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Command::ConvertTextureMapToDDS(cmd) => {
let tex = read_texture(&cmd, cli.global_opts.silent)?;
let output_path = get_output_path(&cli.global_opts.output_path, &cmd.input_path, "dds");
let dds =
convert::create_dds(&tex).context("Failed to create DDS from the texture map")?;
fs::write(&output_path, dds)
.with_context(|| format!("Failed to write DDS file to {:?}", output_path))?;
if !cli.global_opts.silent {
println!("Successfully converted TEXT to DDS at {:?}", output_path);
}
}
Command::ConvertTextureMapToTga(cmd) => {
let tex = read_texture(&cmd, cli.global_opts.silent)?;
let output_path = get_output_path(&cli.global_opts.output_path, &cmd.input_path, "tga");
let tga =
convert::create_tga(&tex).context("Failed to create TGA from the texture map")?;
fs::write(&output_path, tga)
.with_context(|| format!("Failed to write TGA file to {:?}", output_path))?;
if !cli.global_opts.silent {
println!("Successfully converted TEXT to TGA at {:?}", output_path);
}
}
Command::ConvertTgaToTextureMap(cmd) => {
let tga_data = fs::read(&cmd.input_path)
.with_context(|| format!("Failed to read TGA file at {:?}", cmd.input_path))?;
let mut cursor = Cursor::new(tga_data);
let tex = TextureMapBuilder::from_tga(&mut cursor)
.context("Failed to create TextureMapBuilder from TGA data")?
.with_mipblock1(!cmd.no_texd)
.build(cmd.game_version)
.context("Failed to build TextureMap from TGA data")?;
let text_path = get_output_path(&cli.global_opts.output_path, &cmd.input_path, "TEXT");
fs::write(
&text_path,
tex.pack_to_vec().context("Failed to pack TEXT data")?,
)
.with_context(|| format!("Failed to write TEXT file to {:?}", text_path))?;
if !cli.global_opts.silent {
println!("Successfully converted TGA to TEXT at {:?}", text_path);
}
if tex.has_mipblock1() {
let texd_path = text_path.with_extension("TEXD");
fs::write(
&texd_path,
tex.mipblock1()
.context("Failed to retrieve TEXD data from TextureMap")?
.pack_to_vec(cmd.game_version)
.context("Failed to pack TEXD data")?,
)
.with_context(|| format!("Failed to write TEXD file to {:?}", texd_path))?;
if !cli.global_opts.silent {
println!("Successfully wrote TEXD at {:?}", texd_path);
}
}
}
Command::PortTextureMap(cmd) => {
let tex = read_texture_port(&cmd)?;
let output_path =
get_output_path(&cli.global_opts.output_path, &cmd.input_path, "TEXT");
let builder = TextureMapBuilder::from_texture_map(&tex)
.context("Failed to create TextureMapBuilder from existing TextureMap")?
.with_mipblock1(!cmd.no_texd);
let ported_tex = builder
.build(cmd.to_version)
.context("Failed to build ported TextureMap")?;
fs::write(
&output_path,
ported_tex
.pack_to_vec()
.context("Failed to pack TEXT data")?,
)
.with_context(|| format!("Failed to write ported TEXT file to {:?}", output_path))?;
if !cli.global_opts.silent {
println!(
"Successfully ported TEXT from {:?} to {:?} at {:?}",
cmd.from_version, cmd.to_version, output_path
);
}
if ported_tex.has_mipblock1() {
let texd_path = output_path.with_extension("TEXD");
fs::write(
&texd_path,
ported_tex
.mipblock1()
.context("Failed to retrieve TEXD data from ported TextureMap")?
.pack_to_vec(cmd.to_version)
.context("Failed to pack TEXD data")?,
)
.with_context(|| format!("Failed to write ported TEXD file to {:?}", texd_path))?;
if !cli.global_opts.silent {
println!("Successfully wrote ported TEXD at {:?}", texd_path);
}
}
}
}
Ok(())
}
fn read_texture(cmd: &ConvertTextureMap, silent: bool) -> Result<TextureMap> {
let input_data = fs::read(&cmd.input_path)
.with_context(|| format!("Failed to read input TEXT file at {:?}", cmd.input_path))?;
let mut tex = TextureMap::from_memory(&input_data, cmd.game_version)
.context("Failed to parse the TEXT file into TextureMap")?;
if let Some(texd_path) = &cmd.texd_path {
let texd_data = fs::read(texd_path)
.with_context(|| format!("Failed to read TEXD file at {:?}", texd_path))?;
tex.set_mipblock1(
MipblockData::from_memory(&texd_data, cmd.game_version)
.context("Failed to apply TEXD data to TextureMap")?,
);
}
if !silent {
println!("Successfully read the texture file:");
println!("{:<15}: {:?}", "type", tex.texture_type());
if let Some(interpret) = tex.interpret_as() {
println!("{:<15}: {:?}", "interpret", interpret);
}
println!("{:<15}: {}x{}", "size", tex.width(), tex.height());
println!("{:<15}: {}", "mip amount", tex.num_mip_levels());
println!("{:<15}: {}", "has atlas", tex.has_atlas());
}
Ok(tex)
}
fn read_texture_port(cmd: &PortTextureMap) -> Result<TextureMap> {
let input_data = fs::read(&cmd.input_path)
.with_context(|| format!("Failed to read input TEXT file at {:?}", cmd.input_path))?;
let tex = TextureMap::from_memory(&input_data, cmd.from_version)
.context("Failed to parse the TEXT file into TextureMap")?;
if let Some(texd_path) = &cmd.texd_path {
let texd_data = fs::read(texd_path)
.with_context(|| format!("Failed to read TEXD file at {:?}", texd_path))?;
let mut tex_with_texd = tex.clone();
tex_with_texd.set_mipblock1(
MipblockData::from_memory(&texd_data, cmd.from_version)
.context("Failed to apply TEXD data to TextureMap")?,
);
Ok(tex_with_texd)
} else {
Ok(tex)
}
}
fn get_output_path(global_output: &Option<String>, input_path: &str, new_ext: &str) -> PathBuf {
global_output
.as_ref()
.map(PathBuf::from)
.unwrap_or_else(|| Path::new(input_path).with_extension(new_ext))
}