WIP: Implement texture files #191
7 changed files with 393 additions and 192 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)?
|
||||
};
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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()))]
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Reference in a new issue