1use primase_foundations::compress::{Decompressor, Lz4};
19
20use crate::player::{ObjRef, Player};
21use crate::value::{Array, Value};
22use crate::{Result, invalid};
23
24#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
28pub struct Field {
29 pub name: String,
31 pub offset: u32,
33 pub kind: i64,
35 pub shape: Shape,
37 pub array_size: u32,
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize)]
43pub enum Shape {
44 Vector {
46 columns: u32,
48 },
49 Matrix {
51 rows: u32,
53 },
54}
55
56#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
59pub struct StructField {
60 pub name: String,
62 pub offset: u32,
64 pub array_size: u32,
66 pub size: u32,
68 pub members: Vec<Field>,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
75pub struct ConstantBuffer {
76 pub name: String,
78 pub slot: Option<u32>,
80 pub size: u32,
82 pub fields: Vec<Field>,
84 #[serde(skip_serializing_if = "Vec::is_empty")]
86 pub structs: Vec<StructField>,
87}
88
89#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
92pub struct TextureParam {
93 pub name: String,
95 pub slot: u32,
97 pub sampler: Option<u32>,
99 pub dim: i64,
101}
102
103#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize)]
105pub struct Parameters {
106 pub constant_buffers: Vec<ConstantBuffer>,
108 pub textures: Vec<TextureParam>,
110 pub samplers: Vec<(u32, u32)>,
112}
113
114#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
119pub struct Variant {
120 pub group: usize,
122 pub blob_index: u32,
124 pub keywords: Vec<String>,
126 pub gpu_program_type: i64,
128 pub requirements: i64,
130 #[serde(skip_serializing_if = "Option::is_none")]
133 pub parameter_blob: Option<u32>,
134 #[serde(skip_serializing_if = "Option::is_none")]
137 pub parameters: Option<Parameters>,
138}
139
140#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize)]
143pub struct PassProgram {
144 pub variants: Vec<Variant>,
146 pub parameters: Parameters,
148}
149
150impl PassProgram {
151 pub fn parameters_of<'a>(&'a self, v: &'a Variant) -> &'a Parameters {
153 v.parameters.as_ref().unwrap_or(&self.parameters)
154 }
155}
156
157#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
160pub enum Stage {
161 Vertex,
163 Fragment,
165 Geometry,
167 Hull,
169 Domain,
171}
172
173impl Stage {
174 pub const ALL: [Stage; 5] = [
176 Stage::Vertex,
177 Stage::Fragment,
178 Stage::Geometry,
179 Stage::Hull,
180 Stage::Domain,
181 ];
182
183 fn field(self) -> &'static str {
185 match self {
186 Stage::Vertex => "progVertex",
187 Stage::Fragment => "progFragment",
188 Stage::Geometry => "progGeometry",
189 Stage::Hull => "progHull",
190 Stage::Domain => "progDomain",
191 }
192 }
193}
194
195#[derive(Clone, Debug, Default, PartialEq, Eq)]
197pub struct PassStages {
198 pub vertex: PassProgram,
200 pub fragment: PassProgram,
202 pub geometry: PassProgram,
204 pub hull: PassProgram,
206 pub domain: PassProgram,
208}
209
210impl PassStages {
211 pub fn stage(&self, stage: Stage) -> &PassProgram {
213 match stage {
214 Stage::Vertex => &self.vertex,
215 Stage::Fragment => &self.fragment,
216 Stage::Geometry => &self.geometry,
217 Stage::Hull => &self.hull,
218 Stage::Domain => &self.domain,
219 }
220 }
221}
222
223#[derive(Clone, Debug, PartialEq)]
225pub struct ShaderPrograms {
226 pub name: String,
228 pub passes: Vec<Vec<PassStages>>,
230 pub platforms: Vec<i64>,
232 segments: Vec<Vec<(usize, usize, usize)>>,
234 blob: Vec<u8>,
235}
236
237fn items<'a>(v: &'a Value, k: &str) -> Result<&'a [Value]> {
238 match v.get(k) {
239 Some(Value::Array(Array::Values(items))) => Ok(items),
240 Some(Value::Array(a)) if a.is_empty() => Ok(&[]),
241 None => Ok(&[]),
242 _ => Err(invalid(format!("shader program: {k} is not a list"))),
243 }
244}
245
246fn ints(v: &Value) -> Result<Vec<i64>> {
248 let bad = || invalid("shader program: an integer array of another type");
249 Ok(match v {
250 Value::Array(a) => match a {
251 Array::I8(x) => x.iter().map(|&n| i64::from(n)).collect(),
252 Array::U8(x) => x.iter().map(|&n| i64::from(n)).collect(),
253 Array::I16(x) => x.iter().map(|&n| i64::from(n)).collect(),
254 Array::U16(x) => x.iter().map(|&n| i64::from(n)).collect(),
255 Array::I32(x) => x.iter().map(|&n| i64::from(n)).collect(),
256 Array::U32(x) => x.iter().map(|&n| i64::from(n)).collect(),
257 Array::I64(x) => x.clone(),
258 Array::U64(x) => x
259 .iter()
260 .map(|&n| i64::try_from(n).map_err(|_| bad()))
261 .collect::<Result<_>>()?,
262 Array::Values(x) => x
263 .iter()
264 .map(|n| n.as_i64().ok_or_else(bad))
265 .collect::<Result<_>>()?,
266 _ => return Err(bad()),
267 },
268 _ => return Err(bad()),
269 })
270}
271
272fn int(v: &Value, k: &str) -> Result<i64> {
273 v.get(k)
274 .and_then(Value::as_i64)
275 .ok_or_else(|| invalid(format!("shader program: no integer {k}")))
276}
277
278fn u32_of(n: i64, what: &str) -> Result<u32> {
279 u32::try_from(n).map_err(|_| invalid(format!("shader program: {what} {n} out of range")))
280}
281
282fn name_of(e: &Value, names: &std::collections::BTreeMap<i64, String>) -> Result<String> {
284 let i = int(e, "m_NameIndex")?;
285 names
286 .get(&i)
287 .cloned()
288 .ok_or_else(|| invalid(format!("shader program: name index {i} past the pass's names")))
289}
290
291fn fields(
293 v: &Value,
294 vectors: &str,
295 matrices: &str,
296 names: &std::collections::BTreeMap<i64, String>,
297) -> Result<Vec<Field>> {
298 let mut fields = Vec::new();
299 for (key, matrix) in [(vectors, false), (matrices, true)] {
300 for f in items(v, key)? {
301 fields.push(Field {
302 name: name_of(f, names)?,
303 offset: u32_of(int(f, "m_Index")?, "field offset")?,
304 kind: int(f, "m_Type")?,
305 shape: if matrix {
306 Shape::Matrix {
307 rows: u32_of(int(f, "m_RowCount")?, "matrix rows")?,
308 }
309 } else {
310 Shape::Vector {
311 columns: u32_of(int(f, "m_Dim")?, "vector size")?,
312 }
313 },
314 array_size: u32_of(int(f, "m_ArraySize")?, "array size")?,
315 });
316 }
317 }
318 fields.sort_by_key(|f| f.offset);
319 Ok(fields)
320}
321
322fn parameters(v: &Value, names: &std::collections::BTreeMap<i64, String>) -> Result<Parameters> {
324 let name = |e: &Value| name_of(e, names);
325 let bindings = items(v, "m_ConstantBufferBindings")?
326 .iter()
327 .map(|b| {
328 Ok((
329 int(b, "m_NameIndex")?,
330 u32_of(int(b, "m_Index")?, "binding slot")?,
331 ))
332 })
333 .collect::<Result<std::collections::BTreeMap<i64, u32>>>()?;
334 let mut constant_buffers = Vec::new();
335 for cb in items(v, "m_ConstantBuffers")? {
336 let mut structs = items(cb, "m_StructParams")?
337 .iter()
338 .map(|s| {
339 Ok(StructField {
340 name: name(s)?,
341 offset: u32_of(int(s, "m_Index")?, "struct offset")?,
342 array_size: u32_of(int(s, "m_ArraySize")?, "array size")?,
343 size: u32_of(int(s, "m_StructSize")?, "struct size")?,
344 members: fields(s, "m_VectorMembers", "m_MatrixMembers", names)?,
345 })
346 })
347 .collect::<Result<Vec<_>>>()?;
348 structs.sort_by_key(|s| s.offset);
349 constant_buffers.push(ConstantBuffer {
350 name: name(cb)?,
351 slot: bindings.get(&int(cb, "m_NameIndex")?).copied(),
352 size: u32_of(int(cb, "m_Size")?, "buffer size")?,
353 fields: fields(cb, "m_VectorParams", "m_MatrixParams", names)?,
354 structs,
355 });
356 }
357 let textures = items(v, "m_TextureParams")?
358 .iter()
359 .map(|t| {
360 let sampler = int(t, "m_SamplerIndex")?;
361 Ok(TextureParam {
362 name: name(t)?,
363 slot: u32_of(int(t, "m_Index")?, "texture slot")?,
364 sampler: (sampler >= 0)
365 .then(|| u32_of(sampler, "sampler slot"))
366 .transpose()?,
367 dim: int(t, "m_Dim")?,
368 })
369 })
370 .collect::<Result<_>>()?;
371 let samplers = items(v, "m_Samplers")?
372 .iter()
373 .map(|s| {
374 Ok((
375 u32_of(int(s, "sampler")?, "sampler state")?,
376 u32_of(int(s, "bindPoint")?, "sampler slot")?,
377 ))
378 })
379 .collect::<Result<_>>()?;
380 Ok(Parameters {
381 constant_buffers,
382 textures,
383 samplers,
384 })
385}
386
387const PARAMETER_BLOB_MAGIC: u32 = 0x0C0A_75BA;
389
390fn blob_string(r: &mut crate::reader::Reader<'_>) -> Result<String> {
392 let n = usize::try_from(r.u32()?).map_err(|_| invalid("parameter blob: a string length"))?;
393 let bytes = r.take(n)?;
394 r.align(4)?;
395 String::from_utf8(bytes.to_vec()).map_err(|_| invalid("parameter blob: a name that is not UTF-8"))
396}
397
398fn blob_field(r: &mut crate::reader::Reader<'_>) -> Result<Field> {
401 let name = blob_string(r)?;
402 let kind = i64::from(r.u32()?);
403 let (rows, columns, matrix) = (r.u32()?, r.u32()?, r.u32()?);
404 let array_size = r.u32()?;
405 let offset = r.u32()?;
406 let shape = match matrix {
407 0 => Shape::Vector { columns },
408 1 => Shape::Matrix { rows },
409 n => {
410 return Err(invalid(format!(
411 "parameter blob: field {name} has matrix flag {n}"
412 )));
413 }
414 };
415 Ok(Field {
416 name,
417 offset,
418 kind,
419 shape,
420 array_size,
421 })
422}
423
424pub fn parameter_blob(entry: &[u8]) -> Result<Parameters> {
432 let mut r = crate::reader::Reader::new(entry, false);
433 if r.u32()? != PARAMETER_BLOB_MAGIC {
434 return Err(invalid("parameter blob: not the 2021.2 format"));
435 }
436 let buffers = r.u32()?;
437 if (0..4).any(|_| r.u32().map_or(true, |w| w != 0)) {
438 return Err(invalid(
439 "parameter blob: words before the buffers that are not zero",
440 ));
441 }
442 let mut constant_buffers = Vec::new();
443 for _ in 1..buffers {
445 let name = blob_string(&mut r)?;
446 let size = r.u32()?;
447 let fields = (0..r.u32()?)
448 .map(|_| blob_field(&mut r))
449 .collect::<Result<Vec<_>>>()?;
450 let mut structs = Vec::new();
451 for _ in 0..r.u32()? {
452 structs.push(StructField {
453 name: blob_string(&mut r)?,
454 offset: r.u32()?,
455 array_size: r.u32()?,
456 size: r.u32()?,
457 members: (0..r.u32()?)
458 .map(|_| blob_field(&mut r))
459 .collect::<Result<Vec<_>>>()?,
460 });
461 }
462 constant_buffers.push(ConstantBuffer {
463 name,
464 slot: None,
465 size,
466 fields,
467 structs,
468 });
469 }
470 let (mut textures, mut samplers) = (Vec::new(), Vec::new());
471 for _ in 0..r.u32()? {
472 let name = blob_string(&mut r)?;
473 let (kind, slot) = (r.u32()?, r.u32()?);
474 match kind {
475 0 => {
476 let sampler = r.u32()?;
477 textures.push(TextureParam {
478 name,
479 slot,
480 sampler: (sampler != u32::MAX).then_some(sampler),
481 dim: i64::from(r.u32()?),
482 });
483 }
484 1 => {
485 r.u32()?;
486 match constant_buffers.iter_mut().find(|cb| cb.name == name) {
488 Some(cb) => cb.slot = Some(slot),
489 None => constant_buffers.push(ConstantBuffer {
490 name,
491 slot: Some(slot),
492 size: 0,
493 fields: Vec::new(),
494 structs: Vec::new(),
495 }),
496 }
497 }
498 4 => samplers.push((r.u32()?, slot)),
499 n => return Err(invalid(format!("parameter blob: an entry of kind {n}"))),
500 }
501 }
502 if r.remaining() != 0 {
503 return Err(invalid("parameter blob: bytes after the entries"));
504 }
505 Ok(Parameters {
506 constant_buffers,
507 textures,
508 samplers,
509 })
510}
511
512pub fn merge_parameters(common: &Parameters, own: &Parameters) -> Parameters {
516 let mut buffers = own.constant_buffers.clone();
517 for c in &common.constant_buffers {
518 match buffers.iter_mut().find(|b| b.name == c.name) {
519 Some(b) => {
520 for f in &c.fields {
521 if !b.fields.iter().any(|g| g.name == f.name && g.offset == f.offset) {
522 b.fields.push(f.clone());
523 }
524 }
525 b.fields.sort_by_key(|f| f.offset);
526 for s in &c.structs {
527 if !b.structs.iter().any(|g| g.name == s.name) {
528 b.structs.push(s.clone());
529 }
530 }
531 b.structs.sort_by_key(|s| s.offset);
532 b.size = b.size.max(c.size);
533 b.slot = b.slot.or(c.slot);
534 }
535 None => buffers.push(c.clone()),
536 }
537 }
538 let mut textures = own.textures.clone();
539 textures.extend(
540 common
541 .textures
542 .iter()
543 .filter(|t| !own.textures.iter().any(|o| o.slot == t.slot))
544 .cloned(),
545 );
546 let mut samplers = own.samplers.clone();
547 samplers.extend(
548 common
549 .samplers
550 .iter()
551 .filter(|s| !own.samplers.contains(s))
552 .copied(),
553 );
554 Parameters {
555 constant_buffers: buffers,
556 textures,
557 samplers,
558 }
559}
560
561fn stage_program(
564 program: &Value,
565 names: &std::collections::BTreeMap<i64, String>,
566 keyword_names: &[String],
567 name: &str,
568) -> Result<PassProgram> {
569 let mut variants = Vec::new();
570 for v in items(program, "m_SubPrograms")? {
572 let mut keywords = Vec::new();
573 for k in ["m_GlobalKeywordIndices", "m_LocalKeywordIndices"] {
574 if let Some(k) = v.get(k) {
575 for i in ints(k)? {
576 keywords.push(names.get(&i).cloned().ok_or_else(|| {
577 invalid(format!("shader {name}: keyword index {i} past the pass's names"))
578 })?);
579 }
580 }
581 }
582 variants.push(Variant {
583 group: usize::try_from(int(v, "m_ShaderHardwareTier")?)
584 .map_err(|_| invalid("shader program: a negative hardware tier"))?,
585 blob_index: u32_of(int(v, "m_BlobIndex")?, "blob index")?,
586 keywords,
587 gpu_program_type: int(v, "m_GpuProgramType")?,
588 requirements: v.get("m_ShaderRequirements").and_then(Value::as_i64).unwrap_or(0),
589 parameter_blob: None,
590 parameters: Some(parameters(v, names)?),
591 });
592 }
593 let parameter_blobs: Vec<Vec<i64>> = match program.get("m_ParameterBlobIndices") {
595 Some(Value::Array(Array::Values(groups))) => groups.iter().map(ints).collect::<Result<_>>()?,
596 _ => Vec::new(),
597 };
598 for (group, list) in items(program, "m_PlayerSubPrograms")?.iter().enumerate() {
599 let list: &[Value] = match list {
600 Value::Array(Array::Values(x)) => x,
601 Value::Array(a) if a.is_empty() => &[],
602 _ => return Err(invalid("shader pass: a player sub-program list")),
603 };
604 for (place, v) in list.iter().enumerate() {
605 let parameter_blob = parameter_blobs
606 .get(group)
607 .and_then(|g| g.get(place))
608 .map(|i| u32_of(*i, "parameter blob index"))
609 .transpose()?;
610 let keywords = match v.get("m_KeywordIndices") {
611 Some(k) => ints(k)?
612 .into_iter()
613 .map(|i| {
614 usize::try_from(i)
615 .ok()
616 .and_then(|i| keyword_names.get(i))
617 .cloned()
618 .ok_or_else(|| invalid(format!("shader {name}: keyword index {i}")))
619 })
620 .collect::<Result<_>>()?,
621 None => Vec::new(),
622 };
623 variants.push(Variant {
624 group,
625 blob_index: u32_of(int(v, "m_BlobIndex")?, "blob index")?,
626 keywords,
627 gpu_program_type: int(v, "m_GpuProgramType")?,
628 requirements: v.get("m_ShaderRequirements").and_then(Value::as_i64).unwrap_or(0),
629 parameter_blob,
630 parameters: None,
631 });
632 }
633 }
634 let parameters = match program.get("m_CommonParameters") {
635 Some(c) => parameters(c, names)?,
636 None => Parameters::default(),
637 };
638 Ok(PassProgram { variants, parameters })
639}
640
641#[derive(Debug, Default)]
643pub struct Segments(std::collections::BTreeMap<(usize, usize), std::sync::Arc<Vec<u8>>>);
644
645impl Segments {
646 fn get(
647 &mut self,
648 programs: &ShaderPrograms,
649 platform: usize,
650 k: usize,
651 ) -> Result<std::sync::Arc<Vec<u8>>> {
652 if let Some(s) = self.0.get(&(platform, k)) {
653 return Ok(s.clone());
654 }
655 let s = std::sync::Arc::new(programs.segment(platform, k)?);
656 self.0.insert((platform, k), s.clone());
657 Ok(s)
658 }
659}
660
661impl ShaderPrograms {
662 pub fn read(player: &Player, r: &ObjRef) -> Result<Self> {
664 let s = player.decode(r)?;
665 let form = s
666 .get("m_ParsedForm")
667 .ok_or_else(|| invalid(format!("shader {}/{}: no m_ParsedForm", r.file, r.path_id)))?;
668 let name = form
669 .get("m_Name")
670 .and_then(Value::as_str)
671 .unwrap_or("")
672 .to_owned();
673 let keyword_names: Vec<String> = items(form, "m_KeywordNames")?
674 .iter()
675 .map(|k| {
676 k.as_str()
677 .map(str::to_owned)
678 .ok_or_else(|| invalid("shader: a keyword name"))
679 })
680 .collect::<Result<_>>()?;
681 let mut passes = Vec::new();
682 for sub in items(form, "m_SubShaders")? {
683 let mut out = Vec::new();
684 for p in items(sub, "m_Passes")? {
685 let mut names = std::collections::BTreeMap::new();
687 for pair in items(p, "m_NameIndices")? {
688 let Value::Pair(kv) = pair else {
689 return Err(invalid("shader pass: m_NameIndices entry is not a pair"));
690 };
691 let key = kv.0.as_str().ok_or_else(|| invalid("shader pass: a name"))?;
692 let index =
693 kv.1.as_i64()
694 .ok_or_else(|| invalid("shader pass: a name index"))?;
695 names.insert(index, key.to_owned());
696 }
697 let stage = |stage: Stage| match p.get(stage.field()) {
698 Some(program) => stage_program(program, &names, &keyword_names, &name),
699 None => Ok(PassProgram::default()),
700 };
701 out.push(PassStages {
702 vertex: stage(Stage::Vertex)?,
703 fragment: stage(Stage::Fragment)?,
704 geometry: stage(Stage::Geometry)?,
705 hull: stage(Stage::Hull)?,
706 domain: stage(Stage::Domain)?,
707 });
708 }
709 passes.push(out);
710 }
711 let platforms = s.get("platforms").map(ints).transpose()?.unwrap_or_default();
712 let per = |k: &str| -> Result<Vec<Vec<i64>>> {
713 items(&s, k)?
714 .iter()
715 .map(|v| match v {
716 Value::Array(_) => ints(v),
717 x => Ok(vec![
718 x.as_i64().ok_or_else(|| invalid(format!("shader: {k} entry")))?,
719 ]),
720 })
721 .collect()
722 };
723 let (offsets, packed, unpacked) = (
724 per("offsets")?,
725 per("compressedLengths")?,
726 per("decompressedLengths")?,
727 );
728 if offsets.len() != platforms.len()
729 || packed.len() != platforms.len()
730 || unpacked.len() != platforms.len()
731 {
732 return Err(invalid(format!(
733 "shader {name}: blob tables of another length than its platforms"
734 )));
735 }
736 let segments = offsets
737 .iter()
738 .zip(&packed)
739 .zip(&unpacked)
740 .map(|((o, p), u)| {
741 if o.len() != p.len() || o.len() != u.len() {
742 return Err(invalid(format!(
743 "shader {name}: segment tables of different lengths"
744 )));
745 }
746 let size = |n: i64| usize::try_from(n).map_err(|_| invalid("shader: a negative blob size"));
747 o.iter()
748 .zip(p)
749 .zip(u)
750 .map(|((&o, &p), &u)| Ok((size(o)?, size(p)?, size(u)?)))
751 .collect()
752 })
753 .collect::<Result<_>>()?;
754 let blob = s
755 .get("compressedBlob")
756 .and_then(Value::as_bytes)
757 .map(<[u8]>::to_vec)
758 .unwrap_or_default();
759 Ok(ShaderPrograms {
760 name,
761 passes,
762 platforms,
763 segments,
764 blob,
765 })
766 }
767
768 fn segment(&self, platform: usize, k: usize) -> Result<Vec<u8>> {
770 let &(offset, packed, unpacked) = self
771 .segments
772 .get(platform)
773 .and_then(|s| s.get(k))
774 .ok_or_else(|| invalid(format!("shader {}: no blob segment {k}", self.name)))?;
775 let bytes = offset
776 .checked_add(packed)
777 .and_then(|end| self.blob.get(offset..end))
778 .ok_or_else(|| invalid(format!("shader {}: blob segment {k} past the blob", self.name)))?;
779 Lz4.decompress(bytes, unpacked)
780 .map_err(|e| invalid(format!("shader {}: blob segment {k}: {e}", self.name)))
781 }
782
783 pub fn code(&self, platform: i64, index: u32) -> Result<Vec<u8>> {
786 self.code_with(&mut Segments::default(), platform, index)
787 }
788
789 pub fn code_with(&self, cache: &mut Segments, platform: i64, index: u32) -> Result<Vec<u8>> {
792 let entry = self.entry_with(cache, platform, index)?;
793 Self::code_of(&entry)
794 }
795
796 pub fn entry_with(&self, cache: &mut Segments, platform: i64, index: u32) -> Result<Vec<u8>> {
798 let p = self
799 .platforms
800 .iter()
801 .position(|&x| x == platform)
802 .ok_or_else(|| invalid(format!("shader {}: no blob for platform {platform}", self.name)))?;
803 let table = cache.get(self, p, 0)?;
804 let mut r = crate::reader::Reader::new(&table, false);
805 let count = usize::try_from(r.i32()?).map_err(|_| invalid("shader blob: a negative entry count"))?;
806 if index as usize >= count {
807 return Err(invalid(format!(
808 "shader {}: blob entry {index} of {count}",
809 self.name
810 )));
811 }
812 let at = usize::try_from(index)
814 .ok()
815 .and_then(|i| i.checked_mul(12))
816 .and_then(|o| o.checked_add(4))
817 .filter(|at| at.saturating_add(12) <= table.len())
818 .ok_or_else(|| {
819 invalid(format!(
820 "shader {}: blob entry {index} of {count} in a table of {} bytes",
821 self.name,
822 table.len()
823 ))
824 })?;
825 r.pos = at;
826 let (offset, length, segment) = (r.i32()?, r.i32()?, r.i32()?);
827 let span = |n: i32| usize::try_from(n).map_err(|_| invalid("shader blob: a negative entry span"));
828 let (offset, length, segment) = (span(offset)?, span(length)?, span(segment)?);
829 let data = if segment == 0 {
830 table
831 } else {
832 cache.get(self, p, segment)?
833 };
834 let entry = offset
835 .checked_add(length)
836 .and_then(|end| data.get(offset..end))
837 .ok_or_else(|| {
838 invalid(format!(
839 "shader {}: blob entry {index} past its segment",
840 self.name
841 ))
842 })?;
843 Ok(entry.to_vec())
844 }
845
846 fn code_of(entry: &[u8]) -> Result<Vec<u8>> {
849 let mut r = crate::reader::Reader::new(entry, false);
850 let version = r.i32()?;
851 let _program_type = r.i32()?;
852 r.take(12)?;
853 if version >= 201_608_170 {
854 r.take(4)?;
855 }
856 let strings = |r: &mut crate::reader::Reader<'_>| -> Result<()> {
857 let n =
858 usize::try_from(r.i32()?).map_err(|_| invalid("shader blob: a negative keyword count"))?;
859 for _ in 0..n {
860 let len = usize::try_from(r.i32()?)
861 .map_err(|_| invalid("shader blob: a negative string length"))?;
862 r.take(len)?;
863 r.align(4)?;
864 }
865 Ok(())
866 };
867 strings(&mut r)?;
868 if (201_806_140..202_012_090).contains(&version) {
869 strings(&mut r)?;
870 }
871 let len = usize::try_from(r.i32()?).map_err(|_| invalid("shader blob: a negative code length"))?;
872 Ok(r.take(len)?.to_vec())
873 }
874}
875
876pub const D3D11_PROGRAM_TYPES: std::ops::RangeInclusive<i64> = 15..=22;
879
880pub fn dxbc(code: &[u8]) -> Option<&[u8]> {
883 (0..code.len().min(64)).find_map(|at| {
884 let c = code.get(at..)?;
885 if c.get(..4)? != b"DXBC" {
886 return None;
887 }
888 let size = u32::from_le_bytes(c.get(24..28)?.try_into().ok()?) as usize;
889 c.get(..size)
890 })
891}
892
893fn properties(s: &Value) -> Result<&[Value]> {
895 Ok(s.get("m_ParsedForm")
896 .and_then(|f| f.get("m_PropInfo"))
897 .map(|p| items(p, "m_Props"))
898 .transpose()?
899 .unwrap_or(&[]))
900}
901
902pub fn texture_defaults(player: &Player, r: &ObjRef) -> Result<Vec<(String, String)>> {
905 let s = player.decode(r)?;
906 let props = properties(&s)?;
907 let mut out = Vec::new();
908 for p in props {
909 if p.get("m_Type").and_then(Value::as_i64) != Some(4) {
910 continue;
911 }
912 let name = p.get("m_Name").and_then(Value::as_str);
913 let default = p
914 .get("m_DefTexture")
915 .and_then(|d| d.get("m_DefaultName"))
916 .and_then(Value::as_str);
917 if let (Some(name), Some(default)) = (name, default) {
918 out.push((name.to_owned(), default.to_owned()));
919 }
920 }
921 Ok(out)
922}
923
924pub const PROPERTY_HDR: u32 = 16;
926
927#[derive(Clone, Debug, PartialEq, Eq)]
930pub struct ColourProperty {
931 pub name: String,
933 pub flags: u32,
935}
936
937impl ColourProperty {
938 pub fn hdr(&self) -> bool {
941 self.flags & PROPERTY_HDR != 0
942 }
943}
944
945pub fn colour_properties(player: &Player, r: &ObjRef) -> Result<Vec<ColourProperty>> {
947 let s = player.decode(r)?;
948 let mut out = Vec::new();
949 for p in properties(&s)? {
950 if p.get("m_Type").and_then(Value::as_i64) != Some(0) {
951 continue;
952 }
953 let name = p
954 .get("m_Name")
955 .and_then(Value::as_str)
956 .ok_or_else(|| invalid("shader property: a colour property has no m_Name"))?;
957 let flags = p
958 .get("m_Flags")
959 .and_then(Value::as_i64)
960 .and_then(|f| u32::try_from(f).ok())
961 .ok_or_else(|| invalid(format!("shader property {name}: m_Flags is not a u32")))?;
962 out.push(ColourProperty {
963 name: name.to_owned(),
964 flags,
965 });
966 }
967 Ok(out)
968}
969
970#[cfg(test)]
971mod tests {
972 use super::*;
973
974 fn word(out: &mut Vec<u8>, w: u32) {
975 out.extend_from_slice(&w.to_le_bytes());
976 }
977
978 fn text(out: &mut Vec<u8>, s: &str) {
979 word(out, s.len() as u32);
980 out.extend_from_slice(s.as_bytes());
981 while !out.len().is_multiple_of(4) {
982 out.push(0);
983 }
984 }
985
986 fn blob() -> Vec<u8> {
989 let mut b = Vec::new();
990 for w in [PARAMETER_BLOB_MAGIC, 3, 0, 0, 0, 0] {
991 word(&mut b, w);
992 }
993 text(&mut b, "$Globals");
994 word(&mut b, 128);
995 word(&mut b, 2);
996 text(&mut b, "_Color");
997 for w in [0, 1, 4, 0, 0, 16] {
998 word(&mut b, w);
999 }
1000 text(&mut b, "unity_MatrixV");
1001 for w in [0, 4, 4, 1, 0, 64] {
1002 word(&mut b, w);
1003 }
1004 word(&mut b, 0);
1005 text(&mut b, "UnityPerMaterial");
1006 for w in [208, 0, 0] {
1007 word(&mut b, w);
1008 }
1009 word(&mut b, 4);
1010 text(&mut b, "_IceTex");
1011 for w in [0, 0, 4, 4] {
1012 word(&mut b, w);
1013 }
1014 text(&mut b, "_Roughness");
1015 for w in [0, 7, u32::MAX, 4] {
1016 word(&mut b, w);
1017 }
1018 text(&mut b, "UnityPerMaterial");
1019 for w in [1, 6, 0] {
1020 word(&mut b, w);
1021 }
1022 text(&mut b, "");
1023 for w in [4, 3, 341] {
1024 word(&mut b, w);
1025 }
1026 b
1027 }
1028
1029 fn four_wide(name: &str, offset: u32) -> Field {
1030 Field {
1031 name: name.into(),
1032 offset,
1033 kind: 0,
1034 shape: Shape::Vector { columns: 4 },
1035 array_size: 0,
1036 }
1037 }
1038
1039 #[test]
1040 fn a_parameter_blob_gives_buffers_slots_textures_and_samplers() {
1041 let p = parameter_blob(&blob()).unwrap();
1042 assert_eq!(p.constant_buffers.len(), 2);
1043 let globals = &p.constant_buffers[0];
1044 assert_eq!(
1045 (globals.name.as_str(), globals.size, globals.slot),
1046 ("$Globals", 128, None)
1047 );
1048 assert_eq!(
1049 globals.fields,
1050 [
1051 four_wide("_Color", 16),
1052 Field {
1053 name: "unity_MatrixV".into(),
1054 offset: 64,
1055 kind: 0,
1056 shape: Shape::Matrix { rows: 4 },
1057 array_size: 0
1058 },
1059 ]
1060 );
1061 assert_eq!(p.constant_buffers[1].slot, Some(6));
1062 assert_eq!(
1063 p.textures,
1064 [
1065 TextureParam {
1066 name: "_IceTex".into(),
1067 slot: 0,
1068 sampler: Some(4),
1069 dim: 4
1070 },
1071 TextureParam {
1072 name: "_Roughness".into(),
1073 slot: 7,
1074 sampler: None,
1075 dim: 4
1076 },
1077 ]
1078 );
1079 assert_eq!(p.samplers, [(341, 3)]);
1080 }
1081
1082 #[test]
1083 fn a_parameter_blob_that_is_not_the_format_or_holds_what_is_not_read_is_refused() {
1084 let mut other = blob();
1085 other[0] ^= 1;
1086 assert!(parameter_blob(&other).is_err());
1087 let mut long = blob();
1089 long.extend_from_slice(&[0; 4]);
1090 assert!(parameter_blob(&long).is_err());
1091 let mut structured = blob();
1093 let at = structured.len() - 12;
1094 structured[at..at + 4].copy_from_slice(&2u32.to_le_bytes());
1095 assert!(parameter_blob(&structured).is_err());
1096 assert!(parameter_blob(&blob()[..40]).is_err());
1098 }
1099
1100 #[test]
1101 fn a_variants_parameters_are_its_blobs_with_the_pass_common_ones_added() {
1102 let own = parameter_blob(&blob()).unwrap();
1103 let common = Parameters {
1104 constant_buffers: vec![
1105 ConstantBuffer {
1106 name: "$Globals".into(),
1107 slot: Some(0),
1108 size: 256,
1109 fields: vec![four_wide("_Color", 16), four_wide("_Other", 32)],
1110 structs: Vec::new(),
1111 },
1112 ConstantBuffer {
1113 name: "AdditionalLights".into(),
1114 slot: Some(1),
1115 size: 24_576,
1116 fields: vec![four_wide("_Lights", 0)],
1117 structs: Vec::new(),
1118 },
1119 ],
1120 textures: vec![TextureParam {
1121 name: "_Common".into(),
1122 slot: 0,
1123 sampler: None,
1124 dim: 4,
1125 }],
1126 samplers: vec![(1, 0), (341, 3)],
1127 };
1128 let merged = merge_parameters(&common, &own);
1129 let names: Vec<&str> = merged.constant_buffers.iter().map(|c| c.name.as_str()).collect();
1130 assert_eq!(names, ["$Globals", "UnityPerMaterial", "AdditionalLights"]);
1131 let globals = &merged.constant_buffers[0];
1132 let fields: Vec<(&str, u32)> = globals
1134 .fields
1135 .iter()
1136 .map(|f| (f.name.as_str(), f.offset))
1137 .collect();
1138 assert_eq!(fields, [("_Color", 16), ("_Other", 32), ("unity_MatrixV", 64)]);
1139 assert_eq!((globals.size, globals.slot), (256, Some(0)));
1140 assert_eq!(merged.constant_buffers[1].slot, Some(6));
1141 assert_eq!(merged.textures.len(), 2);
1143 assert_eq!(merged.textures[0].name, "_IceTex");
1144 assert_eq!(merged.samplers, [(341, 3), (1, 0)]);
1145 }
1146
1147 #[test]
1149 fn a_blob_entry_past_the_table_is_refused() {
1150 let table = vec![0x40u8, 0xE8, 0x03, 0, 0];
1152 let programs = ShaderPrograms {
1153 name: "s".into(),
1154 passes: vec![],
1155 platforms: vec![4],
1156 segments: vec![vec![(0, table.len(), 4)]],
1157 blob: table,
1158 };
1159 let e = programs.code(4, 999).unwrap_err().to_string();
1160 assert!(e.contains("blob entry 999 of 1000 in a table of 4 bytes"), "{e}");
1161 }
1162 use std::sync::Arc;
1163
1164 #[test]
1165 fn the_dxbc_container_is_found_past_unitys_header() {
1166 let mut container = b"DXBC".to_vec();
1167 container.extend([0; 20]);
1168 container.extend(32u32.to_le_bytes());
1169 container.extend([0; 4]);
1170 let mut code = vec![7, 0, 0, 0, 1, 2];
1171 code.extend(&container);
1172 code.extend([9, 9]);
1173 assert_eq!(dxbc(&code), Some(&container[..]));
1174 assert_eq!(dxbc(&code[..20]), None);
1175 }
1176
1177 fn obj(fields: Vec<(&str, Value)>) -> Value {
1178 Value::Struct(fields.into_iter().map(|(k, v)| (Arc::from(k), v)).collect())
1179 }
1180
1181 fn list(items: Vec<Value>) -> Value {
1182 Value::Array(Array::Values(items))
1183 }
1184
1185 fn vector(name: i64, offset: i64, dim: i64) -> Value {
1186 obj(vec![
1187 ("m_NameIndex", Value::Int(name)),
1188 ("m_Index", Value::Int(offset)),
1189 ("m_ArraySize", Value::Int(0)),
1190 ("m_Type", Value::Int(0)),
1191 ("m_Dim", Value::Int(dim)),
1192 ])
1193 }
1194
1195 fn cbuffer(fields: Vec<Value>, structs: Vec<Value>) -> Value {
1196 obj(vec![
1197 ("m_NameIndex", Value::Int(0)),
1198 ("m_VectorParams", list(fields)),
1199 ("m_MatrixParams", list(Vec::new())),
1200 ("m_StructParams", list(structs)),
1201 ("m_Size", Value::Int(32)),
1202 ])
1203 }
1204
1205 fn params(cbs: Vec<Value>) -> Value {
1206 obj(vec![
1207 ("m_ConstantBuffers", list(cbs)),
1208 (
1209 "m_ConstantBufferBindings",
1210 list(vec![obj(vec![
1211 ("m_NameIndex", Value::Int(0)),
1212 ("m_Index", Value::Int(3)),
1213 ])]),
1214 ),
1215 ])
1216 }
1217
1218 #[test]
1219 fn parameters_resolve_names_slots_offsets_and_structs_and_refuse_what_they_cannot() {
1220 let names: std::collections::BTreeMap<i64, String> = [
1221 (0, "UnityPerMaterial".to_owned()),
1222 (1, "_BaseColor".to_owned()),
1223 (2, "_Smoothness".to_owned()),
1224 ]
1225 .into_iter()
1226 .collect();
1227 let p = parameters(
1229 ¶ms(vec![cbuffer(vec![vector(2, 16, 1), vector(1, 0, 4)], Vec::new())]),
1230 &names,
1231 )
1232 .unwrap();
1233 let cb = &p.constant_buffers[0];
1234 assert_eq!(
1235 (cb.name.as_str(), cb.slot, cb.size),
1236 ("UnityPerMaterial", Some(3), 32)
1237 );
1238 assert_eq!(
1239 cb.fields
1240 .iter()
1241 .map(|f| (f.name.as_str(), f.offset, f.shape))
1242 .collect::<Vec<_>>(),
1243 vec![
1244 ("_BaseColor", 0, Shape::Vector { columns: 4 }),
1245 ("_Smoothness", 16, Shape::Vector { columns: 1 })
1246 ]
1247 );
1248 let instanced = obj(vec![
1250 ("m_NameIndex", Value::Int(1)),
1251 ("m_Index", Value::Int(0)),
1252 ("m_ArraySize", Value::Int(2)),
1253 ("m_StructSize", Value::Int(32)),
1254 ("m_VectorMembers", list(vec![vector(2, 16, 4)])),
1255 ("m_MatrixMembers", list(Vec::new())),
1256 ]);
1257 let p = parameters(¶ms(vec![cbuffer(Vec::new(), vec![instanced])]), &names).unwrap();
1258 let s = &p.constant_buffers[0].structs[0];
1259 assert_eq!(
1260 (s.name.as_str(), s.offset, s.array_size, s.size),
1261 ("_BaseColor", 0, 2, 32)
1262 );
1263 assert_eq!(
1264 s.members
1265 .iter()
1266 .map(|f| (f.name.as_str(), f.offset))
1267 .collect::<Vec<_>>(),
1268 vec![("_Smoothness", 16)]
1269 );
1270 assert!(parameters(¶ms(vec![cbuffer(vec![vector(9, 0, 4)], Vec::new())]), &names).is_err());
1272 assert!(parameters(¶ms(vec![cbuffer(vec![vector(1, -4, 4)], Vec::new())]), &names).is_err());
1273 }
1274
1275 #[test]
1276 fn integer_arrays_read_whatever_their_element_type() {
1277 assert_eq!(ints(&Value::Array(Array::U16(vec![1, 2]))).unwrap(), vec![1, 2]);
1278 assert_eq!(
1279 ints(&list(vec![Value::Int(-1), Value::UInt(5)])).unwrap(),
1280 vec![-1, 5]
1281 );
1282 assert!(ints(&Value::Array(Array::F32(vec![1.0]))).is_err());
1283 assert!(ints(&Value::Int(1)).is_err());
1284 }
1285}