diff --git a/Cargo.lock b/Cargo.lock index 3a5dc7c..bbd5c4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "binrw" version = "0.11.1" @@ -105,12 +111,34 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "ddsfile" +version = "0.5.2-unstable" +source = "git+https://github.com/encounter/ddsfile?rev=880f04c1dffa680eab0e9e09cfa58591fe186a31#880f04c1dffa680eab0e9e09cfa58591fe186a31" +dependencies = [ + "bitflags", + "byteorder", + "enum-primitive-derive", + "num-traits", +] + [[package]] name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "enum-primitive-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c375b9c5eadb68d0a6efee2999fef292f45854c3444c86f09d8ab086ba942b0e" +dependencies = [ + "num-traits", + "quote", + "syn", +] + [[package]] name = "env_logger" version = "0.10.0" @@ -215,6 +243,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "owo-colors" version = "3.5.0" @@ -264,10 +301,11 @@ dependencies = [ "argh", "binrw", "binrw_derive", - "byteorder", + "ddsfile", "env_logger", "log", "memmap2", + "tegra_swizzle", "uuid", ] @@ -296,6 +334,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tegra_swizzle" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898709aaa04e72af51fafa032802e0dee931eee066894478ac0000b4525d30bf" + [[package]] name = "termcolor" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index a0c8fac..5732215 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,10 +18,13 @@ strip = "debuginfo" [dependencies] anyhow = "1.0.69" argh = "0.1.10" +# astc-decode = "0.3.1" binrw = "0.11.1" binrw_derive = "0.11.1" -byteorder = "1.4.3" +ddsfile = { git = "https://github.com/encounter/ddsfile", rev = "880f04c1dffa680eab0e9e09cfa58591fe186a31" } env_logger = "0.10.0" +# image = "0.24.5" log = "0.4.17" memmap2 = "0.5.8" +tegra_swizzle = "0.3.0" uuid = "1.3.0" diff --git a/README.md b/README.md index 8b0ecb8..5a1d1ed 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,21 @@ Re-packages a `.pak`, given an extracted directory. $ retrotool pak package [in_dir] [out_pak] ``` +### txtr convert + +Converts a `.TXTR` file to `.dds` (recommended) or `.astc`. + +Textures are often compressed with BCn or ASTC, which are not commonly supported by image viewers. +[tacentview](https://github.com/bluescan/tacentview) is recommended to view and convert the resulting textures. + +```shell +$ retrotool txtr convert [in].TXTR +# writes to [in].dds + +$ retrotool txtr convert --astc [in].TXTR +# writes to [in].astc +``` + ### fmv0 extract Extracts the contained video from a given `FMV0` file. diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index d5da243..f358c57 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -8,6 +8,6 @@ use argh::FromArgs; #[argh(subcommand)] pub enum SubCommand { Pak(pak::Args), - // Txtr(txtr::Args), + Txtr(txtr::Args), Fmv0(fmv0::Args), } diff --git a/src/cmd/pak.rs b/src/cmd/pak.rs index 0a9e44d..96a342e 100644 --- a/src/cmd/pak.rs +++ b/src/cmd/pak.rs @@ -97,7 +97,7 @@ fn extract(args: ExtractArgs) -> Result<()> { file.write_all(&asset.data)?; // Write custom footer - FormDescriptor { size: 0, unk1: 0, id: K_FORM_FOOT, version: 1, other_version: 1 }.write( + FormDescriptor { size: 0, unk: 0, id: K_FORM_FOOT, version_a: 1, version_b: 1 }.write( &mut file, Endian::Little, |w| { @@ -154,7 +154,7 @@ fn package(args: PackageArgs) -> Result<()> { // log::info!("Found type {} version {}, {}", form.id, form.version, form.other_version); let (foot, mut foot_data, _) = FormDescriptor::slice(remain, Endian::Little)?; ensure!(foot.id == K_FORM_FOOT); - ensure!(foot.version == 1); + ensure!(foot.version_a == 1); let mut ainfo: Option = None; let mut meta: Option<&[u8]> = None; let mut name: Option = None; @@ -174,9 +174,8 @@ fn package(args: PackageArgs) -> Result<()> { } foot_data = remain; } - let ainfo = match ainfo { - Some(a) => a, - None => bail!("Failed to locate asset info footer"), + let Some(ainfo) = ainfo else { + bail!("Failed to locate asset info footer"); }; package.assets.push(Asset { id: ainfo.id, @@ -185,8 +184,8 @@ fn package(args: PackageArgs) -> Result<()> { data: Cow::Owned(data[..data.len() - remain.len()].to_vec()), meta: meta.map(|data| Cow::Owned(data.to_vec())), info: ainfo, - version: form.version, - other_version: form.other_version, + version: form.version_a, + other_version: form.version_b, }); } package.assets.sort_by_key(|a| a.id); diff --git a/src/cmd/txtr.rs b/src/cmd/txtr.rs index 5696856..319a914 100644 --- a/src/cmd/txtr.rs +++ b/src/cmd/txtr.rs @@ -1,18 +1,30 @@ -use std::path::PathBuf; +use std::{ + fs::File, + io::{BufWriter, Cursor, Write}, + path::PathBuf, +}; -use anyhow::{ensure, Result}; +use anyhow::{anyhow, bail, ensure, Context, Result}; use argh::FromArgs; -use binrw::Endian; +use binrw::{BinReaderExt, Endian}; use crate::{ - format::{chunk::ChunkDescriptor, pack::K_CHUNK_META, rfrm::FormDescriptor, FourCC}, - util::file::map_file, + format::{ + chunk::ChunkDescriptor, + pack::{K_CHUNK_META, K_FORM_FOOT}, + rfrm::FormDescriptor, + txtr::{deswizzle, STextureHeader, STextureMetaData}, + FourCC, + }, + util::{astc::write_astc, dds::write_dds, file::map_file, lzss::decompress_into}, }; // Texture pub const K_FORM_TXTR: FourCC = FourCC(*b"TXTR"); // Texture header pub const K_CHUNK_HEAD: FourCC = FourCC(*b"HEAD"); +// GPU data +pub const K_CHUNK_GPU: FourCC = FourCC(*b"GPU "); #[derive(FromArgs, PartialEq, Debug)] /// process TXTR files @@ -29,15 +41,15 @@ enum SubCommand { } #[derive(FromArgs, PartialEq, Eq, Debug)] -/// converts a TXTR file +/// converts a TXTR file to DDS/ASTC #[argh(subcommand, name = "convert")] pub struct ConvertArgs { #[argh(positional)] - /// input file + /// input TXTR input: PathBuf, - #[argh(positional)] - /// output directory - output: PathBuf, + #[argh(switch, short = 'a')] + /// write ASTC file instead of DDS (no mips) + astc: bool, } #[allow(unused)] @@ -47,20 +59,83 @@ pub fn run(args: Args) -> Result<()> { } } -// struct TextureHeader {} - -// struct TextureMeta {} - fn convert(args: ConvertArgs) -> Result<()> { - let mmap = map_file(args.input)?; - let (meta, _, remain) = ChunkDescriptor::slice(&mmap, Endian::Little)?; - ensure!(meta.id == K_CHUNK_META); + let data = map_file(&args.input)?; - let (desc, data, _) = FormDescriptor::slice(remain, Endian::Little)?; - ensure!(desc.id == K_FORM_TXTR); - ensure!(desc.version == 47); - let (desc, _, _) = ChunkDescriptor::slice(data, Endian::Little)?; - ensure!(desc.id == K_CHUNK_HEAD); + let (txtr_desc, txtr_data, remain) = FormDescriptor::slice(&data, Endian::Little)?; + ensure!(txtr_desc.id == K_FORM_TXTR); + ensure!(txtr_desc.version_a == 47); + ensure!(txtr_desc.version_b == 51); + let (foot_desc, mut foot_data, remain) = FormDescriptor::slice(remain, Endian::Little)?; + ensure!(foot_desc.id == K_FORM_FOOT); + ensure!(foot_desc.version_a == 1); + ensure!(foot_desc.version_b == 1); + ensure!(remain.is_empty()); - todo!() + let mut meta: Option = None; + while !foot_data.is_empty() { + let (desc, data, remain) = ChunkDescriptor::slice(foot_data, Endian::Little)?; + if desc.id == K_CHUNK_META { + meta = Some(Cursor::new(data).read_type(Endian::Little)?); + break; + } + foot_data = remain; + } + let Some(meta) = meta else { + bail!("Failed to locate meta chunk"); + }; + + let (head_desc, head_data, remain) = ChunkDescriptor::slice(txtr_data, Endian::Little)?; + ensure!(head_desc.id == K_CHUNK_HEAD); + let head: STextureHeader = Cursor::new(head_data).read_type(Endian::Little)?; + let (gpu_desc, _, remain) = ChunkDescriptor::slice(remain, Endian::Little)?; + ensure!(gpu_desc.id == K_CHUNK_GPU); + ensure!(remain.is_empty()); + + log::debug!("META: {meta:#?}"); + log::debug!("HEAD: {head:#?}"); + + log::info!("Texture info:"); + log::info!(" Type: {:?}", head.kind); + log::info!(" Format: {:?}", head.format); + log::info!(" Size: {}x{}x{}", head.width, head.height, head.layers); + log::info!(" Mip count: {}", head.mip_sizes.len()); + + let mut buffer = vec![0u8; meta.decompressed_size as usize]; + for info in &meta.buffers { + let read = meta + .info + .iter() + .find(|i| i.index as u32 == info.index) + .ok_or_else(|| anyhow!("Failed to locate read info for buffer {}", info.index))?; + let read_buf = &data[read.offset as usize..(read.offset + read.size) as usize]; + let comp_buf = &read_buf[info.offset as usize..(info.offset + info.size) as usize]; + decompress_into( + comp_buf, + &mut buffer[info.dest_offset as usize..(info.dest_offset + info.dest_size) as usize], + )?; + } + + let deswizzled = deswizzle(&head, &buffer)?; + let path = if args.astc { + if !head.format.is_astc() { + bail!("Expected ASTC format, got {:?}", head.format); + } + args.input.with_extension("astc") + } else { + args.input.with_extension("dds") + }; + let mut file = BufWriter::new( + File::create(&path) + .with_context(|| format!("Failed to create output file '{}'", path.display()))?, + ); + log::info!("Writing {}", path.display()); + if args.astc { + write_astc(&mut file, &head, &deswizzled)?; + } else { + write_dds(&mut file, &head, deswizzled)?; + } + file.flush()?; + + Ok(()) } diff --git a/src/format/chunk.rs b/src/format/chunk.rs index 670b8a4..5a9b27d 100644 --- a/src/format/chunk.rs +++ b/src/format/chunk.rs @@ -11,8 +11,6 @@ pub struct ChunkDescriptor { pub id: FourCC, pub size: u64, pub unk: u32, - // game skips this amount of bytes before continuing - // but always 0? pub skip: u64, } @@ -34,8 +32,11 @@ impl ChunkDescriptor { Ok((header, slice, remain)) } - pub fn write(&mut self, w: &mut W, e: Endian, mut cb: CB) -> Result<()> - where CB: FnMut(&mut W) -> Result<()> { + pub fn write(&mut self, w: &mut W, e: Endian, mut cb: CB) -> Result<()> + where + W: Write + Seek, + CB: FnMut(&mut W) -> Result<()>, + { let form_pos = w.stream_position()?; w.write_type(self, e)?; let data_pos = w.stream_position()?; diff --git a/src/format/mod.rs b/src/format/mod.rs index 7b0e7d7..f2e1cda 100644 --- a/src/format/mod.rs +++ b/src/format/mod.rs @@ -1,6 +1,7 @@ pub mod chunk; pub mod pack; pub mod rfrm; +pub mod txtr; use std::fmt::{Debug, Display, Formatter, Write}; diff --git a/src/format/pack.rs b/src/format/pack.rs index 502f9dd..b3e57f7 100644 --- a/src/format/pack.rs +++ b/src/format/pack.rs @@ -10,7 +10,7 @@ use uuid::Uuid; use crate::{ format::{chunk::ChunkDescriptor, rfrm::FormDescriptor, FourCC}, - util::lzss::decompress, + util::lzss::decompress_buffer, }; // Package file @@ -138,11 +138,11 @@ impl Package<'_> { pub fn read(data: &[u8], e: Endian) -> Result { let (pack, pack_data, _) = FormDescriptor::slice(data, e)?; ensure!(pack.id == K_FORM_PACK); - ensure!(pack.version == 1); + ensure!(pack.version_a == 1); log::debug!("PACK: {:?}", pack); let (tocc, mut tocc_data, _) = FormDescriptor::slice(pack_data, e)?; ensure!(tocc.id == K_FORM_TOCC); - ensure!(tocc.version == 3); + ensure!(tocc.version_a == 3); log::debug!("TOCC: {:?}", tocc); let mut adir: Option = None; let mut meta: HashMap = HashMap::new(); @@ -185,57 +185,42 @@ impl Package<'_> { tocc_data = remain; } - let mut package = Package { assets: vec![] }; - if let Some(adir) = adir { - for asset_entry in &adir.entries { - let mut compression_mode = 0u32; - let data: Cow<[u8]> = if asset_entry.size != asset_entry.decompressed_size { - let compressed_data = &data[asset_entry.offset as usize - ..(asset_entry.offset + asset_entry.size) as usize]; - compression_mode = - u32::from_le_bytes(compressed_data[0..4].try_into().unwrap()); - let mut out = vec![0u8; asset_entry.decompressed_size as usize]; - let lzss_data = &compressed_data[4..]; - match compression_mode { - 1 => decompress::<1>(lzss_data, &mut out), - 2 => decompress::<2>(lzss_data, &mut out), - 3 => decompress::<3>(lzss_data, &mut out), - _ => bail!("Unsupported compression mode {}", compression_mode), - } - Cow::Owned(out) - } else { - Cow::Borrowed( - &data[asset_entry.offset as usize - ..(asset_entry.offset + asset_entry.size) as usize], - ) - }; - - // Validate RFRM - { - let (form, _, _) = FormDescriptor::slice(&data, Endian::Little)?; - ensure!(asset_entry.asset_type == form.id); - ensure!(asset_entry.version == form.version); - ensure!(asset_entry.other_version == form.other_version); - ensure!(asset_entry.decompressed_size == form.size + 32 /* RFRM */); - } - - package.assets.push(Asset { - id: asset_entry.asset_id, - kind: asset_entry.asset_type, - name: strg.get(&asset_entry.asset_id).cloned(), - data, - meta: meta.get(&asset_entry.asset_id).map(|data| Cow::Borrowed(*data)), - info: AssetInfo { - id: asset_entry.asset_id, - compression_mode, - orig_offset: asset_entry.offset, - }, - version: asset_entry.version, - other_version: asset_entry.other_version, - }); - } - } else { + let Some(adir) = adir else { bail!("Failed to locate asset directory"); + }; + let mut package = Package { assets: Vec::with_capacity(adir.entries.len()) }; + for asset_entry in &adir.entries { + let compressed_data = &data + [asset_entry.offset as usize..(asset_entry.offset + asset_entry.size) as usize]; + let (compression_mode, data) = if asset_entry.size != asset_entry.decompressed_size { + decompress_buffer(compressed_data, asset_entry.decompressed_size)? + } else { + (0, Cow::Borrowed(compressed_data)) + }; + + // Validate RFRM + { + let (form, _, _) = FormDescriptor::slice(&data, Endian::Little)?; + ensure!(asset_entry.asset_type == form.id); + ensure!(asset_entry.version == form.version_a); + ensure!(asset_entry.other_version == form.version_b); + ensure!(asset_entry.decompressed_size == form.size + 32 /* RFRM */); + } + + package.assets.push(Asset { + id: asset_entry.asset_id, + kind: asset_entry.asset_type, + name: strg.get(&asset_entry.asset_id).cloned(), + data, + meta: meta.get(&asset_entry.asset_id).map(|data| Cow::Borrowed(*data)), + info: AssetInfo { + id: asset_entry.asset_id, + compression_mode, + orig_offset: asset_entry.offset, + }, + version: asset_entry.version, + other_version: asset_entry.other_version, + }); } Ok(package) } @@ -274,11 +259,11 @@ impl Package<'_> { } } let mut adir_pos = 0; - FormDescriptor { size: 0, unk1: 0, id: K_FORM_PACK, version: 1, other_version: 1 }.write( + FormDescriptor { size: 0, unk: 0, id: K_FORM_PACK, version_a: 1, version_b: 1 }.write( w, e, |w| { - FormDescriptor { size: 0, unk1: 0, id: K_FORM_TOCC, version: 3, other_version: 3 } + FormDescriptor { size: 0, unk: 0, id: K_FORM_TOCC, version_a: 3, version_b: 3 } .write(w, e, |w| { ChunkDescriptor { id: K_CHUNK_ADIR, size: 0, unk: 1, skip: 0 }.write( w, diff --git a/src/format/rfrm.rs b/src/format/rfrm.rs index 38978a3..a888dd3 100644 --- a/src/format/rfrm.rs +++ b/src/format/rfrm.rs @@ -13,10 +13,10 @@ pub const K_CHUNK_RFRM: FourCC = FourCC(*b"RFRM"); #[derive(Clone, Debug)] pub struct FormDescriptor { pub size: u64, - pub unk1: u64, + pub unk: u64, pub id: FourCC, - pub version: u32, - pub other_version: u32, + pub version_a: u32, + pub version_b: u32, } impl FormDescriptor { diff --git a/src/format/txtr.rs b/src/format/txtr.rs new file mode 100644 index 0000000..75b5d9f --- /dev/null +++ b/src/format/txtr.rs @@ -0,0 +1,403 @@ +use std::num::NonZeroUsize; + +use anyhow::{ensure, Result}; +use binrw::binrw; +use tegra_swizzle::surface::BlockDim; + +#[binrw] +#[repr(u32)] +#[brw(repr(u32))] +#[derive(Copy, Clone, Debug)] +pub enum ETextureType { + _1D = 0, + _2D = 1, + _3D = 2, + Cube = 3, + _1DArray = 4, + _2DArray = 5, + _2DMultisample = 6, + _2DMultisampleArray = 7, + CubeArray = 8, +} + +#[binrw] +#[repr(u8)] +#[brw(repr(u8))] +#[derive(Copy, Clone, Debug)] +pub enum ETextureWrap { + ClampToEdge = 0, + Repeat = 1, + MirroredRepeat = 2, + MirrorClamp = 3, + ClampToBorder = 4, + Clamp = 5, +} + +#[binrw] +#[repr(u8)] +#[brw(repr(u8))] +#[derive(Copy, Clone, Debug)] +pub enum ETextureFilter { + Nearest = 0, + Linear = 1, +} + +#[binrw] +#[repr(u8)] +#[brw(repr(u8))] +#[derive(Copy, Clone, Debug)] +pub enum ETextureMipFilter { + Nearest = 0, + Linear = 1, +} + +#[binrw] +#[repr(u8)] +#[brw(repr(u8))] +#[derive(Copy, Clone, Debug)] +pub enum ETextureAnisotropicRatio { + None = u8::MAX, + _1 = 0, + _2 = 1, + _4 = 2, + _8 = 3, + _16 = 4, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct STextureHeader { + pub kind: ETextureType, + pub format: ETextureFormat, + pub width: u32, + pub height: u32, + pub layers: u32, + pub tile_mode: u32, + pub swizzle: u32, + #[bw(try_calc = mip_sizes.len().try_into())] + pub mip_count: u32, + #[br(count = mip_count)] + pub mip_sizes: Vec, + pub sampler_data: STextureSamplerData, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct STextureSamplerData { + pub unk: u32, + pub filter: ETextureFilter, + pub mip_filter: ETextureMipFilter, + pub wrap_x: ETextureWrap, + pub wrap_y: ETextureWrap, + pub wrap_z: ETextureWrap, + pub aniso: ETextureAnisotropicRatio, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct STextureReadInfo { + pub index: u8, + pub offset: u32, + pub size: u32, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct STextureCompressedBufferInfo { + pub index: u32, + pub offset: u32, + pub size: u32, + pub dest_offset: u32, + pub dest_size: u32, +} + +#[binrw] +#[derive(Clone, Debug)] +pub struct STextureMetaData { + pub unk1: u32, + pub unk2: u32, + pub alloc_category: u32, + pub gpu_offset: u32, + pub align: u32, + pub decompressed_size: u32, + #[bw(try_calc = info.len().try_into())] + pub info_count: u32, + #[br(count = info_count)] + pub info: Vec, + #[bw(try_calc = buffers.len().try_into())] + pub buffer_count: u32, + #[br(count = buffer_count)] + pub buffers: Vec, +} + +#[binrw] +#[repr(u32)] +#[brw(repr(u32))] +#[derive(Copy, Clone, Debug)] +pub enum ETextureFormat { + R8Unorm = 0, + R8Snorm = 1, + R8Uint = 2, + R8Sint = 3, + R16Unorm = 4, + R16Snorm = 5, + R16Uint = 6, + R16Sint = 7, + R16Float = 8, + R32Uint = 9, + R32Sint = 10, + Rgb8Unorm = 11, + Rgba8Unorm = 12, + Rgba8Srgb = 13, + Rgba16Float = 14, + Rgba32Float = 15, + Depth16Unorm = 16, + Depth16Unorm2 = 17, // ? + Depth24S8Unorm = 18, + Depth32Float = 19, + RgbaBc1Unorm = 20, // DXT1 + RgbaBc1Srgb = 21, // DXT1 + RgbaBc2Unorm = 22, // DXT3 + RgbaBc2Srgb = 23, // DXT3 + RgbaBc3Unorm = 24, // DXT5 + RgbaBc3Srgb = 25, // DXT5 + RgbaBc4Unorm = 26, // RGTC1 + RgbaBc4Snorm = 27, // RGTC1 + RgbaBc5Unorm = 28, // RGTC2 + RgbaBc5Snorm = 29, // RGTC2 + Rg11B10Float = 30, + R32Float = 31, + Rg8Unorm = 32, + Rg8Snorm = 33, + Rg8Uint = 34, + Rg8Sint = 35, + Rg16Float = 36, + Rg16Unorm = 37, + Rg16Snorm = 38, + Rg16Uint = 39, + Rg16Sint = 40, + Rgb10A2Unorm = 41, + Rgb10A2Uint = 42, + Rg32Uint = 43, + Rg32Sint = 44, + Rg32Float = 45, + Rgba16Unorm = 46, + Rgba16Snorm = 47, + Rgba16Uint = 48, + Rgba16Sint = 49, + Rgba32Uint = 50, + Rgba32Sint = 51, + None = 52, + RgbaAstc4x4 = 53, + RgbaAstc5x4 = 54, + RgbaAstc5x5 = 55, + RgbaAstc6x5 = 56, + RgbaAstc6x6 = 57, + RgbaAstc8x5 = 58, + RgbaAstc8x6 = 59, + RgbaAstc8x8 = 60, + RgbaAstc10x5 = 61, + RgbaAstc10x6 = 62, + RgbaAstc10x8 = 63, + RgbaAstc10x10 = 64, + RgbaAstc12x10 = 65, + RgbaAstc12x12 = 66, + RgbaAstc4x4Srgb = 67, + RgbaAstc5x4Srgb = 68, + RgbaAstc5x5Srgb = 69, + RgbaAstc6x5Srgb = 70, + RgbaAstc6x6Srgb = 71, + RgbaAstc8x5Srgb = 72, + RgbaAstc8x6Srgb = 73, + RgbaAstc8x8Srgb = 74, + RgbaAstc10x5Srgb = 75, + RgbaAstc10x6Srgb = 76, + RgbaAstc10x8Srgb = 77, + RgbaAstc10x10Srgb = 78, + RgbaAstc12x10Srgb = 79, + RgbaAstc12x12Srgb = 80, + BptcUfloat = 81, + BptcSfloat = 82, + BptcUnorm = 83, + BptcUnormSrgb = 84, +} + +impl ETextureFormat { + pub fn block_size(self) -> (u8, u8, u8) { + match self { + ETextureFormat::RgbaBc1Unorm + | ETextureFormat::RgbaBc1Srgb + | ETextureFormat::RgbaBc2Unorm + | ETextureFormat::RgbaBc2Srgb + | ETextureFormat::RgbaBc3Unorm + | ETextureFormat::RgbaBc3Srgb + | ETextureFormat::RgbaBc4Unorm + | ETextureFormat::RgbaBc4Snorm + | ETextureFormat::RgbaBc5Unorm + | ETextureFormat::RgbaBc5Snorm + | ETextureFormat::BptcUfloat + | ETextureFormat::BptcSfloat + | ETextureFormat::BptcUnorm + | ETextureFormat::BptcUnormSrgb => (4, 4, 1), + ETextureFormat::RgbaAstc4x4 | ETextureFormat::RgbaAstc4x4Srgb => (4, 4, 1), + ETextureFormat::RgbaAstc5x4 | ETextureFormat::RgbaAstc5x4Srgb => (5, 4, 1), + ETextureFormat::RgbaAstc5x5 | ETextureFormat::RgbaAstc5x5Srgb => (5, 5, 1), + ETextureFormat::RgbaAstc6x5 | ETextureFormat::RgbaAstc6x5Srgb => (6, 5, 1), + ETextureFormat::RgbaAstc6x6 | ETextureFormat::RgbaAstc6x6Srgb => (6, 6, 1), + ETextureFormat::RgbaAstc8x5 | ETextureFormat::RgbaAstc8x5Srgb => (8, 5, 1), + ETextureFormat::RgbaAstc8x6 | ETextureFormat::RgbaAstc8x6Srgb => (8, 6, 1), + ETextureFormat::RgbaAstc8x8 | ETextureFormat::RgbaAstc8x8Srgb => (8, 8, 1), + ETextureFormat::RgbaAstc10x5 | ETextureFormat::RgbaAstc10x5Srgb => (10, 5, 1), + ETextureFormat::RgbaAstc10x6 | ETextureFormat::RgbaAstc10x6Srgb => (10, 6, 1), + ETextureFormat::RgbaAstc10x8 | ETextureFormat::RgbaAstc10x8Srgb => (10, 8, 1), + ETextureFormat::RgbaAstc10x10 | ETextureFormat::RgbaAstc10x10Srgb => (10, 10, 1), + ETextureFormat::RgbaAstc12x10 | ETextureFormat::RgbaAstc12x10Srgb => (12, 10, 1), + ETextureFormat::RgbaAstc12x12 | ETextureFormat::RgbaAstc12x12Srgb => (12, 12, 1), + _ => (1, 1, 1), + } + } + + pub fn is_astc(self) -> bool { + matches!( + self, + ETextureFormat::RgbaAstc4x4 + | ETextureFormat::RgbaAstc5x4 + | ETextureFormat::RgbaAstc5x5 + | ETextureFormat::RgbaAstc6x5 + | ETextureFormat::RgbaAstc6x6 + | ETextureFormat::RgbaAstc8x5 + | ETextureFormat::RgbaAstc8x6 + | ETextureFormat::RgbaAstc8x8 + | ETextureFormat::RgbaAstc10x5 + | ETextureFormat::RgbaAstc10x6 + | ETextureFormat::RgbaAstc10x8 + | ETextureFormat::RgbaAstc10x10 + | ETextureFormat::RgbaAstc12x10 + | ETextureFormat::RgbaAstc12x12 + | ETextureFormat::RgbaAstc4x4Srgb + | ETextureFormat::RgbaAstc5x4Srgb + | ETextureFormat::RgbaAstc5x5Srgb + | ETextureFormat::RgbaAstc6x5Srgb + | ETextureFormat::RgbaAstc6x6Srgb + | ETextureFormat::RgbaAstc8x5Srgb + | ETextureFormat::RgbaAstc8x6Srgb + | ETextureFormat::RgbaAstc8x8Srgb + | ETextureFormat::RgbaAstc10x5Srgb + | ETextureFormat::RgbaAstc10x6Srgb + | ETextureFormat::RgbaAstc10x8Srgb + | ETextureFormat::RgbaAstc10x10Srgb + | ETextureFormat::RgbaAstc12x10Srgb + | ETextureFormat::RgbaAstc12x12Srgb + ) + } + + pub fn bytes_per_pixel(self) -> u32 { + match self { + ETextureFormat::R8Unorm + | ETextureFormat::R8Snorm + | ETextureFormat::R8Uint + | ETextureFormat::R8Sint => 1, + ETextureFormat::R16Unorm + | ETextureFormat::R16Snorm + | ETextureFormat::R16Uint + | ETextureFormat::R16Sint + | ETextureFormat::R16Float => 2, + ETextureFormat::R32Uint | ETextureFormat::R32Sint => 4, + ETextureFormat::Rgb8Unorm => 3, + ETextureFormat::Rgba8Unorm | ETextureFormat::Rgba8Srgb => 4, + ETextureFormat::Rgba16Float => 8, + ETextureFormat::Rgba32Float => 16, + ETextureFormat::Depth16Unorm | ETextureFormat::Depth16Unorm2 => 2, + ETextureFormat::Depth24S8Unorm | ETextureFormat::Depth32Float => 4, + ETextureFormat::RgbaBc1Unorm | ETextureFormat::RgbaBc1Srgb => 8, + ETextureFormat::RgbaBc2Unorm + | ETextureFormat::RgbaBc2Srgb + | ETextureFormat::RgbaBc3Unorm + | ETextureFormat::RgbaBc3Srgb => 16, + ETextureFormat::RgbaBc4Unorm | ETextureFormat::RgbaBc4Snorm => 8, + ETextureFormat::RgbaBc5Unorm | ETextureFormat::RgbaBc5Snorm => 16, + ETextureFormat::Rg11B10Float | ETextureFormat::R32Float => 4, + ETextureFormat::Rg8Unorm + | ETextureFormat::Rg8Snorm + | ETextureFormat::Rg8Uint + | ETextureFormat::Rg8Sint => 2, + ETextureFormat::Rg16Float + | ETextureFormat::Rg16Unorm + | ETextureFormat::Rg16Snorm + | ETextureFormat::Rg16Uint + | ETextureFormat::Rg16Sint => 4, + ETextureFormat::Rgb10A2Unorm | ETextureFormat::Rgb10A2Uint => 4, + ETextureFormat::Rg32Uint | ETextureFormat::Rg32Sint | ETextureFormat::Rg32Float => 8, + ETextureFormat::Rgba16Unorm + | ETextureFormat::Rgba16Snorm + | ETextureFormat::Rgba16Uint + | ETextureFormat::Rgba16Sint => 64, + ETextureFormat::Rgba32Uint | ETextureFormat::Rgba32Sint => 128, + ETextureFormat::None => 0, + ETextureFormat::RgbaAstc4x4 + | ETextureFormat::RgbaAstc5x4 + | ETextureFormat::RgbaAstc5x5 + | ETextureFormat::RgbaAstc6x5 + | ETextureFormat::RgbaAstc6x6 + | ETextureFormat::RgbaAstc8x5 + | ETextureFormat::RgbaAstc8x6 + | ETextureFormat::RgbaAstc8x8 + | ETextureFormat::RgbaAstc10x5 + | ETextureFormat::RgbaAstc10x6 + | ETextureFormat::RgbaAstc10x8 + | ETextureFormat::RgbaAstc10x10 + | ETextureFormat::RgbaAstc12x10 + | ETextureFormat::RgbaAstc12x12 + | ETextureFormat::RgbaAstc4x4Srgb + | ETextureFormat::RgbaAstc5x4Srgb + | ETextureFormat::RgbaAstc5x5Srgb + | ETextureFormat::RgbaAstc6x5Srgb + | ETextureFormat::RgbaAstc6x6Srgb + | ETextureFormat::RgbaAstc8x5Srgb + | ETextureFormat::RgbaAstc8x6Srgb + | ETextureFormat::RgbaAstc8x8Srgb + | ETextureFormat::RgbaAstc10x5Srgb + | ETextureFormat::RgbaAstc10x6Srgb + | ETextureFormat::RgbaAstc10x8Srgb + | ETextureFormat::RgbaAstc10x10Srgb + | ETextureFormat::RgbaAstc12x10Srgb + | ETextureFormat::RgbaAstc12x12Srgb + | ETextureFormat::BptcUfloat + | ETextureFormat::BptcSfloat + | ETextureFormat::BptcUnorm + | ETextureFormat::BptcUnormSrgb => 16, + } + } +} + +pub fn deswizzle(header: &STextureHeader, data: &[u8]) -> Result> { + let (bw, bh, bd) = header.format.block_size(); + let block_dim = BlockDim { + width: NonZeroUsize::new(bw as usize).unwrap(), + height: NonZeroUsize::new(bh as usize).unwrap(), + depth: NonZeroUsize::new(bd as usize).unwrap(), + }; + let bpp = header.format.bytes_per_pixel() as usize; + let expected_size = tegra_swizzle::surface::swizzled_surface_size( + header.width as usize, + header.height as usize, + 1, + block_dim, + None, + bpp, + header.mip_sizes.len(), + header.layers as usize, + ); + ensure!(data.len() == expected_size); + Ok(tegra_swizzle::surface::deswizzle_surface( + header.width as usize, + header.height as usize, + 1, + data, + block_dim, + None, + bpp, + header.mip_sizes.len(), + header.layers as usize, + )?) +} diff --git a/src/main.rs b/src/main.rs index 64d9cde..e759e17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ fn main() { let args: TopLevel = argh_version::from_env(); let result = match args.command { SubCommand::Pak(args) => cmd::pak::run(args), - // SubCommand::Txtr(args) => cmd::txtr::run(args), + SubCommand::Txtr(args) => cmd::txtr::run(args), SubCommand::Fmv0(args) => cmd::fmv0::run(args), }; if let Err(e) = result { diff --git a/src/util/astc.rs b/src/util/astc.rs new file mode 100644 index 0000000..74c1ee2 --- /dev/null +++ b/src/util/astc.rs @@ -0,0 +1,55 @@ +use std::io::{Seek, Write}; + +use anyhow::{bail, Result}; +use binrw::{binrw, BinWriterExt}; + +use crate::format::txtr::STextureHeader; + +pub fn write_astc(w: &mut W, head: &STextureHeader, data: &[u8]) -> Result<()> { + if !head.format.is_astc() { + bail!("Expected ASTC format, got {:?}", head.format); + } + let (block_x, block_y, block_z) = head.format.block_size(); + w.write_ne(&AstcHeader { + block_x, + block_y, + block_z, + dim_x: head.width, + dim_y: head.height, + dim_z: head.layers, + })?; + w.write_all(&data[..head.mip_sizes[0] as usize])?; + Ok(()) +} + +#[binrw] +#[derive(Debug, Clone)] +#[brw(magic = b"\x13\xAB\xA1\x5C")] +pub struct AstcHeader { + pub block_x: u8, + pub block_y: u8, + pub block_z: u8, + #[br(map = AstcU24::as_u32)] + #[bw(map = AstcU24::from_u32)] + pub dim_x: u32, + #[br(map = AstcU24::as_u32)] + #[bw(map = AstcU24::from_u32)] + pub dim_y: u32, + #[br(map = AstcU24::as_u32)] + #[bw(map = AstcU24::from_u32)] + pub dim_z: u32, +} + +#[binrw] +#[derive(Debug, Copy, Clone)] +struct AstcU24([u8; 3]); + +impl AstcU24 { + fn from_u32(&value: &u32) -> Self { + Self([value as u8, (value >> 8) as u8, (value >> 16) as u8]) + } + + fn as_u32(self) -> u32 { + self.0[0] as u32 | ((self.0[1] as u32) << 8) | ((self.0[2] as u32) << 16) + } +} diff --git a/src/util/dds.rs b/src/util/dds.rs new file mode 100644 index 0000000..bb32eb6 --- /dev/null +++ b/src/util/dds.rs @@ -0,0 +1,127 @@ +use std::io::Write; + +use anyhow::{ensure, Result}; +use ddsfile::{AlphaMode, D3D10ResourceDimension, DxgiFormat, NewDxgiParams}; + +use crate::format::txtr::{ETextureFormat, ETextureType, STextureHeader}; + +pub fn write_dds(w: &mut W, head: &STextureHeader, data: Vec) -> Result<()> { + let mut dds = ddsfile::Dds::new_dxgi(NewDxgiParams { + height: head.height, + width: head.width, + depth: None, + format: to_dxgi_format(head.format), + mipmap_levels: Some(head.mip_sizes.len() as u32), + array_layers: Some(head.layers), + caps2: None, + is_cubemap: matches!(head.kind, ETextureType::Cube | ETextureType::CubeArray), + resource_dimension: match head.kind { + ETextureType::_1D | ETextureType::_1DArray => D3D10ResourceDimension::Texture1D, + ETextureType::_2D + | ETextureType::_2DArray + | ETextureType::_2DMultisample + | ETextureType::_2DMultisampleArray + | ETextureType::Cube + | ETextureType::CubeArray => D3D10ResourceDimension::Texture2D, + ETextureType::_3D => D3D10ResourceDimension::Texture3D, + }, + alpha_mode: AlphaMode::Unknown, + })?; + // FIXME: ddsfile ASTC size calc is broken + if !head.format.is_astc() { + ensure!(dds.data.len() == data.len()); + } + dds.data = data; + dds.write(w)?; + Ok(()) +} + +fn to_dxgi_format(format: ETextureFormat) -> DxgiFormat { + match format { + ETextureFormat::R8Unorm => DxgiFormat::R8_UNorm, + ETextureFormat::R8Snorm => DxgiFormat::R8_SNorm, + ETextureFormat::R8Uint => DxgiFormat::R8_UInt, + ETextureFormat::R8Sint => DxgiFormat::R8_SInt, + ETextureFormat::R16Unorm => DxgiFormat::R16_UNorm, + ETextureFormat::R16Snorm => DxgiFormat::R16_SNorm, + ETextureFormat::R16Uint => DxgiFormat::R16_UInt, + ETextureFormat::R16Sint => DxgiFormat::R16_SInt, + ETextureFormat::R16Float => DxgiFormat::R16_Float, + ETextureFormat::R32Uint => DxgiFormat::R32_UInt, + ETextureFormat::R32Sint => DxgiFormat::R32_SInt, + ETextureFormat::Rgb8Unorm => DxgiFormat::Unknown, + ETextureFormat::Rgba8Unorm => DxgiFormat::R8G8B8A8_UNorm, + ETextureFormat::Rgba8Srgb => DxgiFormat::R8G8B8A8_UNorm_sRGB, + ETextureFormat::Rgba16Float => DxgiFormat::R16G16B16A16_Float, + ETextureFormat::Rgba32Float => DxgiFormat::R32G32B32A32_Float, + ETextureFormat::Depth16Unorm => DxgiFormat::D16_UNorm, + ETextureFormat::Depth16Unorm2 => DxgiFormat::D16_UNorm, + ETextureFormat::Depth24S8Unorm => DxgiFormat::D24_UNorm_S8_UInt, + ETextureFormat::Depth32Float => DxgiFormat::D32_Float, + ETextureFormat::RgbaBc1Unorm => DxgiFormat::BC1_UNorm, + ETextureFormat::RgbaBc1Srgb => DxgiFormat::BC1_UNorm_sRGB, + ETextureFormat::RgbaBc2Unorm => DxgiFormat::BC2_UNorm, + ETextureFormat::RgbaBc2Srgb => DxgiFormat::BC2_UNorm_sRGB, + ETextureFormat::RgbaBc3Unorm => DxgiFormat::BC3_UNorm, + ETextureFormat::RgbaBc3Srgb => DxgiFormat::BC3_UNorm_sRGB, + ETextureFormat::RgbaBc4Unorm => DxgiFormat::BC4_UNorm, + ETextureFormat::RgbaBc4Snorm => DxgiFormat::BC4_SNorm, + ETextureFormat::RgbaBc5Unorm => DxgiFormat::BC5_UNorm, + ETextureFormat::RgbaBc5Snorm => DxgiFormat::BC5_SNorm, + ETextureFormat::Rg11B10Float => DxgiFormat::R11G11B10_Float, + ETextureFormat::R32Float => DxgiFormat::R32_Float, + ETextureFormat::Rg8Unorm => DxgiFormat::R8G8_UNorm, + ETextureFormat::Rg8Snorm => DxgiFormat::R8G8_SNorm, + ETextureFormat::Rg8Uint => DxgiFormat::R8G8_UInt, + ETextureFormat::Rg8Sint => DxgiFormat::R8G8_SInt, + ETextureFormat::Rg16Float => DxgiFormat::R16G16_Float, + ETextureFormat::Rg16Unorm => DxgiFormat::R16G16_UNorm, + ETextureFormat::Rg16Snorm => DxgiFormat::R16G16_SNorm, + ETextureFormat::Rg16Uint => DxgiFormat::R16G16_UInt, + ETextureFormat::Rg16Sint => DxgiFormat::R16G16_SInt, + ETextureFormat::Rgb10A2Unorm => DxgiFormat::R10G10B10A2_UNorm, + ETextureFormat::Rgb10A2Uint => DxgiFormat::R10G10B10A2_UInt, + ETextureFormat::Rg32Uint => DxgiFormat::R32G32_UInt, + ETextureFormat::Rg32Sint => DxgiFormat::R32G32_SInt, + ETextureFormat::Rg32Float => DxgiFormat::R32G32_Float, + ETextureFormat::Rgba16Unorm => DxgiFormat::R16G16B16A16_UNorm, + ETextureFormat::Rgba16Snorm => DxgiFormat::R16G16B16A16_SNorm, + ETextureFormat::Rgba16Uint => DxgiFormat::R16G16B16A16_UInt, + ETextureFormat::Rgba16Sint => DxgiFormat::R16G16B16A16_SInt, + ETextureFormat::Rgba32Uint => DxgiFormat::R32G32B32A32_UInt, + ETextureFormat::Rgba32Sint => DxgiFormat::R32G32B32A32_SInt, + ETextureFormat::None => DxgiFormat::Unknown, + ETextureFormat::RgbaAstc4x4 => DxgiFormat::ASTC_4x4_UNorm, + ETextureFormat::RgbaAstc5x4 => DxgiFormat::ASTC_5x4_UNorm, + ETextureFormat::RgbaAstc5x5 => DxgiFormat::ASTC_5x5_UNorm, + ETextureFormat::RgbaAstc6x5 => DxgiFormat::ASTC_6x5_UNorm, + ETextureFormat::RgbaAstc6x6 => DxgiFormat::ASTC_6x6_UNorm, + ETextureFormat::RgbaAstc8x5 => DxgiFormat::ASTC_8x5_UNorm, + ETextureFormat::RgbaAstc8x6 => DxgiFormat::ASTC_8x6_UNorm, + ETextureFormat::RgbaAstc8x8 => DxgiFormat::ASTC_8x8_UNorm, + ETextureFormat::RgbaAstc10x5 => DxgiFormat::ASTC_10x5_UNorm, + ETextureFormat::RgbaAstc10x6 => DxgiFormat::ASTC_10x6_UNorm, + ETextureFormat::RgbaAstc10x8 => DxgiFormat::ASTC_10x8_UNorm, + ETextureFormat::RgbaAstc10x10 => DxgiFormat::ASTC_10x10_UNorm, + ETextureFormat::RgbaAstc12x10 => DxgiFormat::ASTC_12x10_UNorm, + ETextureFormat::RgbaAstc12x12 => DxgiFormat::ASTC_12x12_UNorm, + ETextureFormat::RgbaAstc4x4Srgb => DxgiFormat::ASTC_4x4_UNorm_sRGB, + ETextureFormat::RgbaAstc5x4Srgb => DxgiFormat::ASTC_5x4_UNorm_sRGB, + ETextureFormat::RgbaAstc5x5Srgb => DxgiFormat::ASTC_5x5_UNorm_sRGB, + ETextureFormat::RgbaAstc6x5Srgb => DxgiFormat::ASTC_6x5_UNorm_sRGB, + ETextureFormat::RgbaAstc6x6Srgb => DxgiFormat::ASTC_6x6_UNorm_sRGB, + ETextureFormat::RgbaAstc8x5Srgb => DxgiFormat::ASTC_8x5_UNorm_sRGB, + ETextureFormat::RgbaAstc8x6Srgb => DxgiFormat::ASTC_8x6_UNorm_sRGB, + ETextureFormat::RgbaAstc8x8Srgb => DxgiFormat::ASTC_8x8_UNorm_sRGB, + ETextureFormat::RgbaAstc10x5Srgb => DxgiFormat::ASTC_10x5_UNorm_sRGB, + ETextureFormat::RgbaAstc10x6Srgb => DxgiFormat::ASTC_10x6_UNorm_sRGB, + ETextureFormat::RgbaAstc10x8Srgb => DxgiFormat::ASTC_10x8_UNorm_sRGB, + ETextureFormat::RgbaAstc10x10Srgb => DxgiFormat::ASTC_10x10_UNorm_sRGB, + ETextureFormat::RgbaAstc12x10Srgb => DxgiFormat::ASTC_12x10_UNorm_sRGB, + ETextureFormat::RgbaAstc12x12Srgb => DxgiFormat::ASTC_12x12_UNorm_sRGB, + ETextureFormat::BptcUfloat => DxgiFormat::BC6H_UF16, + ETextureFormat::BptcSfloat => DxgiFormat::BC6H_SF16, + ETextureFormat::BptcUnorm => DxgiFormat::BC7_UNorm, + ETextureFormat::BptcUnormSrgb => DxgiFormat::BC7_UNorm_sRGB, + } +} diff --git a/src/util/lzss.rs b/src/util/lzss.rs index 1dc0414..a3081ad 100644 --- a/src/util/lzss.rs +++ b/src/util/lzss.rs @@ -1,5 +1,9 @@ +use std::borrow::Cow; + +use anyhow::{bail, Result}; + /// https://wiki.axiodl.com/w/LZSS_Compression -pub fn decompress(mut input: &[u8], output: &mut [u8]) { +pub fn decompress(mut input: &[u8], output: &mut [u8]) -> bool { let group_len = 2usize.pow(M as u32 - 1); let mut out_cur = 0usize; @@ -32,5 +36,46 @@ pub fn decompress(mut input: &[u8], output: &mut [u8]) { group -= 1; } - debug_assert_eq!(out_cur, output.len()); + out_cur == output.len() +} + +pub fn decompress_buffer( + compressed_data: &[u8], + decompressed_size: u64, +) -> Result<(u32, Cow<[u8]>)> { + if compressed_data.len() < 4 { + bail!("Invalid compressed data size: {}", compressed_data.len()); + } + if compressed_data[0..4] == [0u8; 4] { + // Shortcut for uncompressed data + return Ok((0, Cow::Borrowed(&compressed_data[4..]))); + } + let mut out = vec![0u8; decompressed_size as usize]; + let mode = decompress_into(compressed_data, &mut out)?; + Ok((mode, Cow::Owned(out))) +} + +pub fn decompress_into(compressed_data: &[u8], out: &mut [u8]) -> Result { + if compressed_data.len() < 4 { + bail!("Invalid compressed data size: {}", compressed_data.len()); + } + let mode = u32::from_le_bytes(compressed_data[0..4].try_into().unwrap()); + let data = &compressed_data[4..]; + if !match mode { + 0 => { + if data.len() == out.len() { + out.copy_from_slice(data); + true + } else { + false + } + } + 1 => decompress::<1>(data, out), + 2 => decompress::<2>(data, out), + 3 => decompress::<3>(data, out), + _ => bail!("Unsupported compression mode {}", mode), + } { + bail!("Decompression failed"); + } + Ok(mode) } diff --git a/src/util/mod.rs b/src/util/mod.rs index 9120437..382095d 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,5 @@ +pub mod astc; +pub mod dds; pub mod file; pub mod lzss;