Add txtr convert

This commit is contained in:
Luke Street
2023-02-14 16:42:27 -05:00
parent 6b04cde444
commit 9f74d103fd
16 changed files with 853 additions and 98 deletions
Generated
+45 -1
View File
@@ -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"
+4 -1
View File
@@ -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"
+15
View File
@@ -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.
+1 -1
View File
@@ -8,6 +8,6 @@ use argh::FromArgs;
#[argh(subcommand)]
pub enum SubCommand {
Pak(pak::Args),
// Txtr(txtr::Args),
Txtr(txtr::Args),
Fmv0(fmv0::Args),
}
+6 -7
View File
@@ -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<AssetInfo> = None;
let mut meta: Option<&[u8]> = None;
let mut name: Option<String> = 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);
+98 -23
View File
@@ -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<STextureMetaData> = 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(())
}
+5 -4
View File
@@ -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<W: Write + Seek, CB>(&mut self, w: &mut W, e: Endian, mut cb: CB) -> Result<()>
where CB: FnMut(&mut W) -> Result<()> {
pub fn write<W, CB>(&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()?;
+1
View File
@@ -1,6 +1,7 @@
pub mod chunk;
pub mod pack;
pub mod rfrm;
pub mod txtr;
use std::fmt::{Debug, Display, Formatter, Write};
+40 -55
View File
@@ -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<Package> {
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<AssetDirectory> = None;
let mut meta: HashMap<Uuid, &[u8]> = 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,
+3 -3
View File
@@ -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 {
+403
View File
@@ -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<u32>,
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<STextureReadInfo>,
#[bw(try_calc = buffers.len().try_into())]
pub buffer_count: u32,
#[br(count = buffer_count)]
pub buffers: Vec<STextureCompressedBufferInfo>,
}
#[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<Vec<u8>> {
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,
)?)
}
+1 -1
View File
@@ -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 {
+55
View File
@@ -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: Write + Seek>(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)
}
}
+127
View File
@@ -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: Write>(w: &mut W, head: &STextureHeader, data: Vec<u8>) -> 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,
}
}
+47 -2
View File
@@ -1,5 +1,9 @@
use std::borrow::Cow;
use anyhow::{bail, Result};
/// https://wiki.axiodl.com/w/LZSS_Compression
pub fn decompress<const M: u8>(mut input: &[u8], output: &mut [u8]) {
pub fn decompress<const M: u8>(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<const M: u8>(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<u32> {
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)
}
+2
View File
@@ -1,3 +1,5 @@
pub mod astc;
pub mod dds;
pub mod file;
pub mod lzss;