Skip to main content

rsx_reader/
cgbinary.rs

1//! Big-endian revision-6 CgBinaryProgram containers, with offsets relative to
2//! their own header. See the crate README for the layout sources and scope.
3use primase_foundations::bytes::{Endian::Big, View};
4use std::ops::Range;
5
6/// What is wrong with a container, and where.
7#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
8#[error("CgBinaryProgram at byte {offset}: {detail}")]
9pub struct Error {
10    /// The byte offset in the container where the problem is.
11    pub offset: usize,
12    /// What was expected there, or what was found instead.
13    pub detail: String,
14}
15
16fn error(offset: usize, detail: impl Into<String>) -> Error {
17    Error {
18        offset,
19        detail: detail.into(),
20    }
21}
22
23/// The offset `delta` bytes after `at`, which the caller has checked to lie in the container.
24fn plus(at: usize, delta: usize) -> Result<usize, Error> {
25    at.checked_add(delta)
26        .ok_or_else(|| error(at, "offset overflows the address space"))
27}
28
29/// The stage-specific header of a vertex program.
30#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct Vertex {
32    /// The number of 16-byte microcode slots.
33    pub instruction_count: u32,
34    /// The slot the program starts at.
35    pub instruction_slot: u32,
36    /// The number of registers it uses.
37    pub register_count: u32,
38    /// The vertex attributes it reads, a mask.
39    pub attribute_input_mask: u32,
40    /// The attributes it writes, a mask.
41    pub attribute_output_mask: u32,
42    /// The user clip planes it writes, a mask.
43    pub user_clip_mask: u32,
44}
45
46/// The stage-specific header of a fragment program.
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub struct Fragment {
49    /// Number of 16-byte microcode slots, including embedded constants.
50    pub instruction_count: u32,
51    /// The interpolated attributes it reads, a mask.
52    pub attribute_input_mask: u32,
53    /// Two bits per texture unit, kept as encoded.
54    pub partial_tex_type: u32,
55    /// The texture coordinate sets it reads, a mask.
56    pub tex_coords_input_mask: u16,
57    /// The sets that are two-component, a mask.
58    pub tex_coords_2d: u16,
59    /// The sets interpolated at the centroid, a mask.
60    pub tex_coords_centroid: u16,
61    /// The number of registers it uses.
62    pub register_count: u8,
63    /// Whether the colour output is written from the half-precision register 0.
64    pub output_from_h0: bool,
65    /// Whether the program writes depth.
66    pub depth_replace: bool,
67    /// Whether the program can kill the pixel.
68    pub pixel_kill: bool,
69    /// The two padding bytes after the flags, as stored.
70    pub padding: [u8; 2],
71}
72
73/// A program's stage and its stage-specific header.
74#[derive(Clone, Debug, PartialEq, Eq)]
75pub enum Stage {
76    /// A vertex program.
77    Vertex(Vertex),
78    /// A fragment program.
79    Fragment(Fragment),
80}
81
82/// One parameter record of the container.
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct Parameter<'a> {
85    /// Original CGtype, CGresource and CGenum values, including unknown values.
86    pub ty: u32,
87    /// The CGresource (a texture unit, an attribute, ...).
88    pub resource: u32,
89    /// The CGenum variability (varying, uniform, ...).
90    pub variability: u32,
91    /// The index within the resource, as a signed value.
92    pub resource_index: i32,
93    /// NUL-terminated source strings, excluding their terminator; no encoding conversion.
94    pub name: Option<&'a [u8]>,
95    /// The semantic string, where the parameter has one.
96    pub semantic: Option<&'a [u8]>,
97    /// The CGenum direction (in, out, ...).
98    pub direction: u32,
99    /// -1 denotes a global parameter.
100    pub parameter_number: i32,
101    /// Whether the program references the parameter.
102    pub referenced: bool,
103    /// Whether the parameter is shared between programs.
104    pub shared: bool,
105    /// Big-endian float bits, preserving NaNs and signed zeros. Float scalar,
106    /// vector and matrix types are supported; other types with defaults refuse.
107    pub default_value: Option<Vec<u32>>,
108    /// Byte offsets of 16-byte constants in this program's microcode.
109    pub embedded_constants: Vec<u32>,
110}
111
112impl Parameter<'_> {
113    /// CG_TEXUNIT0 through CG_TEXUNIT15. A resource index alone is not a texunit.
114    pub fn texture_unit(&self) -> Option<u32> {
115        self.resource.checked_sub(2048).filter(|&n| n < 16)
116    }
117}
118
119/// A container as read.
120#[derive(Clone, Debug, PartialEq, Eq)]
121pub struct Program<'a> {
122    /// The profile: 7003 vertex, 7004 fragment.
123    pub profile: u32,
124    /// The binary revision (6).
125    pub revision: u32,
126    /// The stage-specific header.
127    pub stage: Stage,
128    /// The parameter records, in order.
129    pub parameters: Vec<Parameter<'a>>,
130    /// Original byte order and inline constants retained verbatim.
131    pub microcode: &'a [u8],
132    /// Exactly the advertised container size, excluding any caller's padding.
133    pub bytes: &'a [u8],
134}
135
136struct Reader<'a> {
137    bytes: &'a [u8],
138    fixed: Vec<Range<usize>>,
139}
140
141impl<'a> Reader<'a> {
142    fn bytes(&self, at: usize, n: usize) -> Result<&'a [u8], Error> {
143        View::new(self.bytes)
144            .slice(at, n)
145            .map_err(|e| error(at, e.to_string()))
146    }
147    fn word(&self, at: usize) -> Result<u32, Error> {
148        View::new(self.bytes)
149            .u32(at, Big)
150            .map_err(|e| error(at, e.to_string()))
151    }
152    fn offset(&self, at: usize) -> Result<usize, Error> {
153        usize::try_from(self.word(at)?).map_err(|_| error(at, "offset outside address space"))
154    }
155    fn boolean(&self, at: usize, byte: bool) -> Result<bool, Error> {
156        let n = if byte {
157            u32::from(
158                *self
159                    .bytes(at, 1)?
160                    .first()
161                    .ok_or_else(|| error(at, "a boolean byte"))?,
162            )
163        } else {
164            self.word(at)?
165        };
166        match n {
167            0 => Ok(false),
168            1 => Ok(true),
169            _ => Err(error(at, "boolean is neither zero nor one")),
170        }
171    }
172    fn range(&self, at: usize, count: usize, stride: usize) -> Result<Range<usize>, Error> {
173        let n = count
174            .checked_mul(stride)
175            .ok_or_else(|| error(at, "array size overflow"))?;
176        self.bytes(at, n)?;
177        Ok(at..plus(at, n)?)
178    }
179    fn reserve(&mut self, r: Range<usize>) -> Result<(), Error> {
180        if !r.start.is_multiple_of(4) {
181            return Err(error(r.start, "unaligned structure"));
182        }
183        self.metadata(r.clone())?;
184        if !r.is_empty() {
185            self.fixed.push(r);
186        }
187        Ok(())
188    }
189    fn metadata(&self, r: Range<usize>) -> Result<(), Error> {
190        if !r.is_empty() && self.fixed.iter().any(|f| r.start < f.end && f.start < r.end) {
191            return Err(error(
192                r.start,
193                "overlapping structures or metadata inside a fixed structure",
194            ));
195        }
196        Ok(())
197    }
198    fn string(&self, field: usize) -> Result<Option<&'a [u8]>, Error> {
199        let at = self.offset(field)?;
200        if at == 0 {
201            return Ok(None);
202        }
203        let tail = self
204            .bytes
205            .get(at..)
206            .ok_or_else(|| error(field, "string offset outside container"))?;
207        let len = tail
208            .iter()
209            .position(|&b| b == 0)
210            .ok_or_else(|| error(at, "unterminated string"))?;
211        let r = self.range(at, plus(len, 1)?, 1)?;
212        self.metadata(r)?;
213        Ok(Some(self.bytes(at, len)?))
214    }
215    fn floats(&self, field: usize, ty: u32) -> Result<Option<Vec<u32>>, Error> {
216        let at = self.offset(field)?;
217        if at == 0 {
218            return Ok(None);
219        }
220        let count = match ty {
221            1045 | 1091 => 1,
222            1046..=1048 => (ty.saturating_sub(1044)) as usize,
223            1049..=1064 => {
224                let m = ty.saturating_sub(1049);
225                ((m / 4)
226                    .saturating_add(1)
227                    .saturating_mul((m % 4).saturating_add(1))) as usize
228            }
229            _ => {
230                return Err(error(
231                    field,
232                    format!("default values for CGtype {ty} are unsupported"),
233                ));
234            }
235        };
236        if !at.is_multiple_of(4) {
237            return Err(error(field, "unaligned default value"));
238        }
239        let r = self.range(at, count, 4)?;
240        self.metadata(r.clone())?;
241        r.step_by(4)
242            .map(|p| self.word(p))
243            .collect::<Result<Vec<_>, _>>()
244            .map(Some)
245    }
246    fn embedded(&self, field: usize, ucode_size: usize) -> Result<Vec<u32>, Error> {
247        let at = self.offset(field)?;
248        if at == 0 {
249            return Ok(Vec::new());
250        }
251        if !at.is_multiple_of(4) {
252            return Err(error(field, "unaligned embedded constant table"));
253        }
254        let count = self.offset(at)?;
255        let r = self.range(
256            at,
257            count
258                .checked_add(1)
259                .ok_or_else(|| error(at, "constant count overflow"))?,
260            4,
261        )?;
262        self.metadata(r.clone())?;
263        let mut offsets = Vec::new();
264        for field in r.step_by(4).skip(1) {
265            let offset = self.word(field)?;
266            let n = usize::try_from(offset).map_err(|_| error(field, "constant outside address space"))?;
267            if !n.is_multiple_of(16) || n.checked_add(16).is_none_or(|end| end > ucode_size) {
268                return Err(error(
269                    field,
270                    "embedded constant is unaligned or outside microcode",
271                ));
272            }
273            offsets.push(offset);
274        }
275        Ok(offsets)
276    }
277}
278
279/// Read one container with no trailing bytes. Padding and concatenation belong
280/// to the calling file format; use [`parse_prefix`] for those.
281pub fn parse(bytes: &[u8]) -> Result<Program<'_>, Error> {
282    let p = parse_prefix(bytes)?;
283    if p.bytes.len() != bytes.len() {
284        return Err(error(p.bytes.len(), "trailing bytes after container"));
285    }
286    Ok(p)
287}
288
289/// Read the advertised first container, keeping every offset relative to it.
290/// Refuses unsupported profiles/revisions, out-of-bounds or overlapping fixed
291/// structures, invalid booleans, unterminated strings, and invalid constants.
292pub fn parse_prefix(bytes: &[u8]) -> Result<Program<'_>, Error> {
293    let header = Reader {
294        bytes,
295        fixed: Vec::new(),
296    };
297    header.bytes(0, 32)?;
298    let profile = header.word(0)?;
299    if !matches!(profile, 7003 | 7004) {
300        return Err(error(
301            0,
302            "unsupported profile (expected vertex 7003 or fragment 7004)",
303        ));
304    }
305    let revision = header.word(4)?;
306    if revision != 6 {
307        return Err(error(4, "unsupported binary revision (expected 6)"));
308    }
309    let total = header.offset(8)?;
310    let mut r = Reader {
311        bytes: header.bytes(0, total)?,
312        fixed: Vec::new(),
313    };
314    r.bytes(0, 32)?;
315    r.fixed.push(0..32);
316    let count = r.offset(12)?;
317    let params = r.offset(16)?;
318    let stage_at = r.offset(20)?;
319    let ucode_size = r.offset(24)?;
320    let ucode_at = r.offset(28)?;
321    let param_range = r.range(params, count, 48)?;
322    let params_at = param_range.clone().step_by(48);
323    r.reserve(param_range)?;
324    let stage_range = r.range(stage_at, 1, 24)?;
325    r.reserve(stage_range)?;
326    let ucode_range = r.range(ucode_at, ucode_size, 1)?;
327    r.reserve(ucode_range)?;
328    if !ucode_at.is_multiple_of(16) || !ucode_size.is_multiple_of(16) {
329        return Err(error(28, "microcode offset or size is not 16-byte aligned"));
330    }
331    let stage_bytes = r.bytes(stage_at, 24)?;
332    let s = Reader {
333        bytes: stage_bytes,
334        fixed: Vec::new(),
335    };
336    let instruction_count = s.word(0)?;
337    if usize::try_from(instruction_count)
338        .ok()
339        .and_then(|slots| slots.checked_mul(16))
340        != Some(ucode_size)
341    {
342        return Err(error(
343            stage_at,
344            "instruction slot count disagrees with microcode size",
345        ));
346    }
347    let stage = if profile == 7003 {
348        Stage::Vertex(Vertex {
349            instruction_count,
350            instruction_slot: s.word(4)?,
351            register_count: s.word(8)?,
352            attribute_input_mask: s.word(12)?,
353            attribute_output_mask: s.word(16)?,
354            user_clip_mask: s.word(20)?,
355        })
356    } else {
357        let stage_view = View::new(stage_bytes);
358        let half = |at| {
359            stage_view
360                .u16(at, Big)
361                .map_err(|e| error(stage_at.saturating_add(at), e.to_string()))
362        };
363        let flag = |at| -> Result<bool, Error> { r.boolean(plus(stage_at, at)?, true) };
364        let byte = |at| {
365            stage_view
366                .u8(at)
367                .map_err(|e| error(stage_at.saturating_add(at), e.to_string()))
368        };
369        Stage::Fragment(Fragment {
370            instruction_count,
371            attribute_input_mask: s.word(4)?,
372            partial_tex_type: s.word(8)?,
373            tex_coords_input_mask: half(12)?,
374            tex_coords_2d: half(14)?,
375            tex_coords_centroid: half(16)?,
376            register_count: byte(18)?,
377            output_from_h0: flag(19)?,
378            depth_replace: flag(20)?,
379            pixel_kill: flag(21)?,
380            padding: [byte(22)?, byte(23)?],
381        })
382    };
383    let mut parameters = Vec::new();
384    for at in params_at {
385        let field = |delta| plus(at, delta);
386        let ty = r.word(at)?;
387        // The source signed fields are two's-complement 32-bit values.
388        parameters.push(Parameter {
389            ty,
390            resource: r.word(field(4)?)?,
391            variability: r.word(field(8)?)?,
392            resource_index: r.word(field(12)?)? as i32,
393            name: r.string(field(16)?)?,
394            default_value: r.floats(field(20)?, ty)?,
395            embedded_constants: r.embedded(field(24)?, ucode_size)?,
396            semantic: r.string(field(28)?)?,
397            direction: r.word(field(32)?)?,
398            parameter_number: r.word(field(36)?)? as i32,
399            referenced: r.boolean(field(40)?, false)?,
400            shared: r.boolean(field(44)?, false)?,
401        });
402    }
403    Ok(Program {
404        profile,
405        revision,
406        stage,
407        parameters,
408        microcode: r.bytes(ucode_at, ucode_size)?,
409        bytes: r.bytes,
410    })
411}