1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::Write as _;
3use std::path::Path;
4
5use crate::cfx::ShaderBlob;
6use crate::disasm::ShaderKind;
7
8const FX_MAGIC_LE: u32 = 0xfeff_0901;
9const FX_MAGIC_BYTESWAPPED: u32 = 0x0109_fffe;
10const D3DXPC_SCALAR: u32 = 0;
11const D3DXPC_VECTOR: u32 = 1;
12const D3DXPC_MATRIX_ROWS: u32 = 2;
13const D3DXPC_MATRIX_COLUMNS: u32 = 3;
14const D3DXPC_OBJECT: u32 = 4;
15const D3DXPC_STRUCT: u32 = 5;
16
17const D3DXPT_STRING: u32 = 4;
18const D3DXPT_TEXTURE: u32 = 5;
19const D3DXPT_TEXTURE1D: u32 = 6;
20const D3DXPT_TEXTURE2D: u32 = 7;
21const D3DXPT_TEXTURE3D: u32 = 8;
22const D3DXPT_TEXTURECUBE: u32 = 9;
23const D3DXPT_SAMPLER: u32 = 10;
24const D3DXPT_SAMPLER1D: u32 = 11;
25const D3DXPT_SAMPLER2D: u32 = 12;
26const D3DXPT_SAMPLER3D: u32 = 13;
27const D3DXPT_SAMPLERCUBE: u32 = 14;
28const D3DXPT_PIXELSHADER: u32 = 15;
29const D3DXPT_VERTEXSHADER: u32 = 16;
30
31#[derive(Debug, Clone)]
32pub struct EffectFile {
33 pub tag: u32,
34 pub table_base: usize,
35 pub start_offset: usize,
36 pub parameter_count: u32,
37 pub technique_count: u32,
38 pub object_count: u32,
39 pub string_count: u32,
40 pub resource_count: u32,
41 pub parameters: Vec<EffectParameter>,
42 pub techniques: Vec<Technique>,
43 pub objects: Vec<EffectObject>,
44 pub notes: Vec<String>,
45}
46
47#[derive(Debug, Clone)]
48pub struct EffectParameter {
49 pub index: usize,
50 pub name: String,
51 pub semantic: String,
52 pub value_type: u32,
53 pub class: u32,
54 pub rows: u32,
55 pub columns: u32,
56 pub elements: u32,
57 pub bytes: u32,
58 pub flags: u32,
59 pub object_id: Option<u32>,
60}
61
62#[derive(Debug, Clone)]
63pub struct Technique {
64 pub index: usize,
65 pub name: String,
66 pub annotation_count: u32,
67 pub passes: Vec<Pass>,
68}
69
70#[derive(Debug, Clone)]
71pub struct Pass {
72 pub index: usize,
73 pub name: String,
74 pub annotation_count: u32,
75 pub states: Vec<State>,
76 pub vertex_shader: Option<ShaderRef>,
77 pub pixel_shader: Option<ShaderRef>,
78}
79
80#[derive(Debug, Clone)]
81pub struct State {
82 pub index: usize,
83 pub operation: u32,
84 pub operation_name: String,
85 pub class_name: String,
86 pub state_index: u32,
87 pub typedef_offset: u32,
88 pub value_offset: u32,
89 pub parameter: EffectParameter,
90 pub value: StateValue,
91 pub resource_usage: Option<u32>,
92}
93
94#[derive(Debug, Clone)]
95pub enum StateValue {
96 Empty,
97 ObjectId(u32),
98 Int(Vec<i32>),
99 Float(Vec<f32>),
100 Bool(Vec<bool>),
101 StringObject {
102 object_id: u32,
103 text: Option<String>,
104 },
105 Raw {
106 offset: u32,
107 bytes: usize,
108 },
109}
110
111#[derive(Debug, Clone)]
112pub struct ShaderRef {
113 pub kind: ShaderKind,
114 pub object_id: Option<u32>,
115 pub object_data_offset: Option<usize>,
116 pub object_size: usize,
117 pub shader_index: Option<usize>,
118 pub shader_offset: Option<usize>,
119 pub unresolved_reason: Option<String>,
120}
121
122#[derive(Debug, Clone, Default)]
123pub struct EffectObject {
124 pub id: usize,
125 pub data_offset: Option<usize>,
126 pub size: usize,
127 pub owner_name: Option<String>,
128 pub owner_type: Option<u32>,
129}
130
131struct Reader<'a> {
132 data: &'a [u8],
133 pos: usize,
134}
135
136impl<'a> Reader<'a> {
137 fn new(data: &'a [u8], pos: usize) -> Self {
138 Self { data, pos }
139 }
140
141 fn read_u32(&mut self) -> Result<u32, String> {
142 let off = self.pos;
143 let b = self
144 .data
145 .get(off..off + 4)
146 .ok_or_else(|| format!("truncated dword at 0x{off:x}"))?;
147 self.pos += 4;
148 Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
149 }
150
151 fn skip_dwords(&mut self, count: u32) -> Result<(), String> {
152 let bytes = (count as usize)
153 .checked_mul(4)
154 .ok_or_else(|| "dword skip overflow".to_string())?;
155 let new_pos = self
156 .pos
157 .checked_add(bytes)
158 .ok_or_else(|| "reader overflow".to_string())?;
159 if new_pos > self.data.len() {
160 return Err(format!(
161 "skip beyond EOF: 0x{:x} > 0x{:x}",
162 new_pos,
163 self.data.len()
164 ));
165 }
166 self.pos = new_pos;
167 Ok(())
168 }
169}
170
171#[derive(Debug, Clone)]
172struct TypeDef {
173 name: String,
174 semantic: String,
175 value_type: u32,
176 class: u32,
177 rows: u32,
178 columns: u32,
179 elements: u32,
180 member_count: u32,
181 bytes: u32,
182}
183
184pub fn parse_effect(data: &[u8], shaders: &[ShaderBlob]) -> Result<EffectFile, String> {
185 if data.len() < 8 {
186 return Err("file is too short for a D3DX effect header".to_string());
187 }
188
189 let tag = read_u32_at(data, 0)?;
190 if tag != FX_MAGIC_LE && tag != FX_MAGIC_BYTESWAPPED {
191 return Err(format!("not a D3DX9 compiled effect tag: 0x{tag:08x}"));
192 }
193
194 let table_base = 8usize;
195 let start_rel = read_u32_at(data, 4)? as usize;
196 let start_offset = table_base
197 .checked_add(start_rel)
198 .ok_or_else(|| "effect start offset overflow".to_string())?;
199 if start_offset + 16 > data.len() {
200 return Err(format!(
201 "effect start offset outside file: 0x{start_offset:x}"
202 ));
203 }
204
205 let mut notes = Vec::new();
206 let mut objects = Vec::new();
207 let mut r = Reader::new(data, start_offset);
208 let parameter_count = r.read_u32()?;
209 let technique_count = r.read_u32()?;
210 let unknown = r.read_u32()?;
211 let object_count = r.read_u32()?;
212 if unknown != 0 {
213 notes.push(format!("effect header unknown dword is 0x{unknown:08x}"));
214 }
215 for id in 0..object_count as usize {
216 objects.push(EffectObject {
217 id,
218 ..EffectObject::default()
219 });
220 }
221
222 let mut parameters = Vec::new();
223 for index in 0..parameter_count as usize {
224 let parameter = parse_top_level_parameter(data, table_base, &mut r, index, &mut objects)?;
225 parameters.push(parameter);
226 }
227
228 let mut techniques = Vec::new();
229 for index in 0..technique_count as usize {
230 let technique = parse_technique(data, table_base, &mut r, index, &mut objects)?;
231 techniques.push(technique);
232 }
233
234 let string_count = if r.pos + 4 <= data.len() {
239 r.read_u32()?
240 } else {
241 0
242 };
243 let resource_count = if r.pos + 4 <= data.len() {
244 r.read_u32()?
245 } else {
246 0
247 };
248
249 for _ in 0..string_count {
250 let object_id = r.read_u32()? as usize;
251 copy_object_data(data, &mut r, object_id, &mut objects, &mut notes)?;
252 }
253
254 for resource_index in 0..resource_count {
255 match parse_resource_like(
256 data,
257 &mut r,
258 &mut techniques,
259 &mut parameters,
260 &mut objects,
261 &mut notes,
262 ) {
263 Ok(()) => {}
264 Err(e) => {
265 notes.push(format!("resource {resource_index} parse stopped: {e}"));
266 break;
267 }
268 }
269 }
270
271 resolve_shader_refs(&mut techniques, &objects, shaders);
272
273 Ok(EffectFile {
274 tag,
275 table_base,
276 start_offset,
277 parameter_count,
278 technique_count,
279 object_count,
280 string_count,
281 resource_count,
282 parameters,
283 techniques,
284 objects,
285 notes,
286 })
287}
288
289fn parse_top_level_parameter(
290 data: &[u8],
291 base: usize,
292 r: &mut Reader<'_>,
293 index: usize,
294 objects: &mut [EffectObject],
295) -> Result<EffectParameter, String> {
296 let typedef_offset = r.read_u32()?;
297 let value_offset = r.read_u32()?;
298 let flags = r.read_u32()?;
299 let annotation_count = r.read_u32()?;
300 let td = parse_typedef_at(data, base, typedef_offset)?;
301 let value = parse_value_at(data, base, value_offset, &td, objects)?;
302 let param = parameter_from_typedef(index, td, flags, value.object_id());
303 if let Some(id) = param.object_id
304 && let Some(obj) = objects.get_mut(id as usize)
305 {
306 obj.owner_name = Some(param.name.clone());
307 obj.owner_type = Some(param.value_type);
308 }
309 r.skip_dwords(annotation_count.saturating_mul(2))?;
310 Ok(param)
311}
312
313fn parse_technique(
314 data: &[u8],
315 base: usize,
316 r: &mut Reader<'_>,
317 index: usize,
318 objects: &mut [EffectObject],
319) -> Result<Technique, String> {
320 let name_offset = r.read_u32()?;
321 let name =
322 read_name_at(data, base, name_offset).unwrap_or_else(|_| format!("technique_{index}"));
323 let annotation_count = r.read_u32()?;
324 let pass_count = r.read_u32()?;
325 r.skip_dwords(annotation_count.saturating_mul(2))?;
326
327 let mut passes = Vec::new();
328 for pass_index in 0..pass_count as usize {
329 passes.push(parse_pass(data, base, r, pass_index, objects)?);
330 }
331
332 Ok(Technique {
333 index,
334 name,
335 annotation_count,
336 passes,
337 })
338}
339
340fn parse_pass(
341 data: &[u8],
342 base: usize,
343 r: &mut Reader<'_>,
344 index: usize,
345 objects: &mut [EffectObject],
346) -> Result<Pass, String> {
347 let name_offset = r.read_u32()?;
348 let name = read_name_at(data, base, name_offset).unwrap_or_else(|_| format!("pass{index}"));
349 let annotation_count = r.read_u32()?;
350 let state_count = r.read_u32()?;
351 r.skip_dwords(annotation_count.saturating_mul(2))?;
352
353 let mut states = Vec::new();
354 let mut vertex_shader = None;
355 let mut pixel_shader = None;
356 for state_index in 0..state_count as usize {
357 let state = parse_state(data, base, r, state_index, objects)?;
358 if is_vertex_shader_state(&state) {
359 vertex_shader = Some(ShaderRef {
360 kind: ShaderKind::Vertex,
361 object_id: state.value.object_id(),
362 object_data_offset: None,
363 object_size: 0,
364 shader_index: None,
365 shader_offset: None,
366 unresolved_reason: None,
367 });
368 } else if is_pixel_shader_state(&state) {
369 pixel_shader = Some(ShaderRef {
370 kind: ShaderKind::Pixel,
371 object_id: state.value.object_id(),
372 object_data_offset: None,
373 object_size: 0,
374 shader_index: None,
375 shader_offset: None,
376 unresolved_reason: None,
377 });
378 }
379 states.push(state);
380 }
381
382 Ok(Pass {
383 index,
384 name,
385 annotation_count,
386 states,
387 vertex_shader,
388 pixel_shader,
389 })
390}
391
392fn parse_state(
393 data: &[u8],
394 base: usize,
395 r: &mut Reader<'_>,
396 index: usize,
397 objects: &mut [EffectObject],
398) -> Result<State, String> {
399 let operation = r.read_u32()?;
400 let state_index = r.read_u32()?;
401 let typedef_offset = r.read_u32()?;
402 let td = parse_typedef_at(data, base, typedef_offset)?;
403 let value_offset = r.read_u32()?;
404 let parsed = parse_value_at(data, base, value_offset, &td, objects)?;
405 let parameter = parameter_from_typedef(index, td, 0, parsed.object_id());
406 let (operation_name, class_name) = operation_info(operation);
407 Ok(State {
408 index,
409 operation,
410 operation_name: operation_name.to_string(),
411 class_name: class_name.to_string(),
412 state_index,
413 typedef_offset,
414 value_offset,
415 parameter,
416 value: parsed.value,
417 resource_usage: None,
418 })
419}
420
421#[derive(Debug, Clone)]
422struct ParsedValue {
423 value: StateValue,
424}
425
426impl ParsedValue {
427 fn object_id(&self) -> Option<u32> {
428 self.value.object_id()
429 }
430}
431
432impl StateValue {
433 pub fn object_id(&self) -> Option<u32> {
434 match self {
435 StateValue::ObjectId(id) => Some(*id),
436 StateValue::StringObject { object_id, .. } => Some(*object_id),
437 _ => None,
438 }
439 }
440}
441
442fn parse_value_at(
443 data: &[u8],
444 base: usize,
445 value_offset: u32,
446 td: &TypeDef,
447 objects: &mut [EffectObject],
448) -> Result<ParsedValue, String> {
449 let off = checked_base_offset(base, value_offset)?;
450 if off > data.len() {
451 return Err(format!("value offset outside file: 0x{off:x}"));
452 }
453
454 let value = if td.class == D3DXPC_OBJECT || is_object_type(td.value_type) {
455 if off + 4 > data.len() {
456 StateValue::Empty
457 } else {
458 let object_id = read_u32_at(data, off)?;
459 if let Some(obj) = objects.get_mut(object_id as usize) {
460 if !td.name.is_empty() {
461 obj.owner_name = Some(td.name.clone());
462 }
463 obj.owner_type = Some(td.value_type);
464 }
465 if td.value_type == D3DXPT_STRING {
466 let text = objects
467 .get(object_id as usize)
468 .and_then(|o| o.data_offset)
469 .and_then(|pos| read_nul_string(data, pos).ok());
470 StateValue::StringObject { object_id, text }
471 } else {
472 StateValue::ObjectId(object_id)
473 }
474 }
475 } else if td.value_type == 3 {
476 let count = scalar_count(td).min(256);
477 let mut values = Vec::new();
478 for i in 0..count {
479 let p = off + (i as usize) * 4;
480 if p + 4 <= data.len() {
481 values.push(f32::from_le_bytes([
482 data[p],
483 data[p + 1],
484 data[p + 2],
485 data[p + 3],
486 ]));
487 }
488 }
489 StateValue::Float(values)
490 } else if td.value_type == 2 {
491 let count = scalar_count(td).min(256);
492 let mut values = Vec::new();
493 for i in 0..count {
494 let p = off + (i as usize) * 4;
495 if p + 4 <= data.len() {
496 values.push(i32::from_le_bytes([
497 data[p],
498 data[p + 1],
499 data[p + 2],
500 data[p + 3],
501 ]));
502 }
503 }
504 StateValue::Int(values)
505 } else if td.value_type == 1 {
506 let count = scalar_count(td).min(256);
507 let mut values = Vec::new();
508 for i in 0..count {
509 let p = off + (i as usize) * 4;
510 if p + 4 <= data.len() {
511 values.push(read_u32_at(data, p)? != 0);
512 }
513 }
514 StateValue::Bool(values)
515 } else {
516 StateValue::Raw {
517 offset: value_offset,
518 bytes: td.bytes as usize,
519 }
520 };
521
522 Ok(ParsedValue { value })
523}
524
525fn scalar_count(td: &TypeDef) -> u32 {
526 let elems = if td.elements == 0 { 1 } else { td.elements };
527 (td.rows.max(1))
528 .saturating_mul(td.columns.max(1))
529 .saturating_mul(elems)
530}
531
532fn parse_typedef_at(data: &[u8], base: usize, offset: u32) -> Result<TypeDef, String> {
533 let pos = checked_base_offset(base, offset)?;
534 let mut r = Reader::new(data, pos);
535 let value_type = r.read_u32()?;
536 let class = r.read_u32()?;
537 let name_offset = r.read_u32()?;
538 let name = read_name_at(data, base, name_offset).unwrap_or_default();
539 let semantic_offset = r.read_u32()?;
540 let semantic = read_name_at(data, base, semantic_offset).unwrap_or_default();
541 let elements = r.read_u32()?;
542
543 let (rows, columns, member_count, object_size) = match class {
547 D3DXPC_VECTOR => {
548 let columns = r.read_u32()?;
549 let rows = r.read_u32()?;
550 (
551 rows,
552 columns,
553 0,
554 rows.saturating_mul(columns).saturating_mul(4),
555 )
556 }
557 D3DXPC_SCALAR | D3DXPC_MATRIX_ROWS | D3DXPC_MATRIX_COLUMNS => {
558 let rows = r.read_u32()?;
559 let columns = r.read_u32()?;
560 (
561 rows,
562 columns,
563 0,
564 rows.saturating_mul(columns).saturating_mul(4),
565 )
566 }
567 D3DXPC_STRUCT => {
568 let members = r.read_u32()?;
569 (0, 0, members, 0)
570 }
571 D3DXPC_OBJECT => (0, 0, 0, 4),
572 _ => (0, 0, 0, 0),
573 };
574
575 let elem_count = if elements == 0 { 1 } else { elements };
576 let bytes = if class == D3DXPC_STRUCT {
577 0
578 } else if class == D3DXPC_OBJECT {
579 if matches!(
580 value_type,
581 D3DXPT_SAMPLER
582 | D3DXPT_SAMPLER1D
583 | D3DXPT_SAMPLER2D
584 | D3DXPT_SAMPLER3D
585 | D3DXPT_SAMPLERCUBE
586 ) {
587 0
588 } else {
589 object_size.saturating_mul(elem_count)
590 }
591 } else {
592 object_size.saturating_mul(elem_count)
593 };
594
595 Ok(TypeDef {
596 name,
597 semantic,
598 value_type,
599 class,
600 rows,
601 columns,
602 elements,
603 member_count,
604 bytes,
605 })
606}
607
608fn parameter_from_typedef(
609 index: usize,
610 td: TypeDef,
611 flags: u32,
612 object_id: Option<u32>,
613) -> EffectParameter {
614 EffectParameter {
615 index,
616 name: td.name,
617 semantic: td.semantic,
618 value_type: td.value_type,
619 class: td.class,
620 rows: td.rows,
621 columns: td.columns,
622 elements: td.elements,
623 bytes: td.bytes,
624 flags,
625 object_id,
626 }
627}
628
629fn copy_object_data(
630 data: &[u8],
631 r: &mut Reader<'_>,
632 object_id: usize,
633 objects: &mut [EffectObject],
634 notes: &mut Vec<String>,
635) -> Result<(), String> {
636 let size = r.read_u32()? as usize;
637 let start = r.pos;
638 let end = start
639 .checked_add(size)
640 .ok_or_else(|| "object data size overflow".to_string())?;
641 if end > data.len() {
642 return Err(format!("object {object_id} data outside file: 0x{end:x}"));
643 }
644 if let Some(obj) = objects.get_mut(object_id) {
645 obj.data_offset = Some(start);
646 obj.size = size;
647 } else {
648 notes.push(format!(
649 "object data references out-of-range object id {object_id}"
650 ));
651 }
652 r.pos = align4(end);
653 if r.pos > data.len() {
654 return Err(format!("object {object_id} aligned data outside file"));
655 }
656 Ok(())
657}
658
659fn parse_resource_like(
660 data: &[u8],
661 r: &mut Reader<'_>,
662 techniques: &mut [Technique],
663 parameters: &mut [EffectParameter],
664 objects: &mut [EffectObject],
665 notes: &mut Vec<String>,
666) -> Result<(), String> {
667 let rec_start = r.pos;
671 let technique_index = r.read_u32()?;
672 let index = r.read_u32()?;
673 let element_index = r.read_u32()?;
674 let state_index = r.read_u32()?;
675 let usage = r.read_u32()?;
676
677 let object_id = if technique_index == 0xffff_ffff {
678 let param = parameters
679 .get(index as usize)
680 .ok_or_else(|| format!("resource parameter index {index} outside parameter table"))?;
681 if element_index != 0xffff_ffff {
682 notes.push(format!(
683 "resource at 0x{rec_start:x} references parameter element {element_index}; using parent object id"
684 ));
685 }
686 param.object_id.ok_or_else(|| {
687 format!("resource at 0x{rec_start:x} top-level parameter {index} has no object id")
688 })?
689 } else {
690 let tech = techniques
691 .get_mut(technique_index as usize)
692 .ok_or_else(|| {
693 format!("resource technique index {technique_index} outside technique table")
694 })?;
695 let pass = tech.passes.get_mut(index as usize).ok_or_else(|| {
696 format!("resource pass index {index} outside technique {technique_index}")
697 })?;
698 let state = pass
699 .states
700 .get_mut(state_index as usize)
701 .ok_or_else(|| format!("resource state index {state_index} outside technique {technique_index} pass {index}"))?;
702 state.resource_usage = Some(usage);
703 state.parameter.object_id.ok_or_else(|| {
704 format!(
705 "resource at 0x{rec_start:x} technique {technique_index} pass {index} state {state_index} has no object id"
706 )
707 })?
708 };
709
710 copy_object_data(data, r, object_id as usize, objects, notes)?;
711 Ok(())
712}
713
714fn resolve_shader_refs(
715 techniques: &mut [Technique],
716 objects: &[EffectObject],
717 shaders: &[ShaderBlob],
718) {
719 for tech in techniques {
720 for pass in &mut tech.passes {
721 if let Some(sr) = &mut pass.vertex_shader {
722 resolve_one_shader_ref(sr, objects, shaders);
723 }
724 if let Some(sr) = &mut pass.pixel_shader {
725 resolve_one_shader_ref(sr, objects, shaders);
726 }
727 }
728 }
729}
730
731fn resolve_one_shader_ref(sr: &mut ShaderRef, objects: &[EffectObject], shaders: &[ShaderBlob]) {
732 let Some(id) = sr.object_id else {
733 sr.unresolved_reason = Some("state did not contain an object id".to_string());
734 return;
735 };
736 let Some(obj) = objects.get(id as usize) else {
737 sr.unresolved_reason = Some(format!("object id {id} is outside object table"));
738 return;
739 };
740 sr.object_data_offset = obj.data_offset;
741 sr.object_size = obj.size;
742 let Some(data_off) = obj.data_offset else {
743 sr.unresolved_reason = Some(format!("object id {id} has no copied data payload"));
744 return;
745 };
746
747 let by_offset = shaders
748 .iter()
749 .find(|s| s.kind == sr.kind && s.offset == data_off);
750 let by_range = shaders.iter().find(|s| {
751 s.kind == sr.kind
752 && data_off >= s.offset
753 && data_off < s.end_offset
754 && s.offset >= data_off.saturating_sub(16)
755 });
756 let by_size = shaders.iter().find(|s| {
757 s.kind == sr.kind && obj.size == s.bytes.len() && obj.data_offset == Some(s.offset)
758 });
759 let matched = by_offset.or(by_size).or(by_range);
760 if let Some(shader) = matched {
761 sr.shader_index = Some(shader.index);
762 sr.shader_offset = Some(shader.offset);
763 sr.unresolved_reason = None;
764 } else {
765 sr.unresolved_reason = Some(format!(
766 "object id {id} payload offset=0x{data_off:x} size={} did not match any scanned {:?} shader",
767 obj.size, sr.kind
768 ));
769 }
770}
771
772fn is_object_type(t: u32) -> bool {
773 matches!(
774 t,
775 D3DXPT_STRING
776 | D3DXPT_TEXTURE
777 | D3DXPT_TEXTURE1D
778 | D3DXPT_TEXTURE2D
779 | D3DXPT_TEXTURE3D
780 | D3DXPT_TEXTURECUBE
781 | D3DXPT_SAMPLER
782 | D3DXPT_SAMPLER1D
783 | D3DXPT_SAMPLER2D
784 | D3DXPT_SAMPLER3D
785 | D3DXPT_SAMPLERCUBE
786 | D3DXPT_PIXELSHADER
787 | D3DXPT_VERTEXSHADER
788 )
789}
790
791fn is_vertex_shader_state(state: &State) -> bool {
792 state.operation == 146 || state.parameter.value_type == D3DXPT_VERTEXSHADER
793}
794
795fn is_pixel_shader_state(state: &State) -> bool {
796 state.operation == 147 || state.parameter.value_type == D3DXPT_PIXELSHADER
797}
798
799pub fn operation_info(op: u32) -> (&'static str, &'static str) {
800 match op {
801 0 => ("ZEnable", "RenderState"),
802 1 => ("FillMode", "RenderState"),
803 2 => ("ShadeMode", "RenderState"),
804 3 => ("ZWriteEnable", "RenderState"),
805 4 => ("AlphaTestEnable", "RenderState"),
806 5 => ("LastPixel", "RenderState"),
807 6 => ("SrcBlend", "RenderState"),
808 7 => ("DestBlend", "RenderState"),
809 8 => ("CullMode", "RenderState"),
810 9 => ("ZFunc", "RenderState"),
811 10 => ("AlphaRef", "RenderState"),
812 11 => ("AlphaFunc", "RenderState"),
813 12 => ("DitherEnable", "RenderState"),
814 13 => ("AlphaBlendEnable", "RenderState"),
815 14 => ("FogEnable", "RenderState"),
816 15 => ("SpecularEnable", "RenderState"),
817 16 => ("FogColor", "RenderState"),
818 17 => ("FogTableMode", "RenderState"),
819 18 => ("FogStart", "RenderState"),
820 19 => ("FogEnd", "RenderState"),
821 20 => ("FogDensity", "RenderState"),
822 21 => ("RangeFogEnable", "RenderState"),
823 22 => ("StencilEnable", "RenderState"),
824 23 => ("StencilFail", "RenderState"),
825 24 => ("StencilZFail", "RenderState"),
826 25 => ("StencilPass", "RenderState"),
827 26 => ("StencilFunc", "RenderState"),
828 27 => ("StencilRef", "RenderState"),
829 28 => ("StencilMask", "RenderState"),
830 29 => ("StencilWriteMask", "RenderState"),
831 30 => ("TextureFactor", "RenderState"),
832 31 => ("Wrap0", "RenderState"),
833 32 => ("Wrap1", "RenderState"),
834 33 => ("Wrap2", "RenderState"),
835 34 => ("Wrap3", "RenderState"),
836 35 => ("Wrap4", "RenderState"),
837 36 => ("Wrap5", "RenderState"),
838 37 => ("Wrap6", "RenderState"),
839 38 => ("Wrap7", "RenderState"),
840 39 => ("Clipping", "RenderState"),
841 40 => ("Lighting", "RenderState"),
842 41 => ("Ambient", "RenderState"),
843 42 => ("FogVertexMode", "RenderState"),
844 43 => ("ColorVertex", "RenderState"),
845 44 => ("LocalViewer", "RenderState"),
846 45 => ("NormalizeNormals", "RenderState"),
847 46 => ("DiffuseMaterialSource", "RenderState"),
848 47 => ("SpecularMaterialSource", "RenderState"),
849 48 => ("AmbientMaterialSource", "RenderState"),
850 49 => ("EmissiveMaterialSource", "RenderState"),
851 50 => ("VertexBlend", "RenderState"),
852 51 => ("ClipPlaneEnable", "RenderState"),
853 52 => ("PointSize", "RenderState"),
854 53 => ("PointSizeMin", "RenderState"),
855 54 => ("PointSpriteEnable", "RenderState"),
856 55 => ("PointScaleEnable", "RenderState"),
857 56 => ("PointScaleA", "RenderState"),
858 57 => ("PointScaleB", "RenderState"),
859 58 => ("PointScaleC", "RenderState"),
860 59 => ("MultiSampleAntiAlias", "RenderState"),
861 60 => ("MultiSampleMask", "RenderState"),
862 61 => ("PatchEdgeStyle", "RenderState"),
863 62 => ("DebugMonitorToken", "RenderState"),
864 63 => ("PointSizeMax", "RenderState"),
865 64 => ("IndexedVertexBlendEnable", "RenderState"),
866 65 => ("ColorWriteEnable", "RenderState"),
867 66 => ("TweenFactor", "RenderState"),
868 67 => ("BlendOp", "RenderState"),
869 68 => ("PositionDegree", "RenderState"),
870 69 => ("NormalDegree", "RenderState"),
871 70 => ("ScissorTestEnable", "RenderState"),
872 71 => ("SlopeScaleDepthBias", "RenderState"),
873 72 => ("AntiAliasedLineEnable", "RenderState"),
874 73 => ("MinTessellationLevel", "RenderState"),
875 74 => ("MaxTessellationLevel", "RenderState"),
876 75 => ("AdaptiveTessX", "RenderState"),
877 76 => ("AdaptiveTessY", "RenderState"),
878 77 => ("AdaptiveTessZ", "RenderState"),
879 78 => ("AdaptiveTessW", "RenderState"),
880 79 => ("EnableAdaptiveTessellation", "RenderState"),
881 80 => ("TwoSidedStencilMode", "RenderState"),
882 81 => ("CCWStencilFail", "RenderState"),
883 82 => ("CCWStencilZFail", "RenderState"),
884 83 => ("CCWStencilPass", "RenderState"),
885 84 => ("CCWStencilFunc", "RenderState"),
886 85 => ("ColorWriteEnable1", "RenderState"),
887 86 => ("ColorWriteEnable2", "RenderState"),
888 87 => ("ColorWriteEnable3", "RenderState"),
889 88 => ("BlendFactor", "RenderState"),
890 89 => ("SRGBWriteEnable", "RenderState"),
891 90 => ("DepthBias", "RenderState"),
892 91 => ("Wrap8", "RenderState"),
893 92 => ("Wrap9", "RenderState"),
894 93 => ("Wrap10", "RenderState"),
895 94 => ("Wrap11", "RenderState"),
896 95 => ("Wrap12", "RenderState"),
897 96 => ("Wrap13", "RenderState"),
898 97 => ("Wrap14", "RenderState"),
899 98 => ("Wrap15", "RenderState"),
900 99 => ("SeparateAlphaBlendEnable", "RenderState"),
901 100 => ("SrcBlendAlpha", "RenderState"),
902 101 => ("DestBlendAlpha", "RenderState"),
903 102 => ("BlendOpAlpha", "RenderState"),
904 146 => ("VertexShader", "VertexShader"),
905 147 => ("PixelShader", "PixelShader"),
906 _ => ("UnknownState", "Unknown"),
907 }
908}
909
910fn read_u32_at(data: &[u8], off: usize) -> Result<u32, String> {
911 let b = data
912 .get(off..off + 4)
913 .ok_or_else(|| format!("truncated dword at 0x{off:x}"))?;
914 Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
915}
916
917fn checked_base_offset(base: usize, offset: u32) -> Result<usize, String> {
918 base.checked_add(offset as usize)
919 .ok_or_else(|| format!("offset overflow: base=0x{base:x} offset=0x{offset:x}"))
920}
921
922fn read_name_at(data: &[u8], base: usize, offset: u32) -> Result<String, String> {
923 if offset == 0 {
924 return Ok(String::new());
925 }
926 let pos = checked_base_offset(base, offset)?;
927 let len = read_u32_at(data, pos)? as usize;
928 let start = pos + 4;
929 let end = start
930 .checked_add(len)
931 .ok_or_else(|| "name length overflow".to_string())?;
932 let bytes = data
933 .get(start..end)
934 .ok_or_else(|| format!("name outside file at 0x{pos:x} len={len}"))?;
935 let nul = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
936 Ok(String::from_utf8_lossy(&bytes[..nul]).to_string())
937}
938
939fn read_nul_string(data: &[u8], pos: usize) -> Result<String, String> {
940 let bytes = data
941 .get(pos..)
942 .ok_or_else(|| format!("string offset outside file: 0x{pos:x}"))?;
943 let nul = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
944 Ok(String::from_utf8_lossy(&bytes[..nul]).to_string())
945}
946
947fn align4(v: usize) -> usize {
948 (v + 3) & !3
949}
950
951pub fn safe_name(name: &str, fallback: &str) -> String {
952 let mut out = String::new();
953 for ch in name.chars() {
954 if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' || ch == '.' {
955 out.push(ch);
956 } else if ch.is_whitespace() {
957 out.push('_');
958 }
959 }
960 if out.is_empty() {
961 fallback.to_string()
962 } else {
963 out
964 }
965}
966
967pub fn format_effect_map(effect: &EffectFile, shaders: &[ShaderBlob]) -> String {
968 let mut s = String::new();
969 let _ = writeln!(s, "tag: 0x{:08x}", effect.tag);
970 let _ = writeln!(s, "table_base: 0x{:08x}", effect.table_base);
971 let _ = writeln!(s, "start_offset: 0x{:08x}", effect.start_offset);
972 let _ = writeln!(s, "parameters: {}", effect.parameter_count);
973 let _ = writeln!(s, "techniques: {}", effect.technique_count);
974 let _ = writeln!(s, "objects: {}", effect.object_count);
975 let _ = writeln!(s, "strings: {}", effect.string_count);
976 let _ = writeln!(s, "resources: {}", effect.resource_count);
977 if !effect.notes.is_empty() {
978 let _ = writeln!(s, "notes:");
979 for note in &effect.notes {
980 let _ = writeln!(s, " - {note}");
981 }
982 }
983 let _ = writeln!(s);
984 let _ = writeln!(s, "top_level_parameters:");
985 for p in &effect.parameters {
986 let _ = writeln!(
987 s,
988 " [{}] {} type={} class={} rows={} cols={} elems={} bytes={} object_id={:?} semantic={}",
989 p.index,
990 p.name,
991 p.value_type,
992 p.class,
993 p.rows,
994 p.columns,
995 p.elements,
996 p.bytes,
997 p.object_id,
998 p.semantic
999 );
1000 }
1001 let _ = writeln!(s);
1002 let _ = writeln!(s, "objects:");
1003 for o in &effect.objects {
1004 let _ = writeln!(
1005 s,
1006 " [{}] owner={:?} type={:?} offset={} size={}",
1007 o.id,
1008 o.owner_name,
1009 o.owner_type,
1010 o.data_offset
1011 .map(|v| format!("0x{v:08x}"))
1012 .unwrap_or_else(|| "none".to_string()),
1013 o.size
1014 );
1015 }
1016 let _ = writeln!(s);
1017 let _ = writeln!(s, "shader_blobs:");
1018 for sh in shaders {
1019 let _ = writeln!(
1020 s,
1021 " [{}] {} offset=0x{:08x} size={} ctab={}",
1022 sh.index,
1023 sh.profile(),
1024 sh.offset,
1025 sh.bytes.len(),
1026 sh.ctab.is_some()
1027 );
1028 }
1029 let _ = writeln!(s);
1030 let _ = writeln!(s, "techniques:");
1031 for tech in &effect.techniques {
1032 let _ = writeln!(
1033 s,
1034 "technique [{}] {} passes={}",
1035 tech.index,
1036 tech.name,
1037 tech.passes.len()
1038 );
1039 for pass in &tech.passes {
1040 let _ = writeln!(
1041 s,
1042 " pass [{}] {} states={}",
1043 pass.index,
1044 pass.name,
1045 pass.states.len()
1046 );
1047 if let Some(vs) = &pass.vertex_shader {
1048 write_shader_ref(&mut s, " VS", vs);
1049 }
1050 if let Some(ps) = &pass.pixel_shader {
1051 write_shader_ref(&mut s, " PS", ps);
1052 }
1053 for state in &pass.states {
1054 let _ = writeln!(
1055 s,
1056 " state [{}] op={} {} class={} index={} type={} param={} usage={:?} value={}",
1057 state.index,
1058 state.operation,
1059 state.operation_name,
1060 state.class_name,
1061 state.state_index,
1062 state.parameter.value_type,
1063 state.parameter.name,
1064 state.resource_usage,
1065 format_state_value(&state.value)
1066 );
1067 }
1068 }
1069 }
1070 s
1071}
1072
1073fn write_shader_ref(s: &mut String, label: &str, sr: &ShaderRef) {
1074 let _ = writeln!(
1075 s,
1076 "{} object_id={:?} object_offset={} object_size={} shader_index={:?} shader_offset={} unresolved={:?}",
1077 label,
1078 sr.object_id,
1079 sr.object_data_offset
1080 .map(|v| format!("0x{v:08x}"))
1081 .unwrap_or_else(|| "none".to_string()),
1082 sr.object_size,
1083 sr.shader_index,
1084 sr.shader_offset
1085 .map(|v| format!("0x{v:08x}"))
1086 .unwrap_or_else(|| "none".to_string()),
1087 sr.unresolved_reason
1088 );
1089}
1090
1091fn format_state_value(v: &StateValue) -> String {
1092 match v {
1093 StateValue::Empty => "empty".to_string(),
1094 StateValue::ObjectId(id) => format!("object_id({id})"),
1095 StateValue::Int(xs) => format!("int{:?}", xs),
1096 StateValue::Float(xs) => format!("float{:?}", xs),
1097 StateValue::Bool(xs) => format!("bool{:?}", xs),
1098 StateValue::StringObject { object_id, text } => {
1099 format!("string_object({object_id}, {:?})", text)
1100 }
1101 StateValue::Raw { offset, bytes } => format!("raw(offset=0x{offset:x}, bytes={bytes})"),
1102 }
1103}
1104
1105pub fn used_shader_indices(effect: &EffectFile) -> BTreeSet<usize> {
1106 let mut out = BTreeSet::new();
1107 for tech in &effect.techniques {
1108 for pass in &tech.passes {
1109 if let Some(vs) = &pass.vertex_shader
1110 && let Some(i) = vs.shader_index
1111 {
1112 out.insert(i);
1113 }
1114 if let Some(ps) = &pass.pixel_shader
1115 && let Some(i) = ps.shader_index
1116 {
1117 out.insert(i);
1118 }
1119 }
1120 }
1121 out
1122}
1123
1124pub fn technique_shader_outputs<'a>(
1125 effect: &'a EffectFile,
1126 shaders: &'a [ShaderBlob],
1127) -> Vec<(String, &'a ShaderBlob)> {
1128 let by_index: BTreeMap<usize, &ShaderBlob> = shaders.iter().map(|s| (s.index, s)).collect();
1129 let mut out = Vec::new();
1130 for tech in &effect.techniques {
1131 let tech_name = safe_name(&tech.name, &format!("technique_{}", tech.index));
1132 for pass in &tech.passes {
1133 let pass_name = safe_name(&pass.name, &format!("pass{}", pass.index));
1134 let prefix_base = format!(
1135 "t{:04}_{}__p{:02}_{}",
1136 tech.index, tech_name, pass.index, pass_name
1137 );
1138 if let Some(vs) = &pass.vertex_shader
1139 && let Some(idx) = vs.shader_index
1140 && let Some(blob) = by_index.get(&idx)
1141 {
1142 out.push((format!("{}__vs", prefix_base), *blob));
1143 }
1144 if let Some(ps) = &pass.pixel_shader
1145 && let Some(idx) = ps.shader_index
1146 && let Some(blob) = by_index.get(&idx)
1147 {
1148 out.push((format!("{}__ps", prefix_base), *blob));
1149 }
1150 }
1151 }
1152 out
1153}
1154
1155pub fn write_outputs_for_blob(
1156 out_dir: &Path,
1157 prefix: &str,
1158 blob: &ShaderBlob,
1159 hlsl: &str,
1160 asm: &str,
1161 ctab_text: Option<&str>,
1162) -> std::io::Result<()> {
1163 std::fs::write(
1164 out_dir.join("bytecode").join(format!("{prefix}.bin")),
1165 &blob.bytes,
1166 )?;
1167 std::fs::write(out_dir.join("hlsl").join(format!("{prefix}.hlsl")), hlsl)?;
1168 std::fs::write(out_dir.join("asm").join(format!("{prefix}.asm")), asm)?;
1169 if let Some(ctab) = ctab_text {
1170 std::fs::write(
1171 out_dir.join("hlsl").join(format!("{prefix}.ctab.txt")),
1172 ctab,
1173 )?;
1174 }
1175 Ok(())
1176}