WIP: Implement texture files #191

Draft
lucas wants to merge 14 commits from feat/textures into master
7 changed files with 393 additions and 192 deletions
Showing only changes of commit bcbc005df7 - Show all commits

13
Cargo.lock generated
View file

@ -1123,6 +1123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666"
dependencies = [
"crc32fast",
"libz-sys",
"miniz_oxide 0.8.0",
]
@ -2036,6 +2037,17 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "libz-sys"
version = "1.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
@ -3169,6 +3181,7 @@ dependencies = [
"color-eyre",
"csv-async",
"fastrand",
"flate2",
"futures",
"futures-util",
"glob",

View file

@ -29,6 +29,7 @@ druid = { version = "0.8", features = ["im", "serde", "image", "png", "jpeg", "b
druid-widget-nursery = "0.1"
dtmt-shared = { path = "lib/dtmt-shared" }
fastrand = "2.1.0"
flate2 = { version = "1.0.30", features = ["zlib"] }
futures = "0.3.25"
futures-util = "0.3.24"
glob = "0.3.0"

View file

@ -275,7 +275,13 @@ struct ExtractOptions<'a> {
#[tracing::instrument(
skip(ctx, options),
fields(decompile = options.decompile, flatten = options.flatten, dry_run = options.dry_run)
fields(
bundle_name = tracing::field::Empty,
bundle_hash = tracing::field::Empty,
decompile = options.decompile,
flatten = options.flatten,
dry_run = options.dry_run,
)
)]
async fn extract_bundle<P1, P2>(
ctx: Arc<sdk::Context>,
@ -318,6 +324,11 @@ where
let bundle = {
let data = fs::read(path.as_ref()).await?;
let name = Bundle::get_name_from_path(&ctx, path.as_ref());
{
let span = tracing::span::Span::current();
span.record("bundle_hash", format!("{:X}", name));
span.record("bundle_name", name.display().to_string());
}
Bundle::from_binary(&ctx, name, data)?
};

View file

@ -10,6 +10,7 @@ byteorder = { workspace = true }
color-eyre = { workspace = true }
csv-async = { workspace = true }
fastrand = { workspace = true }
flate2 = { workspace = true }
futures = { workspace = true }
futures-util = { workspace = true }
glob = { workspace = true }

View file

@ -42,6 +42,26 @@ impl<T: FromBinary> FromBinary for Vec<T> {
}
}
pub fn flags_from_bits<T: bitflags::Flags>(bits: T::Bits) -> T
where
<T as bitflags::Flags>::Bits: std::fmt::Binary,
{
if let Some(flags) = T::from_bits(bits) {
flags
} else {
let unknown = bits & !T::all().bits();
tracing::warn!(
"Unknown bits found for '{}': known = {:0b}, unknown = {:0b}",
std::any::type_name::<T>(),
T::all().bits(),
unknown
);
T::from_bits_truncate(bits)
}
}
pub mod sync {
use std::ffi::CStr;
use std::io::{self, Read, Seek, SeekFrom, Write};

View file

@ -5,16 +5,15 @@ use bitflags::bitflags;
use color_eyre::eyre::Context;
use color_eyre::{eyre, SectionExt};
use color_eyre::{Help, Result};
use num_traits::ToPrimitive as _;
use flate2::read::ZlibDecoder;
use oodle::{OodleLZ_CheckCRC, OodleLZ_FuzzSafe};
use serde::{Deserialize, Serialize};
use tokio::fs;
use crate::binary::sync::{ReadExt, WriteExt};
use crate::bundle::file::UserFile;
use crate::filetype::texture::dds::{DXGIFormat, ImageType};
use crate::murmur::{HashGroup, IdString32, IdString64};
use crate::{BundleFile, BundleFileType, BundleFileVariant};
use crate::{binary, BundleFile, BundleFileType, BundleFileVariant};
mod dds;
@ -43,7 +42,7 @@ struct TextureDefinitionOutput {
}
bitflags! {
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Default)]
struct TextureFlags: u32 {
const STREAMABLE = 0b0000_0001;
const UNKNOWN = 1 << 1;
@ -57,7 +56,7 @@ struct TextureHeaderMipInfo {
size: usize,
}
#[derive(Clone, Debug)]
#[derive(Clone, Default)]
struct TextureHeader {
flags: TextureFlags,
n_streamable_mipmaps: usize,
@ -67,13 +66,33 @@ struct TextureHeader {
meta_size: usize,
}
impl std::fmt::Debug for TextureHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TextureHeader")
.field("flags", &self.flags)
.field("n_streamable_mipmaps", &self.n_streamable_mipmaps)
.field("width", &self.width)
.field("height", &self.height)
.field("mip_infos", &{
let mut s = self
.mip_infos
.iter()
.fold(String::from("["), |mut s, info| {
s.push_str(&format!("{}/{}, ", info.offset, info.size));
s
});
s.push(']');
s
})
.field("meta_size", &self.meta_size)
.finish()
}
}
impl TextureHeader {
#[tracing::instrument(skip(r))]
fn from_binary(mut r: impl ReadExt) -> Result<Self> {
let flags = r.read_u32().and_then(|bits| {
TextureFlags::from_bits(bits)
.ok_or_else(|| eyre::eyre!("Unknown bits set in TextureFlags: {:032b}", bits))
})?;
let flags = r.read_u32().map(binary::flags_from_bits)?;
let n_streamable_mipmaps = r.read_u32()? as usize;
let width = r.read_u32()? as usize;
let height = r.read_u32()? as usize;
@ -121,7 +140,7 @@ impl TextureHeader {
}
}
#[derive(Clone, Debug)]
#[derive(Clone)]
struct Texture {
header: TextureHeader,
data: Vec<u8>,
@ -129,6 +148,37 @@ struct Texture {
category: IdString32,
}
impl std::fmt::Debug for Texture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut out = f.debug_struct("Texture");
out.field("header", &self.header);
if self.data.len() <= 5 {
out.field("data", &format!("{:x?}", &self.data));
} else {
out.field(
"data",
&format!("{:x?}.. ({} bytes)", &self.data[..5], &self.data.len()),
);
}
if let Some(stream) = self.stream.as_ref() {
if stream.len() <= 5 {
out.field("stream", &format!("{:x?}", &stream));
} else {
out.field(
"stream",
&format!("{:x?}.. ({} bytes)", &stream[..5], &stream.len()),
);
}
} else {
out.field("stream", &"None");
}
out.field("category", &self.category).finish()
}
}
impl Texture {
#[tracing::instrument(skip(data, chunks))]
fn decompress_stream_data(mut data: impl Read, chunks: impl AsRef<[usize]>) -> Result<Vec<u8>> {
@ -214,35 +264,83 @@ impl Texture {
Ok(out)
}
#[tracing::instrument(skip(ctx, r, stream_r))]
#[tracing::instrument(
"Texture::from_binary",
skip(ctx, r, stream_r),
fields(
compression_type = tracing::field::Empty,
compressed_size = tracing::field::Empty,
uncompressed_size = tracing::field::Empty,
)
)]
fn from_binary(
ctx: &crate::Context,
mut r: impl Read + Seek,
mut stream_r: Option<impl Read>,
) -> Result<Self> {
// Looking at the executable in IDA, there is one other valid value: `2`.
// If this ever comes up in the game data, I'll have to reverse engineer the
// (de)compression algorithm through IDA.
let compression_type = r.read_u32()?;
eyre::ensure!(
compression_type == 1,
"Unknown compression type for texture '{}'",
compression_type
);
let compressed_size = r.read_u32()? as usize;
let uncompressed_size = r.read_u32()? as usize;
let out_buf = {
{
let span = tracing::Span::current();
span.record("compression_type", compression_type);
span.record("compressed_size", compressed_size);
span.record("uncompressed_size", uncompressed_size);
}
let mut comp_buf = vec![0; compressed_size];
r.read_exact(&mut comp_buf)?;
oodle::decompress(
let out_buf = match compression_type {
// Uncompressed
// This one never seems to contain the additional `TextureHeader` metadata,
// so we return early in this branch.
0 => {
eyre::ensure!(
compressed_size == 0 && uncompressed_size == 0,
"Cannot handle texture with compression_type == 0, but buffer sizes > 0"
);
tracing::trace!("Found raw texture");
let pos = r.stream_position()?;
let end = {
r.seek(SeekFrom::End(0))?;
let end = r.stream_position()?;
r.seek(SeekFrom::Start(pos))?;
end
};
// Reads until the last u32.
let mut data = vec![0u8; (end - pos - 4) as usize];
r.read_exact(&mut data)?;
let category = r.read_u32().map(IdString32::from)?;
return Ok(Self {
header: TextureHeader::default(),
data,
stream: None,
category,
});
}
1 => oodle::decompress(
comp_buf,
uncompressed_size,
OodleLZ_FuzzSafe::No,
OodleLZ_CheckCRC::No,
)?
)?,
2 => {
let mut decoder = ZlibDecoder::new(comp_buf.as_slice());
let mut buf = Vec::with_capacity(uncompressed_size);
decoder.read_to_end(&mut buf)?;
buf
}
_ => eyre::bail!(
"Unknown compression type for texture '{}'",
compression_type
),
};
eyre::ensure!(
@ -339,73 +437,67 @@ impl Texture {
serde_sjson::to_string(&texture).wrap_err("Failed to serialize texture definition")
}
#[tracing::instrument(skip(self))]
fn to_user_files(&self, name: String) -> Result<Vec<UserFile>> {
let mut files = Vec::with_capacity(2);
{
let data = self.to_sjson(name.clone())?.as_bytes().to_vec();
let name = PathBuf::from(&name)
.with_extension("texture")
.display()
.to_string();
files.push(UserFile::with_name(data, name));
}
// For debugging purposes, also extract the raw files
if cfg!(debug_assertions) {
if let Some(stream) = &self.stream {
let stream_name = PathBuf::from(&name).with_extension("stream");
files.push(UserFile::with_name(
stream.clone(),
stream_name.display().to_string(),
));
}
let name = PathBuf::from(&name)
.with_extension("raw.dds")
.display()
.to_string();
files.push(UserFile::with_name(self.data.clone(), name));
}
{
#[tracing::instrument(fields(
dds_header = tracing::field::Empty,
dx10_header = tracing::field::Empty,
image_format = tracing::field::Empty,
))]
fn create_dds_user_file(&self, name: String) -> Result<UserFile> {
let mut data = Cursor::new(&self.data);
let mut dds_header =
dds::DDSHeader::from_binary(&mut data).wrap_err("Failed to read DDS header")?;
eyre::ensure!(
dds_header.pixel_format.flags.contains(dds::DDPF::FOURCC)
&& dds_header.pixel_format.four_cc == dds::FOURCC_DX10,
"Only DX10 textures are currently supported."
);
{
let span = tracing::Span::current();
span.record("dds_header", format!("{:?}", dds_header));
}
if !dds_header.pixel_format.flags.contains(dds::DDPF::FOURCC) {
tracing::debug!("Found DDS without FourCC. Dumping raw data");
return Ok(UserFile::with_name(self.data.clone(), name));
}
// eyre::ensure!(
// dds_header.pixel_format.four_cc == dds::FourCC::DX10,
// "Only DX10 textures are currently supported. FourCC == {}",
// dds_header.pixel_format.four_cc,
// );
let dx10_header =
dds::Dx10Header::from_binary(&mut data).wrap_err("Failed to read DX10 header")?;
match dx10_header.dxgi_format {
DXGIFormat::BC1_UNORM
| DXGIFormat::BC3_UNORM
| DXGIFormat::BC4_UNORM
| DXGIFormat::BC5_UNORM
| DXGIFormat::BC6H_UF16
| DXGIFormat::BC7_UNORM => {}
_ => {
eyre::bail!(
"Unsupported DXGI format: {} (0x{:0X})",
dx10_header.dxgi_format,
dx10_header.dxgi_format.to_u32().unwrap_or_default()
);
}
{
let span = tracing::Span::current();
span.record("dx10_header", format!("{:?}", dx10_header));
}
let stingray_image_format =
dds::stripped_format_from_header(&dds_header, &dx10_header)?;
eyre::ensure!(
stingray_image_format.image_type == ImageType::Image2D,
"Unsupported image type: {}",
stingray_image_format.image_type,
);
// match dx10_header.dxgi_format {
// DXGIFormat::BC1_UNORM
// | DXGIFormat::BC3_UNORM
// | DXGIFormat::BC4_UNORM
// | DXGIFormat::BC5_UNORM
// | DXGIFormat::BC6H_UF16
// | DXGIFormat::BC7_UNORM => {}
// _ => {
// eyre::bail!(
// "Unsupported DXGI format: {} (0x{:0X})",
// dx10_header.dxgi_format,
// dx10_header.dxgi_format.to_u32().unwrap_or_default()
// );
// }
// }
let stingray_image_format = dds::stripped_format_from_header(&dds_header, &dx10_header)?;
{
let span = tracing::Span::current();
span.record("image_format", format!("{:?}", stingray_image_format));
}
// eyre::ensure!(
// stingray_image_format.image_type == ImageType::Image2D,
// "Unsupported image type: {}",
// stingray_image_format.image_type,
// );
let block_size = 4 * dds_header.pitch_or_linear_size / dds_header.width;
let bits_per_block: usize = match block_size {
@ -462,29 +554,71 @@ impl Texture {
.wrap_err("Failed to write texture data")?;
};
files.push(UserFile::with_name(out_data.into_inner(), name));
Ok(UserFile::with_name(out_data.into_inner(), name))
}
#[tracing::instrument(skip(self))]
fn to_user_files(&self, name: String) -> Result<Vec<UserFile>> {
let mut files = Vec::with_capacity(2);
{
let data = self.to_sjson(name.clone())?.as_bytes().to_vec();
let name = PathBuf::from(&name)
.with_extension("texture")
.display()
.to_string();
files.push(UserFile::with_name(data, name));
}
// For debugging purposes, also extract the raw files
if cfg!(debug_assertions) {
if let Some(stream) = &self.stream {
let stream_name = PathBuf::from(&name).with_extension("stream");
files.push(UserFile::with_name(
stream.clone(),
stream_name.display().to_string(),
));
}
let name = PathBuf::from(&name)
.with_extension("raw.dds")
.display()
.to_string();
files.push(UserFile::with_name(self.data.clone(), name));
}
match self
.create_dds_user_file(name)
.wrap_err("Failed to create DDS file")
{
Ok(dds) => files.push(dds),
Err(err) => {
if cfg!(debug_assertions) {
tracing::error!(
"{:?}",
err.with_section(|| {
"Running in debug mode, continuing to produce raw files".header("Note:")
})
);
} else {
return Err(err);
}
}
};
Ok(files)
}
}
#[tracing::instrument(skip(ctx, data), fields(buf_len = data.as_ref().len()))]
#[tracing::instrument(skip(ctx, data, stream_data), fields(data_len = data.as_ref().len()))]
pub(crate) async fn decompile_data(
ctx: &crate::Context,
name: String,
data: impl AsRef<[u8]>,
stream_file_name: Option<PathBuf>,
stream_data: Option<impl AsRef<[u8]>>,
) -> Result<Vec<UserFile>> {
let mut r = Cursor::new(data.as_ref());
let mut stream_r = if let Some(file_name) = stream_file_name {
let stream_data = fs::read(&file_name)
.await
.wrap_err_with(|| format!("Failed to read stream file '{}'", file_name.display()))?;
Some(Cursor::new(stream_data))
} else {
None
};
let mut r = Cursor::new(data);
let mut stream_r = stream_data.map(Cursor::new);
let texture = Texture::from_binary(ctx, &mut r, stream_r.as_mut())?;
texture
@ -498,29 +632,20 @@ pub(crate) async fn decompile(
name: String,
variant: &BundleFileVariant,
) -> Result<Vec<UserFile>> {
if !variant.external() {
tracing::debug!("Decompiling texture from bundle data");
let stream_file_name = variant.data_file_name().map(|name| match &ctx.game_dir {
let data_file = variant.data_file_name().map(|name| match &ctx.game_dir {
Some(dir) => dir.join("bundle").join(name),
None => PathBuf::from("bundle").join(name),
});
return decompile_data(ctx, name, variant.data(), stream_file_name).await;
}
let Some(file_name) = variant.data_file_name() else {
eyre::bail!("Texture file has no data and no data file");
if variant.external() {
let Some(path) = data_file else {
eyre::bail!("File is marked external but has no data file name");
};
tracing::debug!("Decompiling texture from external file '{}'", file_name);
let path = match &ctx.game_dir {
Some(dir) => dir.join("bundle").join(file_name),
None => PathBuf::from("bundle").join(file_name),
};
tracing::trace!(path = %path.display());
tracing::debug!(
"Decompiling texture from external file '{}'",
path.display()
);
let data = fs::read(&path)
.await
@ -529,7 +654,25 @@ pub(crate) async fn decompile(
"Provide a game directory in the config file or make sure the `data` directory is next to the provided bundle."
})?;
decompile_data(ctx, name, &data, None).await
decompile_data(ctx, name, data, None::<&[u8]>).await
} else {
tracing::debug!("Decompiling texture from bundle data");
let stream_data = match data_file {
Some(path) => {
let data = fs::read(&path)
.await
.wrap_err_with(|| format!("Failed to read data file '{}'", path.display()))
.with_suggestion(|| {
"Provide a game directory in the config file or make sure the `data` directory is next to the provided bundle."
})?;
Some(data)
}
None => None,
};
decompile_data(ctx, name, variant.data(), stream_data).await
}
}
#[tracing::instrument(skip(sjson, name), fields(sjson_len = sjson.as_ref().len(), name = %name.display()))]

View file

@ -7,10 +7,10 @@ use color_eyre::Result;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::{FromPrimitive as _, ToPrimitive as _};
use crate::binary;
use crate::binary::sync::{ReadExt, WriteExt};
const MAGIC_DDS: u32 = 0x20534444;
pub const FOURCC_DX10: u32 = 0x30315844;
bitflags! {
#[derive(Clone, Copy, Debug)]
@ -75,27 +75,8 @@ bitflags! {
}
}
fn flags_from_bits<T: bitflags::Flags>(bits: T::Bits) -> T
where
<T as bitflags::Flags>::Bits: std::fmt::Binary,
{
if let Some(flags) = T::from_bits(bits) {
flags
} else {
let unknown = bits & !T::all().bits();
tracing::warn!(
"Unknown bits found for '{}': known = {:0b}, unknown = {:0b}",
std::any::type_name::<T>(),
T::all().bits(),
unknown
);
T::from_bits_truncate(bits)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
#[repr(u32)]
pub enum D3D10ResourceDimension {
Unknown = 0,
Buffer = 1,
@ -107,6 +88,7 @@ pub enum D3D10ResourceDimension {
#[allow(clippy::upper_case_acronyms)]
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, strum::Display, FromPrimitive, ToPrimitive)]
#[repr(u32)]
pub enum DXGIFormat {
UNKNOWN = 0,
R32G32B32A32_TYPELESS = 1,
@ -243,7 +225,7 @@ pub struct Dx10Header {
}
impl Dx10Header {
#[tracing::instrument(skip(r))]
#[tracing::instrument("Dx10Header::from_binary", skip(r))]
pub fn from_binary(mut r: impl ReadExt) -> Result<Self> {
let dxgi_format = r
.read_u32()
@ -251,7 +233,7 @@ impl Dx10Header {
let resource_dimension = r.read_u32().map(|val| {
D3D10ResourceDimension::from_u32(val).unwrap_or(D3D10ResourceDimension::Unknown)
})?;
let misc_flag = r.read_u32().map(flags_from_bits)?;
let misc_flag = r.read_u32().map(binary::flags_from_bits)?;
let array_size = r.read_u32()? as usize;
let misc_flags2 = r.read_u32()?;
@ -264,7 +246,7 @@ impl Dx10Header {
})
}
#[tracing::instrument(skip(w))]
#[tracing::instrument("Dx10Header::to_binary", skip(w))]
pub fn to_binary(&self, mut w: impl WriteExt) -> Result<()> {
w.write_u32(
self.dxgi_format
@ -284,10 +266,30 @@ impl Dx10Header {
}
}
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display, FromPrimitive, ToPrimitive)]
#[repr(u32)]
pub enum FourCC {
Empty = u32::MAX,
DXT1 = 0x31545844,
DXT2 = 0x33545844,
DXT5 = 0x35545844,
AXI1 = 0x31495441,
AXI2 = 0x32495441,
DX10 = 0x30315844,
D3D_A16B16G16R16 = 0x24,
D3D_R16F = 0x6F,
D3D_G16R16F = 0x70,
D3D_A16B16G16R16F = 0x71,
D3D_R32F = 0x72,
D3D_G32R32F = 0x73,
D3D_A32B32G32R32F = 0x74,
}
#[derive(Clone, Copy, Debug)]
pub struct DDSPixelFormat {
pub flags: DDPF,
pub four_cc: u32,
pub four_cc: FourCC,
pub rgb_bit_count: u32,
pub r_bit_mask: u32,
pub g_bit_mask: u32,
@ -296,7 +298,7 @@ pub struct DDSPixelFormat {
}
impl DDSPixelFormat {
#[tracing::instrument(skip(r))]
#[tracing::instrument("DDSPixelFormat::from_binary", skip(r))]
pub fn from_binary(mut r: impl ReadExt) -> Result<Self> {
let size = r.read_u32()? as usize;
eyre::ensure!(
@ -305,8 +307,17 @@ impl DDSPixelFormat {
size
);
let flags = r.read_u32().map(flags_from_bits)?;
let four_cc = r.read_u32()?;
let flags: DDPF = r.read_u32().map(binary::flags_from_bits)?;
let four_cc = if flags.contains(DDPF::FOURCC) {
r.read_u32().and_then(|bytes| {
FourCC::from_u32(bytes).ok_or_eyre(format!("Unknown FourCC value: {:08X}", bytes))
})?
} else {
r.skip_u32(0)?;
FourCC::Empty
};
let rgb_bit_count = r.read_u32()?;
let r_bit_mask = r.read_u32()?;
let g_bit_mask = r.read_u32()?;
@ -324,13 +335,13 @@ impl DDSPixelFormat {
})
}
#[tracing::instrument(skip(w))]
#[tracing::instrument("DDSPixelFormat::to_binary", skip(w))]
pub fn to_binary(&self, mut w: impl WriteExt) -> Result<()> {
// Structure size
w.write_u32(32)?;
w.write_u32(self.flags.bits())?;
w.write_u32(self.four_cc)?;
w.write_u32(self.four_cc.to_u32().unwrap_or_default())?;
w.write_u32(self.rgb_bit_count)?;
w.write_u32(self.r_bit_mask)?;
w.write_u32(self.g_bit_mask)?;
@ -356,7 +367,7 @@ pub struct DDSHeader {
}
impl DDSHeader {
#[tracing::instrument(skip(r))]
#[tracing::instrument("DDSHeader::from_binary", skip(r))]
pub fn from_binary(mut r: impl ReadExt) -> Result<Self> {
r.skip_u32(MAGIC_DDS).wrap_err("Invalid magic bytes")?;
@ -367,7 +378,7 @@ impl DDSHeader {
size
);
let flags = r.read_u32().map(flags_from_bits)?;
let flags = r.read_u32().map(binary::flags_from_bits)?;
let height = r.read_u32()? as usize;
let width = r.read_u32()? as usize;
let pitch_or_linear_size = r.read_u32()? as usize;
@ -378,8 +389,8 @@ impl DDSHeader {
r.seek(SeekFrom::Current(11 * 4))?;
let pixel_format = DDSPixelFormat::from_binary(&mut r)?;
let caps = r.read_u32().map(flags_from_bits)?;
let caps_2 = r.read_u32().map(flags_from_bits)?;
let caps = r.read_u32().map(binary::flags_from_bits)?;
let caps_2 = r.read_u32().map(binary::flags_from_bits)?;
// Skip unused and reserved bytes
r.seek(SeekFrom::Current(3 * 4))?;
@ -397,7 +408,7 @@ impl DDSHeader {
})
}
#[tracing::instrument(skip(w))]
#[tracing::instrument("DDSHeader::to_binary", skip(w))]
pub fn to_binary(&self, mut w: impl WriteExt) -> Result<()> {
w.write_u32(MAGIC_DDS)?;
@ -422,7 +433,8 @@ impl DDSHeader {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Display)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)]
#[repr(u32)]
pub enum ImageType {
Image2D = 0,
Image3D = 1,