1#![deny(
28 clippy::arithmetic_side_effects,
29 clippy::indexing_slicing,
30 clippy::cast_possible_truncation
31)]
32
33use crate::{Result, error};
34
35#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum Subobject {
39 StateObjectConfig {
41 name: String,
43 flags: u32,
45 },
46 GlobalRootSignature {
48 name: String,
50 signature: Vec<u8>,
52 },
53 LocalRootSignature {
55 name: String,
57 signature: Vec<u8>,
59 },
60 ExportsAssociation {
62 name: String,
64 subobject: String,
66 exports: Vec<String>,
68 },
69 ShaderConfig {
71 name: String,
73 max_payload_bytes: u32,
75 max_attribute_bytes: u32,
77 },
78 PipelineConfig {
80 name: String,
82 max_recursion_depth: u32,
84 },
85 HitGroup {
87 name: String,
89 kind: u32,
91 any_hit: Option<String>,
93 closest_hit: Option<String>,
95 intersection: Option<String>,
97 },
98 PipelineConfig1 {
100 name: String,
102 max_recursion_depth: u32,
104 flags: u32,
106 },
107}
108
109const VERSION: u32 = 0x10;
110const STRINGS: u32 = 1;
111const INDEX_ARRAYS: u32 = 2;
112const RAW_BYTES: u32 = 5;
113const SUBOBJECTS: u32 = 6;
114const NULL: u32 = u32::MAX;
115
116fn word(data: &[u8], at: usize) -> Result<u32> {
117 at.checked_add(4)
118 .and_then(|end| data.get(at..end))
119 .and_then(|b| b.try_into().ok())
120 .map(u32::from_le_bytes)
121 .ok_or_else(|| error(at.saturating_mul(8), "RDAT: a word past the end of its part"))
122}
123
124fn part(rdat: &[u8], wanted: u32) -> Result<Option<&[u8]>> {
126 let version = word(rdat, 0)?;
127 if version != VERSION {
128 return Err(error(
129 0,
130 format!("RDAT version {version:#x}: only {VERSION:#x} is read"),
131 ));
132 }
133 let count = word(rdat, 4)?;
134 for k in 0..count {
135 let at = usize::try_from(k)
136 .ok()
137 .and_then(|k| k.checked_mul(4))
138 .and_then(|k| k.checked_add(8))
139 .ok_or_else(|| error(32, "RDAT: part count"))?;
140 let offset = usize::try_from(word(rdat, at)?).map_err(|_| error(0, "RDAT: part offset"))?;
141 if word(rdat, offset)? != wanted {
142 continue;
143 }
144 let size = usize::try_from(word(rdat, offset.saturating_add(4))?)
145 .map_err(|_| error(0, "RDAT: part size"))?;
146 let start = offset.saturating_add(8);
147 return rdat
148 .get(start..start.saturating_add(size))
149 .map(Some)
150 .ok_or_else(|| error(offset.saturating_mul(8), "RDAT: a part past the end of the data"));
151 }
152 Ok(None)
153}
154
155struct Strings<'a>(&'a [u8]);
156impl Strings<'_> {
157 fn get(&self, offset: u32) -> Result<Option<String>> {
159 if offset == NULL {
160 return Ok(None);
161 }
162 let start = usize::try_from(offset).map_err(|_| error(0, "RDAT: string offset"))?;
163 let rest = self
164 .0
165 .get(start..)
166 .ok_or_else(|| error(0, format!("RDAT: string {offset} past the string buffer")))?;
167 let end = rest
168 .iter()
169 .position(|b| *b == 0)
170 .ok_or_else(|| error(0, "RDAT: a string without its end"))?;
171 let text = std::str::from_utf8(rest.get(..end).unwrap_or(&[]))
172 .map_err(|_| error(0, "RDAT: a string that is not UTF-8"))?;
173 Ok((!text.is_empty()).then(|| text.to_owned()))
174 }
175
176 fn required(&self, offset: u32, what: &str) -> Result<String> {
177 self.get(offset)?
178 .ok_or_else(|| error(0, format!("RDAT: {what} has no name")))
179 }
180}
181
182pub fn subobjects(rdat: &[u8]) -> Result<Vec<Subobject>> {
184 let Some(table) = part(rdat, SUBOBJECTS)? else {
185 return Ok(Vec::new());
186 };
187 let strings = Strings(part(rdat, STRINGS)?.unwrap_or(&[]));
188 let arrays = part(rdat, INDEX_ARRAYS)?.unwrap_or(&[]);
189 let raw = part(rdat, RAW_BYTES)?.unwrap_or(&[]);
190 let count = usize::try_from(word(table, 0)?).map_err(|_| error(0, "RDAT: subobject count"))?;
191 let stride = usize::try_from(word(table, 4)?).map_err(|_| error(0, "RDAT: subobject stride"))?;
192 if stride < 8 {
193 return Err(error(0, format!("RDAT: subobject records of {stride} bytes")));
194 }
195 let mut out = Vec::new();
196 for k in 0..count {
197 let at = k
198 .checked_mul(stride)
199 .and_then(|o| o.checked_add(8))
200 .ok_or_else(|| error(0, "RDAT: subobject table size"))?;
201 let record = table
202 .get(at..at.saturating_add(stride))
203 .ok_or_else(|| error(0, "RDAT: the subobject table is shorter than its count says"))?;
204 let field = |n: usize| word(record, n.saturating_mul(4));
205 let kind = field(0)?;
206 let name = strings.required(field(1)?, "a subobject")?;
207 out.push(match kind {
208 0 => Subobject::StateObjectConfig {
209 name,
210 flags: field(2)?,
211 },
212 1 | 2 => {
213 let offset =
214 usize::try_from(field(2)?).map_err(|_| error(0, "RDAT: root signature offset"))?;
215 let size = usize::try_from(field(3)?).map_err(|_| error(0, "RDAT: root signature size"))?;
216 let signature = raw
217 .get(offset..offset.saturating_add(size))
218 .ok_or_else(|| error(0, format!("RDAT: root signature {name} past the raw bytes")))?
219 .to_vec();
220 if kind == 1 {
221 Subobject::GlobalRootSignature { name, signature }
222 } else {
223 Subobject::LocalRootSignature { name, signature }
224 }
225 }
226 8 => {
227 let subobject = strings.required(field(2)?, "an association's subobject")?;
228 let first = usize::try_from(field(3)?).map_err(|_| error(0, "RDAT: exports index"))?;
230 let n = usize::try_from(word(arrays, first.saturating_mul(4))?)
231 .map_err(|_| error(0, "RDAT: exports count"))?;
232 let mut exports = Vec::new();
233 for i in 0..n {
234 let at = first.saturating_add(1).saturating_add(i).saturating_mul(4);
235 exports.push(strings.required(word(arrays, at)?, "an export")?);
236 }
237 Subobject::ExportsAssociation {
238 name,
239 subobject,
240 exports,
241 }
242 }
243 9 => Subobject::ShaderConfig {
244 name,
245 max_payload_bytes: field(2)?,
246 max_attribute_bytes: field(3)?,
247 },
248 10 => Subobject::PipelineConfig {
249 name,
250 max_recursion_depth: field(2)?,
251 },
252 11 => Subobject::HitGroup {
253 name,
254 kind: field(2)?,
255 any_hit: strings.get(field(3)?)?,
256 closest_hit: strings.get(field(4)?)?,
257 intersection: strings.get(field(5)?)?,
258 },
259 12 => Subobject::PipelineConfig1 {
260 name,
261 max_recursion_depth: field(2)?,
262 flags: field(3)?,
263 },
264 3..=7 => return Err(error(0, format!("RDAT: subobject kind {kind} is reserved"))),
265 other => return Err(error(0, format!("RDAT: subobject kind {other}"))),
266 });
267 }
268 Ok(out)
269}
270
271pub fn export_name(function: &str) -> String {
273 let undecorated = function.strip_prefix('\u{1}').unwrap_or(function);
274 match undecorated.strip_prefix('?') {
275 Some(rest) => rest.split("@@").next().unwrap_or(rest).to_owned(),
276 None => undecorated.to_owned(),
277 }
278}