Implement more texture formats

The second compression method found in the game's code seems to be Zlib,
but it doesn't seem to be used in the game files. What does get used is
a compression type of `0`, which appears to be uncompressed data.

For DDS formats, all the ones that are currently used by in the game
files can be emitted as is. Though for some of them, other tools might
not be able to display them.
This commit is contained in:
Lucas Schwiderski 2024-07-26 10:43:42 +02:00
parent 233389ebb1
commit bcbc005df7
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
7 changed files with 393 additions and 192 deletions

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,