use std::io::SeekFrom;
use binrw::{BinRead, BinWrite, binrw};
use image_dds::{Surface, ddsfile::Dds};
use xc3_write::Xc3Write;
pub use wiiu_swizzle::SwizzleError;
use crate::{error::CreateMtxtError, xc3_write_binwrite_impl};
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, BinWrite, PartialEq, Eq, Clone)]
pub struct Mtxt {
pub image_data: Vec<u8>,
pub footer: MtxtFooter,
}
const FOOTER_SIZE: u64 = 112;
#[binrw]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct MtxtFooter {
pub swizzle: u32,
pub surface_dim: SurfaceDim,
pub width: u32,
pub height: u32,
pub depth_or_array_layers: u32,
pub mipmap_count: u32,
pub surface_format: SurfaceFormat,
pub size: u32,
pub unk_mip_offset: u32,
pub tile_mode: TileMode,
pub unk1: u32,
pub alignment: u32,
pub pitch: u32,
pub mipmap_offsets: [u32; 13],
pub version: u32,
#[brw(magic(b"MTXT"))]
#[br(temp)]
#[bw(ignore)]
_magic: (),
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(BinRead, BinWrite, Debug, Clone, Copy, PartialEq, Eq)]
#[brw(repr(u32))]
pub enum SurfaceDim {
D2 = 1,
D3 = 2,
Cube = 3,
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(BinRead, BinWrite, Debug, Clone, Copy, PartialEq, Eq)]
#[brw(repr(u32))]
pub enum SurfaceFormat {
R8G8B8A8Unorm = 26,
BC1Unorm = 49,
BC2Unorm = 50,
BC3Unorm = 51,
BC4Unorm = 52,
BC5Unorm = 53,
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(BinRead, BinWrite, Debug, Clone, Copy, PartialEq, Eq)]
#[brw(repr(u32))]
pub enum TileMode {
D1TiledThin1 = 2,
D2TiledThin1 = 4,
D2TiledThick = 7,
}
impl BinRead for Mtxt {
type Args<'a> = ();
fn read_options<R: std::io::Read + std::io::Seek>(
reader: &mut R,
endian: binrw::Endian,
args: Self::Args<'_>,
) -> binrw::BinResult<Self> {
let saved_pos = reader.stream_position()?;
reader.seek(SeekFrom::End(-(FOOTER_SIZE as i64)))?;
let image_size = reader.stream_position()?.saturating_sub(saved_pos);
let footer = MtxtFooter::read_options(reader, endian, args)?;
reader.seek(SeekFrom::Start(saved_pos))?;
let mut image_data = vec![0u8; image_size as usize];
reader.read_exact(&mut image_data)?;
Ok(Mtxt { image_data, footer })
}
}
impl Mtxt {
pub fn deswizzled_image_data(&self) -> Result<Vec<u8>, SwizzleError> {
if self.footer.width == 2 && self.image_data.len() == 144 {
return Ok(self.image_data.clone());
}
wiiu_swizzle::Gx2Surface {
dim: self.footer.surface_dim.into(),
width: self.footer.width,
height: self.footer.height,
depth_or_array_layers: self.footer.depth_or_array_layers,
mipmap_count: self.footer.mipmap_count,
format: self.footer.surface_format.into(),
aa: wiiu_swizzle::AaMode::X1,
usage: 0,
image_data: &self.image_data,
mipmap_data: if self.footer.mipmap_count > 1 {
&self.image_data[self.footer.size as usize..]
} else {
&[]
},
tile_mode: self.footer.tile_mode.into(),
swizzle: self.footer.swizzle,
alignment: self.footer.swizzle,
pitch: self.footer.pitch,
mipmap_offsets: self.footer.mipmap_offsets,
}
.deswizzle()
}
pub fn to_surface(&self) -> Result<Surface<Vec<u8>>, SwizzleError> {
Ok(Surface {
width: self.footer.width,
height: self.footer.height,
depth: if self.footer.surface_dim == SurfaceDim::D3 {
self.footer.depth_or_array_layers
} else {
1
},
layers: if self.footer.surface_dim != SurfaceDim::D3 {
self.footer.depth_or_array_layers
} else {
1
},
mipmaps: self.footer.mipmap_count,
image_format: self.footer.surface_format.into(),
data: self.deswizzled_image_data()?,
})
}
pub fn from_surface<T: AsRef<[u8]>>(surface: Surface<T>) -> Result<Self, CreateMtxtError> {
let surface_format = surface.image_format.try_into()?;
Ok(Self {
image_data: Vec::new(),
footer: MtxtFooter {
swizzle: 0,
surface_dim: if surface.layers == 6 {
SurfaceDim::Cube
} else if surface.depth > 1 {
SurfaceDim::D3
} else {
SurfaceDim::D2
},
width: surface.width,
height: surface.height,
depth_or_array_layers: surface.depth.max(surface.layers),
mipmap_count: surface.mipmaps,
surface_format,
size: 0,
unk_mip_offset: 0,
tile_mode: TileMode::D2TiledThin1,
unk1: 0,
alignment: surface_format.bytes_per_pixel() * 512,
pitch: 0,
mipmap_offsets: [0; 13],
version: 10002,
},
})
}
pub fn to_dds(&self) -> Result<Dds, crate::dds::CreateDdsError> {
self.to_surface()?.to_dds().map_err(Into::into)
}
}
impl From<SurfaceFormat> for image_dds::ImageFormat {
fn from(value: SurfaceFormat) -> Self {
match value {
SurfaceFormat::R8G8B8A8Unorm => Self::Rgba8Unorm,
SurfaceFormat::BC1Unorm => Self::BC1RgbaUnorm,
SurfaceFormat::BC2Unorm => Self::BC2RgbaUnorm,
SurfaceFormat::BC3Unorm => Self::BC3RgbaUnorm,
SurfaceFormat::BC4Unorm => Self::BC4RUnorm,
SurfaceFormat::BC5Unorm => Self::BC5RgUnorm,
}
}
}
impl From<SurfaceFormat> for wiiu_swizzle::SurfaceFormat {
fn from(value: SurfaceFormat) -> Self {
Self::from_repr(value as u32).unwrap()
}
}
impl From<SurfaceDim> for wiiu_swizzle::SurfaceDim {
fn from(value: SurfaceDim) -> Self {
Self::from_repr(value as u32).unwrap()
}
}
impl From<TileMode> for wiiu_swizzle::TileMode {
fn from(value: TileMode) -> Self {
Self::from_repr(value as u32).unwrap()
}
}
impl TryFrom<image_dds::ImageFormat> for SurfaceFormat {
type Error = CreateMtxtError;
fn try_from(value: image_dds::ImageFormat) -> Result<Self, Self::Error> {
match value {
image_dds::ImageFormat::Rgba8Unorm => Ok(Self::R8G8B8A8Unorm),
image_dds::ImageFormat::BC1RgbaUnorm => Ok(Self::BC1Unorm),
image_dds::ImageFormat::BC2RgbaUnorm => Ok(Self::BC2Unorm),
image_dds::ImageFormat::BC3RgbaUnorm => Ok(Self::BC3Unorm),
image_dds::ImageFormat::BC4RUnorm => Ok(Self::BC4Unorm),
image_dds::ImageFormat::BC5RgUnorm => Ok(Self::BC5Unorm),
_ => Err(CreateMtxtError::UnsupportedImageFormat(value)),
}
}
}
impl SurfaceFormat {
pub fn block_dim(&self) -> (u32, u32) {
match self {
SurfaceFormat::R8G8B8A8Unorm => (1, 1),
SurfaceFormat::BC1Unorm => (4, 4),
SurfaceFormat::BC2Unorm => (4, 4),
SurfaceFormat::BC3Unorm => (4, 4),
SurfaceFormat::BC4Unorm => (4, 4),
SurfaceFormat::BC5Unorm => (4, 4),
}
}
pub fn bytes_per_pixel(&self) -> u32 {
match self {
SurfaceFormat::R8G8B8A8Unorm => 4,
SurfaceFormat::BC1Unorm => 8,
SurfaceFormat::BC2Unorm => 16,
SurfaceFormat::BC3Unorm => 16,
SurfaceFormat::BC4Unorm => 8,
SurfaceFormat::BC5Unorm => 16,
}
}
}
xc3_write_binwrite_impl!(Mtxt);