1use std::fmt;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum RegisterSet {
5 Bool,
6 Int4,
7 Float4,
8 Sampler,
9 Unknown(u16),
10}
11
12impl RegisterSet {
13 pub fn from_u16(v: u16) -> Self {
14 match v {
15 0 => Self::Bool,
16 1 => Self::Int4,
17 2 => Self::Float4,
18 3 => Self::Sampler,
19 x => Self::Unknown(x),
20 }
21 }
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum TypeClass {
26 Scalar,
27 Vector,
28 MatrixRows,
29 MatrixColumns,
30 Object,
31 Struct,
32 Unknown(u16),
33}
34
35impl TypeClass {
36 pub fn from_u16(v: u16) -> Self {
37 match v {
38 0 => Self::Scalar,
39 1 => Self::Vector,
40 2 => Self::MatrixRows,
41 3 => Self::MatrixColumns,
42 4 => Self::Object,
43 5 => Self::Struct,
44 x => Self::Unknown(x),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum ValueType {
51 Void,
52 Bool,
53 Int,
54 Float,
55 String,
56 Texture,
57 Texture1D,
58 Texture2D,
59 Texture3D,
60 TextureCube,
61 Sampler,
62 Sampler1D,
63 Sampler2D,
64 Sampler3D,
65 SamplerCube,
66 PixelShader,
67 VertexShader,
68 PixelFragment,
69 VertexFragment,
70 Unsupported,
71 Unknown(u16),
72}
73
74impl ValueType {
75 pub fn from_u16(v: u16) -> Self {
76 match v {
77 0 => Self::Void,
78 1 => Self::Bool,
79 2 => Self::Int,
80 3 => Self::Float,
81 4 => Self::String,
82 5 => Self::Texture,
83 6 => Self::Texture1D,
84 7 => Self::Texture2D,
85 8 => Self::Texture3D,
86 9 => Self::TextureCube,
87 10 => Self::Sampler,
88 11 => Self::Sampler1D,
89 12 => Self::Sampler2D,
90 13 => Self::Sampler3D,
91 14 => Self::SamplerCube,
92 15 => Self::PixelShader,
93 16 => Self::VertexShader,
94 17 => Self::PixelFragment,
95 18 => Self::VertexFragment,
96 19 => Self::Unsupported,
97 x => Self::Unknown(x),
98 }
99 }
100}
101
102#[derive(Debug, Clone)]
103pub struct StructMemberInfo {
104 pub name: String,
105 pub type_info: TypeInfo,
106}
107
108#[derive(Debug, Clone)]
109pub struct TypeInfo {
110 pub class: TypeClass,
111 pub value_type: ValueType,
112 pub rows: u16,
113 pub columns: u16,
114 pub elements: u16,
115 pub struct_members: u16,
116 pub struct_member_info_offset: u32,
117 pub members: Vec<StructMemberInfo>,
118}
119
120impl TypeInfo {
121 pub fn hlsl_type_name(&self) -> String {
122 if self.class == TypeClass::Struct {
123 return "struct".to_string();
124 }
125 match self.value_type {
126 ValueType::Sampler | ValueType::Sampler2D => "sampler2D".to_string(),
127 ValueType::Sampler1D => "sampler1D".to_string(),
128 ValueType::Sampler3D => "sampler3D".to_string(),
129 ValueType::SamplerCube => "samplerCUBE".to_string(),
130 ValueType::Texture | ValueType::Texture2D => "texture".to_string(),
131 ValueType::Texture1D => "texture1D".to_string(),
132 ValueType::Texture3D => "texture3D".to_string(),
133 ValueType::TextureCube => "textureCUBE".to_string(),
134 ValueType::Bool => vector_or_matrix_type("bool", self.rows, self.columns, self.class),
135 ValueType::Int => vector_or_matrix_type("int", self.rows, self.columns, self.class),
136 ValueType::Float => vector_or_matrix_type("float", self.rows, self.columns, self.class),
137 _ => "float4".to_string(),
138 }
139 }
140}
141
142#[derive(Debug, Clone)]
143pub struct ConstantInfo {
144 pub name: String,
145 pub register_set: RegisterSet,
146 pub register_index: u16,
147 pub register_count: u16,
148 pub type_info: Option<TypeInfo>,
149}
150
151impl ConstantInfo {
152 pub fn register_name(&self) -> String {
153 match self.register_set {
154 RegisterSet::Bool => format!("b{}", self.register_index),
155 RegisterSet::Int4 => format!("i{}", self.register_index),
156 RegisterSet::Float4 => format!("c{}", self.register_index),
157 RegisterSet::Sampler => format!("s{}", self.register_index),
158 RegisterSet::Unknown(_) => format!("u{}", self.register_index),
159 }
160 }
161
162 pub fn hlsl_decl_type(&self) -> String {
163 let Some(t) = &self.type_info else {
164 return match self.register_set {
165 RegisterSet::Sampler => "sampler2D".to_string(),
166 RegisterSet::Bool => "bool".to_string(),
167 RegisterSet::Int4 => "int4".to_string(),
168 RegisterSet::Float4 => "float4".to_string(),
169 RegisterSet::Unknown(_) => "float4".to_string(),
170 };
171 };
172
173 if t.class == TypeClass::Struct {
174 return format!("{}_type", sanitize_type_name(&self.name));
175 }
176
177 match t.value_type {
178 ValueType::Sampler | ValueType::Sampler2D => "sampler2D".to_string(),
179 ValueType::Sampler1D => "sampler1D".to_string(),
180 ValueType::Sampler3D => "sampler3D".to_string(),
181 ValueType::SamplerCube => "samplerCUBE".to_string(),
182 ValueType::Texture | ValueType::Texture2D => "texture".to_string(),
183 ValueType::Texture1D => "texture1D".to_string(),
184 ValueType::Texture3D => "texture3D".to_string(),
185 ValueType::TextureCube => "textureCUBE".to_string(),
186 ValueType::Bool => vector_or_matrix_type("bool", t.rows, t.columns, t.class),
187 ValueType::Int => vector_or_matrix_type("int", t.rows, t.columns, t.class),
188 ValueType::Float => vector_or_matrix_type("float", t.rows, t.columns, t.class),
189 _ => match self.register_set {
190 RegisterSet::Sampler => "sampler2D".to_string(),
191 RegisterSet::Bool => "bool".to_string(),
192 RegisterSet::Int4 => "int4".to_string(),
193 RegisterSet::Float4 => "float4".to_string(),
194 RegisterSet::Unknown(_) => "float4".to_string(),
195 },
196 }
197 }
198
199 pub fn struct_type_name(&self) -> Option<String> {
200 let t = self.type_info.as_ref()?;
201 if t.class == TypeClass::Struct {
202 Some(format!("{}_type", sanitize_type_name(&self.name)))
203 } else {
204 None
205 }
206 }
207}
208
209fn sanitize_type_name(name: &str) -> String {
210 let mut out = String::new();
211 for (i, ch) in name.chars().enumerate() {
212 let ok = if i == 0 {
213 ch == '_' || ch.is_ascii_alphabetic()
214 } else {
215 ch == '_' || ch.is_ascii_alphanumeric()
216 };
217 out.push(if ok { ch } else { '_' });
218 }
219 if out.is_empty() || out.chars().next().is_some_and(|c| c.is_ascii_digit()) {
220 format!("_{}", out)
221 } else {
222 out
223 }
224}
225
226fn vector_or_matrix_type(base: &str, rows: u16, columns: u16, class: TypeClass) -> String {
227 match class {
228 TypeClass::Scalar => base.to_string(),
229 TypeClass::Vector => format!("{}{}", base, columns.max(rows).max(1)),
230 TypeClass::MatrixRows | TypeClass::MatrixColumns => {
231 if rows > 1 && columns > 1 {
232 format!("{}{}x{}", base, rows, columns)
233 } else {
234 format!("{}4", base)
235 }
236 }
237 _ => {
238 if rows > 1 && columns > 1 {
239 format!("{}{}x{}", base, rows, columns)
240 } else if columns > 1 {
241 format!("{}{}", base, columns)
242 } else {
243 base.to_string()
244 }
245 }
246 }
247}
248
249#[derive(Debug, Clone)]
250pub struct ConstantTable {
251 pub creator: Option<String>,
252 pub version: u32,
253 pub flags: u32,
254 pub target: Option<String>,
255 pub constants: Vec<ConstantInfo>,
256}
257
258impl ConstantTable {
259 pub fn find_register_name(&self, register_set: RegisterSet, index: u16) -> Option<&str> {
260 self.constants
261 .iter()
262 .find(|c| c.register_set == register_set && c.register_index == index)
263 .map(|c| c.name.as_str())
264 }
265
266 pub fn hlsl_uniforms(&self) -> String {
267 let mut out = String::new();
268
269 for c in &self.constants {
270 if let (Some(type_name), Some(type_info)) = (c.struct_type_name(), c.type_info.as_ref()) {
271 out.push_str(&format!("struct {} {{\n", type_name));
272 for m in &type_info.members {
273 out.push_str(&format!(" {} {};\n", m.type_info.hlsl_type_name(), m.name));
274 }
275 out.push_str("};\n");
276 }
277 }
278
279 for c in &self.constants {
280 let ty = c.hlsl_decl_type();
281 let reg = c.register_name();
282 out.push_str(&format!("uniform {} {};\n", ty, c.name));
283 }
284 out
285 }
286}
287
288#[derive(Debug, Clone)]
289pub enum CtabError {
290 TooSmall,
291 BadMagic,
292 OutOfBounds(&'static str, usize),
293 InvalidStringOffset(u32),
294}
295
296impl fmt::Display for CtabError {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 match self {
299 Self::TooSmall => write!(f, "CTAB payload is too small"),
300 Self::BadMagic => write!(f, "CTAB magic is missing"),
301 Self::OutOfBounds(what, off) => write!(f, "{} is out of bounds at offset {}", what, off),
302 Self::InvalidStringOffset(off) => write!(f, "invalid CTAB string offset {}", off),
303 }
304 }
305}
306
307impl std::error::Error for CtabError {}
308
309fn read_u16(data: &[u8], off: usize) -> Result<u16, CtabError> {
310 let b = data.get(off..off + 2).ok_or(CtabError::OutOfBounds("u16", off))?;
311 Ok(u16::from_le_bytes([b[0], b[1]]))
312}
313
314fn read_u32(data: &[u8], off: usize) -> Result<u32, CtabError> {
315 let b = data.get(off..off + 4).ok_or(CtabError::OutOfBounds("u32", off))?;
316 Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
317}
318
319fn read_c_string(data: &[u8], off: u32) -> Result<String, CtabError> {
320 let off = off as usize;
321 if off >= data.len() {
322 return Err(CtabError::InvalidStringOffset(off as u32));
323 }
324 let mut end = off;
325 while end < data.len() && data[end] != 0 {
326 end += 1;
327 }
328 Ok(String::from_utf8_lossy(&data[off..end]).to_string())
329}
330
331fn parse_type_info(data: &[u8], off: usize, depth: usize) -> Result<TypeInfo, CtabError> {
332 if depth > 16 {
333 return Err(CtabError::OutOfBounds("recursive type info", off));
334 }
335 if off + 16 > data.len() {
336 return Err(CtabError::OutOfBounds("type info", off));
337 }
338
339 let class = TypeClass::from_u16(read_u16(data, off)?);
340 let value_type = ValueType::from_u16(read_u16(data, off + 2)?);
341 let rows = read_u16(data, off + 4)?;
342 let columns = read_u16(data, off + 6)?;
343 let elements = read_u16(data, off + 8)?;
344 let struct_members = read_u16(data, off + 10)?;
345 let struct_member_info_offset = read_u32(data, off + 12)?;
346
347 let mut members = Vec::new();
348 if class == TypeClass::Struct && struct_member_info_offset != 0 {
349 let member_base = struct_member_info_offset as usize;
350 for idx in 0..struct_members as usize {
351 let m_off = member_base + idx * 8;
352 if m_off + 8 > data.len() {
353 break;
354 }
355 let name_off = read_u32(data, m_off)?;
356 let type_off = read_u32(data, m_off + 4)? as usize;
357 let name = read_c_string(data, name_off).unwrap_or_else(|_| format!("member_{}", idx));
358 if type_off != 0 && type_off + 16 <= data.len() {
359 let type_info = parse_type_info(data, type_off, depth + 1)?;
360 members.push(StructMemberInfo { name, type_info });
361 }
362 }
363 }
364
365 Ok(TypeInfo {
366 class,
367 value_type,
368 rows,
369 columns,
370 elements,
371 struct_members,
372 struct_member_info_offset,
373 members,
374 })
375}
376
377pub fn parse_ctab(payload: &[u8]) -> Result<ConstantTable, CtabError> {
378 if payload.len() < 28 {
379 return Err(CtabError::TooSmall);
380 }
381 if payload.get(0..4) != Some(b"CTAB") {
382 return Err(CtabError::BadMagic);
383 }
384
385 let size = read_u32(payload, 4)? as usize;
386 let table_len = if size >= 28 && size <= payload.len() { size } else { payload.len() };
387 let data = &payload[..table_len];
388
389 let creator_off = read_u32(data, 8)?;
390 let version = read_u32(data, 12)?;
391 let constants_count = read_u32(data, 16)? as usize;
392 let constant_info_off = read_u32(data, 20)? as usize;
393 let flags = read_u32(data, 24)?;
394 let target_off = if data.len() >= 32 { read_u32(data, 28).unwrap_or(0) } else { 0 };
395
396 let creator = if creator_off != 0 { read_c_string(data, creator_off).ok() } else { None };
397 let target = if target_off != 0 { read_c_string(data, target_off).ok() } else { None };
398
399 let mut constants = Vec::with_capacity(constants_count);
400 for idx in 0..constants_count {
401 let off = constant_info_off + idx * 20;
402 if off + 20 > data.len() {
403 break;
404 }
405
406 let name_off = read_u32(data, off)?;
407 let register_set = RegisterSet::from_u16(read_u16(data, off + 4)?);
408 let register_index = read_u16(data, off + 6)?;
409 let register_count = read_u16(data, off + 8)?;
410 let type_info_off = read_u32(data, off + 12)? as usize;
411
412 let name = read_c_string(data, name_off).unwrap_or_else(|_| format!("unnamed_{}", idx));
413 let type_info = if type_info_off != 0 && type_info_off + 16 <= data.len() {
414 parse_type_info(data, type_info_off, 0).ok()
415 } else {
416 None
417 };
418
419 constants.push(ConstantInfo {
420 name,
421 register_set,
422 register_index,
423 register_count,
424 type_info,
425 });
426 }
427
428 Ok(ConstantTable {
429 creator,
430 version,
431 flags,
432 target,
433 constants,
434 })
435}