use binrw::{binrw, prelude::*, NullString, Endian, VecArgs};
use convert::{create_nutexb, create_nutexb_unswizzled};
use std::{
io::{Cursor, Read, Seek, SeekFrom, Write},
num::NonZeroUsize,
path::Path,
};
use tegra_swizzle::surface::{deswizzled_surface_size, swizzled_surface_size, BlockDim};
#[cfg(feature = "ddsfile")]
pub use ddsfile;
#[cfg(feature = "ddsfile")]
pub use dds::ReadDdsError;
#[cfg(feature = "ddsfile")]
mod dds;
#[cfg(feature = "image")]
pub use image;
mod convert;
pub use convert::Surface;
const FOOTER_SIZE: usize = 112;
const LAYER_MIPMAPS_SIZE: usize = 64;
#[derive(Debug, Clone, BinWrite)]
pub struct NutexbFile {
pub data: Vec<u8>,
pub layer_mipmaps: Vec<LayerMipmaps>,
pub footer: NutexbFooter,
}
impl BinRead for NutexbFile {
type Args<'arg> = ();
fn read_options<R: Read + Seek>(
reader: &mut R,
_endian: Endian,
_args: Self::Args<'_>,
) -> BinResult<Self> {
reader.seek(SeekFrom::End(-(FOOTER_SIZE as i64)))?;
let footer: NutexbFooter = reader.read_le()?;
reader.seek(SeekFrom::Current(
-(FOOTER_SIZE as i64 + LAYER_MIPMAPS_SIZE as i64 * footer.layer_count as i64),
))?;
let data_size = reader.stream_position()?;
let layer_mipmaps: Vec<LayerMipmaps> = reader.read_le_args(VecArgs {
count: footer.layer_count as usize,
inner: (footer.mipmap_count,),
})?;
reader.seek(SeekFrom::Start(0))?;
let mut data = vec![0u8; data_size as usize];
reader.read_exact(&mut data)?;
Ok(Self {
data,
layer_mipmaps,
footer,
})
}
}
impl NutexbFile {
pub fn read<R: Read + Seek>(reader: &mut R) -> BinResult<Self> {
reader.read_le::<NutexbFile>()
}
pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<NutexbFile, binrw::Error> {
let mut file = Cursor::new(std::fs::read(path)?);
let nutexb = file.read_le::<NutexbFile>()?;
Ok(nutexb)
}
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<(), binrw::Error> {
self.write_le(writer).map_err(Into::into)
}
pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), binrw::Error> {
let mut writer = Cursor::new(Vec::new());
self.write(&mut writer)?;
std::fs::write(path, writer.into_inner()).map_err(Into::into)
}
pub fn deswizzled_data(&self) -> Result<Vec<u8>, tegra_swizzle::SwizzleError> {
tegra_swizzle::surface::deswizzle_surface(
self.footer.width as usize,
self.footer.height as usize,
self.footer.depth as usize,
&self.data,
self.footer.image_format.block_dim(),
None,
self.footer.image_format.bytes_per_pixel() as usize,
self.footer.mipmap_count as usize,
self.footer.layer_count as usize,
)
}
pub fn from_surface<T: AsRef<[u8]>, S: Into<String>>(
image: Surface<T>,
name: S,
) -> Result<Self, tegra_swizzle::SwizzleError> {
create_nutexb(image, name)
}
pub fn from_surface_unswizzled<T: AsRef<[u8]>, S: Into<String>>(
surface: &Surface<T>,
name: S,
) -> Self {
create_nutexb_unswizzled(surface, name)
}
#[cfg(feature = "ddsfile")]
pub fn from_dds<S: Into<String>>(dds: &ddsfile::Dds, name: S) -> Result<Self, ReadDdsError> {
let surface = dds::create_surface(dds)?;
Self::from_surface(surface, name).map_err(Into::into)
}
#[cfg(feature = "ddsfile")]
pub fn to_dds(&self) -> Result<ddsfile::Dds, tegra_swizzle::SwizzleError> {
dds::create_dds(&self)
}
#[cfg(feature = "image")]
pub fn from_image<S: Into<String>>(
image: &image::RgbaImage,
name: S,
) -> Result<Self, tegra_swizzle::SwizzleError> {
let surface = Surface {
width: image.width(),
height: image.height(),
depth: 1, image_data: image.as_raw(),
mipmap_count: 1,
layer_count: 1,
image_format: NutexbFormat::R8G8B8A8Srgb,
};
Self::from_surface(surface, name)
}
pub fn optimize_size(&mut self) {
let new_len = if self.footer.unk3 == 0x1000 {
swizzled_surface_size(
self.footer.width as usize,
self.footer.height as usize,
self.footer.depth as usize,
self.footer.image_format.block_dim(),
None,
self.footer.image_format.bytes_per_pixel() as usize,
self.footer.mipmap_count as usize,
self.footer.layer_count as usize,
)
} else {
deswizzled_surface_size(
self.footer.width as usize,
self.footer.height as usize,
self.footer.depth as usize,
self.footer.image_format.block_dim(),
self.footer.image_format.bytes_per_pixel() as usize,
self.footer.mipmap_count as usize,
self.footer.layer_count as usize,
)
};
self.data.resize(new_len, 0);
self.footer.data_size = self.data.len() as u32;
}
}
#[binrw]
#[derive(Debug, Clone, PartialEq)]
#[brw(magic = b" XNT")]
pub struct NutexbFooter {
#[brw(pad_size_to = 0x40)]
pub string: NullString,
pub width: u32,
pub height: u32,
pub depth: u32,
pub image_format: NutexbFormat,
pub unk2: u32, pub mipmap_count: u32,
pub unk3: u32,
pub layer_count: u32,
pub data_size: u32,
#[brw(magic = b" XET")]
pub version: (u16, u16),
}
#[binrw]
#[derive(Debug, Clone)]
#[br(import(mipmap_count: u32))]
pub struct LayerMipmaps {
#[brw(pad_size_to = 0x40)]
#[br(count = mipmap_count)]
pub mipmap_sizes: Vec<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, BinRead, BinWrite)]
#[brw(repr(u32))]
pub enum NutexbFormat {
R8Unorm = 0x0100,
R8G8B8A8Unorm = 0x0400,
R8G8B8A8Srgb = 0x0405,
R32G32B32A32Float = 0x0434,
B8G8R8A8Unorm = 0x0450,
B8G8R8A8Srgb = 0x0455,
BC1Unorm = 0x0480,
BC1Srgb = 0x0485,
BC2Unorm = 0x0490,
BC2Srgb = 0x0495,
BC3Unorm = 0x04a0,
BC3Srgb = 0x04a5,
BC4Unorm = 0x0180,
BC4Snorm = 0x0185,
BC5Unorm = 0x0280,
BC5Snorm = 0x0285,
BC6Ufloat = 0x04d7,
BC6Sfloat = 0x04d8,
BC7Unorm = 0x04e0,
BC7Srgb = 0x04e5,
}
impl NutexbFormat {
pub fn bytes_per_pixel(&self) -> u32 {
match &self {
NutexbFormat::R8G8B8A8Unorm
| NutexbFormat::R8G8B8A8Srgb
| NutexbFormat::B8G8R8A8Unorm
| NutexbFormat::B8G8R8A8Srgb => 4,
NutexbFormat::R32G32B32A32Float => 16,
NutexbFormat::BC1Unorm | NutexbFormat::BC1Srgb => 8,
NutexbFormat::BC2Unorm | NutexbFormat::BC2Srgb => 16,
NutexbFormat::BC3Unorm | NutexbFormat::BC3Srgb => 16,
NutexbFormat::BC4Unorm | NutexbFormat::BC4Snorm => 8,
NutexbFormat::BC5Unorm | NutexbFormat::BC5Snorm => 16,
NutexbFormat::BC6Ufloat | NutexbFormat::BC6Sfloat => 16,
NutexbFormat::BC7Unorm | NutexbFormat::BC7Srgb => 16,
NutexbFormat::R8Unorm => 1,
}
}
pub fn block_width(&self) -> u32 {
match &self {
NutexbFormat::R8Unorm
| NutexbFormat::R8G8B8A8Unorm
| NutexbFormat::R8G8B8A8Srgb
| NutexbFormat::R32G32B32A32Float
| NutexbFormat::B8G8R8A8Unorm
| NutexbFormat::B8G8R8A8Srgb => 1,
_ => 4,
}
}
pub fn block_height(&self) -> u32 {
self.block_width()
}
pub fn block_depth(&self) -> u32 {
1
}
pub(crate) fn block_dim(&self) -> BlockDim {
BlockDim {
width: NonZeroUsize::new(self.block_width() as usize).unwrap(),
height: NonZeroUsize::new(self.block_height() as usize).unwrap(),
depth: NonZeroUsize::new(self.block_depth() as usize).unwrap(),
}
}
}