mirror of
https://github.com/PrimeDecomp/retrotool.git
synced 2026-07-12 18:18:58 -07:00
Add txtr convert
This commit is contained in:
Generated
+45
-1
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,6 +1,7 @@
|
||||
pub mod chunk;
|
||||
pub mod pack;
|
||||
pub mod rfrm;
|
||||
pub mod txtr;
|
||||
|
||||
use std::fmt::{Debug, Display, Formatter, Write};
|
||||
|
||||
|
||||
+40
-55
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod astc;
|
||||
pub mod dds;
|
||||
pub mod file;
|
||||
pub mod lzss;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user