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}