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#[cfg_attr(
18    feature = "tracing",
19    tracing::instrument(level = "debug", skip(writer, dds))
20)]
21pub fn write_dds<W: Write>(writer: &mut W, dds: &Dds) -> Result<(), DdsBinaryError> {
22    match &dds.source_flavor {
23        DdsSourceFlavor::CResDds(cresdds) => write_cresdds_prefix(writer, dds, cresdds),
24        DdsSourceFlavor::Standard => {
25            if let Some(format) = dds.d3d_format() {
26                validate_canonical_standard_d3d_format(format)?;
27            }
28            dds.to_ddsfile().write(writer).map_err(DdsBinaryError::from)
29        }
30    }
31}
32
33/// Writes the 20-byte CResDDS prefix header followed by the raw surface payload.
34///
35/// Format (`0x14` bytes):
36/// - `+0x00` `u32 LE`: width
37/// - `+0x04` `u32 LE`: height
38/// - `+0x08` `u8`: bytes-per-pixel code (`3`=DXT1, `4`=DXT5)
39/// - `+0x09..+0x0B` `[u8; 3]`: reserved gap bytes (preserved verbatim)
40/// - `+0x0C` `u32 LE`: base-level payload size
41/// - `+0x10` `f32 LE`: alpha-mean metadata
42///
43/// Ghidra evidence: `CResDDS::OnResourceServiced` (`0x00710f30`),
44/// `CResDDS::GetDDSAttrib` (`0x00710ee0`).
45fn write_cresdds_prefix<W: Write>(
46    writer: &mut W,
47    dds: &Dds,
48    cresdds: &CResDdsHeader,
49) -> Result<(), DdsBinaryError> {
50    write_u32(writer, cresdds.width)?;
51    write_u32(writer, cresdds.height)?;
52    write_u8(writer, cresdds.bytes_per_pixel_code)?;
53    writer.write_all(&cresdds.reserved_gap_bytes)?;
54    write_u32(writer, cresdds.base_level_data_size)?;
55    write_f32(writer, cresdds.alpha_mean)?;
56    writer.write_all(&dds.data)?;
57    Ok(())
58}
59
60/// Serializes DDS data to a byte vector.
61#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(dds)))]
62pub fn write_dds_to_vec(dds: &Dds) -> Result<Vec<u8>, DdsBinaryError> {
63    let mut cursor = Cursor::new(Vec::new());
64    write_dds(&mut cursor, dds)?;
65    Ok(cursor.into_inner())
66}