Skip to main content

rakata_formats/dds/
writer.rs

1//! DDS binary writer.
2
3use std::io::{Cursor, Write};
4
5use crate::binary::{write_f32, write_u32, write_u8};
6
7use super::{
8    validate_canonical_standard_d3d_format, CResDdsHeader, Dds, DdsBinaryError, DdsSourceFlavor,
9};
10
11/// Writes DDS data to a writer.
12///
13/// Dispatches on `source_flavor`: if the container was decoded from a
14/// `CResDDS` prefix header, the output is re-emitted in that format
15/// (20-byte prefix + raw payload).  Otherwise the standard `DDS `
16/// container is written.
17///
18/// # Errors
19///
20/// [`DdsBinaryError::InvalidHeader`] when a standard container carries DXT3,
21/// which vanilla never writes and this crate refuses to emit; the check runs
22/// before anything is written. [`DdsBinaryError::Io`] and
23/// [`DdsBinaryError::Ddsfile`] come from the write itself and can leave a
24/// partial file. The prefixed `CResDDS` flavour skips the format check, since
25/// its payload is copied rather than re-encoded.
26#[cfg_attr(
27    feature = "tracing",
28    tracing::instrument(level = "debug", skip(writer, dds))
29)]
30pub fn write_dds<W: Write>(writer: &mut W, dds: &Dds) -> Result<(), DdsBinaryError> {
31    match &dds.source_flavor {
32        DdsSourceFlavor::CResDds(cresdds) => write_cresdds_prefix(writer, dds, cresdds),
33        DdsSourceFlavor::Standard => {
34            if let Some(format) = dds.d3d_format() {
35                validate_canonical_standard_d3d_format(format)?;
36            }
37            dds.to_ddsfile().write(writer).map_err(DdsBinaryError::from)
38        }
39    }
40}
41
42/// Writes the 20-byte CResDDS prefix header followed by the raw surface payload.
43///
44/// Format (`0x14` bytes):
45/// - `+0x00` `u32 LE`: width
46/// - `+0x04` `u32 LE`: height
47/// - `+0x08` `u8`: bytes-per-pixel code (`3`=DXT1, `4`=DXT5)
48/// - `+0x09..+0x0B` `[u8; 3]`: reserved gap bytes (preserved verbatim)
49/// - `+0x0C` `u32 LE`: base-level payload size
50/// - `+0x10` `f32 LE`: alpha-mean metadata
51///
52/// Ghidra evidence: `CResDDS::OnResourceServiced` (`0x00710f30`),
53/// `CResDDS::GetDDSAttrib` (`0x00710ee0`).
54fn write_cresdds_prefix<W: Write>(
55    writer: &mut W,
56    dds: &Dds,
57    cresdds: &CResDdsHeader,
58) -> Result<(), DdsBinaryError> {
59    write_u32(writer, cresdds.width)?;
60    write_u32(writer, cresdds.height)?;
61    write_u8(writer, cresdds.bytes_per_pixel_code)?;
62    writer.write_all(&cresdds.reserved_gap_bytes)?;
63    write_u32(writer, cresdds.base_level_data_size)?;
64    write_f32(writer, cresdds.alpha_mean)?;
65    writer.write_all(&dds.data)?;
66    Ok(())
67}
68
69/// Serializes DDS data to a byte vector.
70///
71/// # Errors
72///
73/// [`DdsBinaryError::InvalidHeader`] on the terms [`write_dds`] gives, and
74/// [`DdsBinaryError::Ddsfile`] from encoding a standard container. The `Vec`
75/// target has no I/O to fail at.
76#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(dds)))]
77pub fn write_dds_to_vec(dds: &Dds) -> Result<Vec<u8>, DdsBinaryError> {
78    let mut cursor = Cursor::new(Vec::new());
79    write_dds(&mut cursor, dds)?;
80    Ok(cursor.into_inner())
81}