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
893pub fn texture_defaults(player: &Player, r: &ObjRef) -> Result<Vec<(String, String)>> {
896 let s = player.decode(r)?;
897 let props = s
898 .get("m_ParsedForm")
899 .and_then(|f| f.get("m_PropInfo"))
900 .map(|p| items(p, "m_Props"))
901 .transpose()?
902 .unwrap_or(&[]);
903 let mut out = Vec::new();
904 for p in props {
905 if p.get("m_Type").and_then(Value::as_i64) != Some(4) {
906 continue;
907 }
908 let name = p.get("m_Name").and_then(Value::as_str);
909 let default = p
910 .get("m_DefTexture")
911 .and_then(|d| d.get("m_DefaultName"))
912 .and_then(Value::as_str);
913 if let (Some(name), Some(default)) = (name, default) {
914 out.push((name.to_owned(), default.to_owned()));
915 }
916 }
917 Ok(out)
918}
919
920#[cfg(test)]
921mod tests {
922 use super::*;
923
924 fn word(out: &mut Vec<u8>, w: u32) {
925 out.extend_from_slice(&w.to_le_bytes());
926 }
927
928 fn text(out: &mut Vec<u8>, s: &str) {
929 word(out, s.len() as u32);
930 out.extend_from_slice(s.as_bytes());
931 while !out.len().is_multiple_of(4) {
932 out.push(0);
933 }
934 }
935
936 fn blob() -> Vec<u8> {
939 let mut b = Vec::new();
940 for w in [PARAMETER_BLOB_MAGIC, 3, 0, 0, 0, 0] {
941 word(&mut b, w);
942 }
943 text(&mut b, "$Globals");
944 word(&mut b, 128);
945 word(&mut b, 2);
946 text(&mut b, "_Color");
947 for w in [0, 1, 4, 0, 0, 16] {
948 word(&mut b, w);
949 }
950 text(&mut b, "unity_MatrixV");
951 for w in [0, 4, 4, 1, 0, 64] {
952 word(&mut b, w);
953 }
954 word(&mut b, 0);
955 text(&mut b, "UnityPerMaterial");
956 for w in [208, 0, 0] {
957 word(&mut b, w);
958 }
959 word(&mut b, 4);
960 text(&mut b, "_IceTex");
961 for w in [0, 0, 4, 4] {
962 word(&mut b, w);
963 }
964 text(&mut b, "_Roughness");
965 for w in [0, 7, u32::MAX, 4] {
966 word(&mut b, w);
967 }
968 text(&mut b, "UnityPerMaterial");
969 for w in [1, 6, 0] {
970 word(&mut b, w);
971 }
972 text(&mut b, "");
973 for w in [4, 3, 341] {
974 word(&mut b, w);
975 }
976 b
977 }
978
979 fn four_wide(name: &str, offset: u32) -> Field {
980 Field {
981 name: name.into(),
982 offset,
983 kind: 0,
984 shape: Shape::Vector { columns: 4 },
985 array_size: 0,
986 }
987 }
988
989 #[test]
990 fn a_parameter_blob_gives_buffers_slots_textures_and_samplers() {
991 let p = parameter_blob(&blob()).unwrap();
992 assert_eq!(p.constant_buffers.len(), 2);
993 let globals = &p.constant_buffers[0];
994 assert_eq!(
995 (globals.name.as_str(), globals.size, globals.slot),
996 ("$Globals", 128, None)
997 );
998 assert_eq!(
999 globals.fields,
1000 [
1001 four_wide("_Color", 16),
1002 Field {
1003 name: "unity_MatrixV".into(),
1004 offset: 64,
1005 kind: 0,
1006 shape: Shape::Matrix { rows: 4 },
1007 array_size: 0
1008 },
1009 ]
1010 );
1011 assert_eq!(p.constant_buffers[1].slot, Some(6));
1012 assert_eq!(
1013 p.textures,
1014 [
1015 TextureParam {
1016 name: "_IceTex".into(),
1017 slot: 0,
1018 sampler: Some(4),
1019 dim: 4
1020 },
1021 TextureParam {
1022 name: "_Roughness".into(),
1023 slot: 7,
1024 sampler: None,
1025 dim: 4
1026 },
1027 ]
1028 );
1029 assert_eq!(p.samplers, [(341, 3)]);
1030 }
1031
1032 #[test]
1033 fn a_parameter_blob_that_is_not_the_format_or_holds_what_is_not_read_is_refused() {
1034 let mut other = blob();
1035 other[0] ^= 1;
1036 assert!(parameter_blob(&other).is_err());
1037 let mut long = blob();
1039 long.extend_from_slice(&[0; 4]);
1040 assert!(parameter_blob(&long).is_err());
1041 let mut structured = blob();
1043 let at = structured.len() - 12;
1044 structured[at..at + 4].copy_from_slice(&2u32.to_le_bytes());
1045 assert!(parameter_blob(&structured).is_err());
1046 assert!(parameter_blob(&blob()[..40]).is_err());
1048 }
1049
1050 #[test]
1051 fn a_variants_parameters_are_its_blobs_with_the_pass_common_ones_added() {
1052 let own = parameter_blob(&blob()).unwrap();
1053 let common = Parameters {
1054 constant_buffers: vec![
1055 ConstantBuffer {
1056 name: "$Globals".into(),
1057 slot: Some(0),
1058 size: 256,
1059 fields: vec![four_wide("_Color", 16), four_wide("_Other", 32)],
1060 structs: Vec::new(),
1061 },
1062 ConstantBuffer {
1063 name: "AdditionalLights".into(),
1064 slot: Some(1),
1065 size: 24_576,
1066 fields: vec![four_wide("_Lights", 0)],
1067 structs: Vec::new(),
1068 },
1069 ],
1070 textures: vec![TextureParam {
1071 name: "_Common".into(),
1072 slot: 0,
1073 sampler: None,
1074 dim: 4,
1075 }],
1076 samplers: vec![(1, 0), (341, 3)],
1077 };
1078 let merged = merge_parameters(&common, &own);
1079 let names: Vec<&str> = merged.constant_buffers.iter().map(|c| c.name.as_str()).collect();
1080 assert_eq!(names, ["$Globals", "UnityPerMaterial", "AdditionalLights"]);
1081 let globals = &merged.constant_buffers[0];
1082 let fields: Vec<(&str, u32)> = globals
1084 .fields
1085 .iter()
1086 .map(|f| (f.name.as_str(), f.offset))
1087 .collect();
1088 assert_eq!(fields, [("_Color", 16), ("_Other", 32), ("unity_MatrixV", 64)]);
1089 assert_eq!((globals.size, globals.slot), (256, Some(0)));
1090 assert_eq!(merged.constant_buffers[1].slot, Some(6));
1091 assert_eq!(merged.textures.len(), 2);
1093 assert_eq!(merged.textures[0].name, "_IceTex");
1094 assert_eq!(merged.samplers, [(341, 3), (1, 0)]);
1095 }
1096
1097 #[test]
1099 fn a_blob_entry_past_the_table_is_refused() {
1100 let table = vec![0x40u8, 0xE8, 0x03, 0, 0];
1102 let programs = ShaderPrograms {
1103 name: "s".into(),
1104 passes: vec![],
1105 platforms: vec![4],
1106 segments: vec![vec![(0, table.len(), 4)]],
1107 blob: table,
1108 };
1109 let e = programs.code(4, 999).unwrap_err().to_string();
1110 assert!(e.contains("blob entry 999 of 1000 in a table of 4 bytes"), "{e}");
1111 }
1112 use std::sync::Arc;
1113
1114 #[test]
1115 fn the_dxbc_container_is_found_past_unitys_header() {
1116 let mut container = b"DXBC".to_vec();
1117 container.extend([0; 20]);
1118 container.extend(32u32.to_le_bytes());
1119 container.extend([0; 4]);
1120 let mut code = vec![7, 0, 0, 0, 1, 2];
1121 code.extend(&container);
1122 code.extend([9, 9]);
1123 assert_eq!(dxbc(&code), Some(&container[..]));
1124 assert_eq!(dxbc(&code[..20]), None);
1125 }
1126
1127 fn obj(fields: Vec<(&str, Value)>) -> Value {
1128 Value::Struct(fields.into_iter().map(|(k, v)| (Arc::from(k), v)).collect())
1129 }
1130
1131 fn list(items: Vec<Value>) -> Value {
1132 Value::Array(Array::Values(items))
1133 }
1134
1135 fn vector(name: i64, offset: i64, dim: i64) -> Value {
1136 obj(vec![
1137 ("m_NameIndex", Value::Int(name)),
1138 ("m_Index", Value::Int(offset)),
1139 ("m_ArraySize", Value::Int(0)),
1140 ("m_Type", Value::Int(0)),
1141 ("m_Dim", Value::Int(dim)),
1142 ])
1143 }
1144
1145 fn cbuffer(fields: Vec<Value>, structs: Vec<Value>) -> Value {
1146 obj(vec![
1147 ("m_NameIndex", Value::Int(0)),
1148 ("m_VectorParams", list(fields)),
1149 ("m_MatrixParams", list(Vec::new())),
1150 ("m_StructParams", list(structs)),
1151 ("m_Size", Value::Int(32)),
1152 ])
1153 }
1154
1155 fn params(cbs: Vec<Value>) -> Value {
1156 obj(vec![
1157 ("m_ConstantBuffers", list(cbs)),
1158 (
1159 "m_ConstantBufferBindings",
1160 list(vec![obj(vec![
1161 ("m_NameIndex", Value::Int(0)),
1162 ("m_Index", Value::Int(3)),
1163 ])]),
1164 ),
1165 ])
1166 }
1167
1168 #[test]
1169 fn parameters_resolve_names_slots_offsets_and_structs_and_refuse_what_they_cannot() {
1170 let names: std::collections::BTreeMap<i64, String> = [
1171 (0, "UnityPerMaterial".to_owned()),
1172 (1, "_BaseColor".to_owned()),
1173 (2, "_Smoothness".to_owned()),
1174 ]
1175 .into_iter()
1176 .collect();
1177 let p = parameters(
1179 ¶ms(vec![cbuffer(vec![vector(2, 16, 1), vector(1, 0, 4)], Vec::new())]),
1180 &names,
1181 )
1182 .unwrap();
1183 let cb = &p.constant_buffers[0];
1184 assert_eq!(
1185 (cb.name.as_str(), cb.slot, cb.size),
1186 ("UnityPerMaterial", Some(3), 32)
1187 );
1188 assert_eq!(
1189 cb.fields
1190 .iter()
1191 .map(|f| (f.name.as_str(), f.offset, f.shape))
1192 .collect::<Vec<_>>(),
1193 vec![
1194 ("_BaseColor", 0, Shape::Vector { columns: 4 }),
1195 ("_Smoothness", 16, Shape::Vector { columns: 1 })
1196 ]
1197 );
1198 let instanced = obj(vec![
1200 ("m_NameIndex", Value::Int(1)),
1201 ("m_Index", Value::Int(0)),
1202 ("m_ArraySize", Value::Int(2)),
1203 ("m_StructSize", Value::Int(32)),
1204 ("m_VectorMembers", list(vec![vector(2, 16, 4)])),
1205 ("m_MatrixMembers", list(Vec::new())),
1206 ]);
1207 let p = parameters(¶ms(vec![cbuffer(Vec::new(), vec![instanced])]), &names).unwrap();
1208 let s = &p.constant_buffers[0].structs[0];
1209 assert_eq!(
1210 (s.name.as_str(), s.offset, s.array_size, s.size),
1211 ("_BaseColor", 0, 2, 32)
1212 );
1213 assert_eq!(
1214 s.members
1215 .iter()
1216 .map(|f| (f.name.as_str(), f.offset))
1217 .collect::<Vec<_>>(),
1218 vec![("_Smoothness", 16)]
1219 );
1220 assert!(parameters(¶ms(vec![cbuffer(vec![vector(9, 0, 4)], Vec::new())]), &names).is_err());
1222 assert!(parameters(¶ms(vec![cbuffer(vec![vector(1, -4, 4)], Vec::new())]), &names).is_err());
1223 }
1224
1225 #[test]
1226 fn integer_arrays_read_whatever_their_element_type() {
1227 assert_eq!(ints(&Value::Array(Array::U16(vec![1, 2]))).unwrap(), vec![1, 2]);
1228 assert_eq!(
1229 ints(&list(vec![Value::Int(-1), Value::UInt(5)])).unwrap(),
1230 vec![-1, 5]
1231 );
1232 assert!(ints(&Value::Array(Array::F32(vec![1.0]))).is_err());
1233 assert!(ints(&Value::Int(1)).is_err());
1234 }
1235}