Skip to main content

siglus_cfx_decompiler/
disasm.rs

1use std::fmt;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4pub enum ShaderKind {
5    Vertex,
6    Pixel,
7}
8
9impl ShaderKind {
10    pub fn profile_prefix(self) -> &'static str {
11        match self {
12            ShaderKind::Vertex => "vs",
13            ShaderKind::Pixel => "ps",
14        }
15    }
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum Opcode {
20    Nop,
21    Mov,
22    Add,
23    Sub,
24    Mad,
25    Mul,
26    Rcp,
27    Rsq,
28    Dp3,
29    Dp4,
30    Min,
31    Max,
32    Slt,
33    Sge,
34    Exp,
35    Log,
36    Lit,
37    Dst,
38    Lrp,
39    Frc,
40    M4x4,
41    M4x3,
42    M3x4,
43    M3x3,
44    M3x2,
45    Call,
46    CallNZ,
47    Loop,
48    Ret,
49    EndLoop,
50    Label,
51    Dcl,
52    Pow,
53    Crs,
54    Sgn,
55    Abs,
56    Nrm,
57    SinCos,
58    Rep,
59    EndRep,
60    If,
61    IfC,
62    Else,
63    EndIf,
64    Break,
65    BreakC,
66    MovA,
67    DefB,
68    DefI,
69    TexCoord,
70    TexKill,
71    Tex,
72    TexBem,
73    TexBeml,
74    TexReg2AR,
75    TexReg2GB,
76    TexM3x2Pad,
77    TexM3x2Tex,
78    TexM3x3Pad,
79    TexM3x3Tex,
80    TexM3x3Diff,
81    TexM3x3Spec,
82    TexM3x3VSpec,
83    ExpP,
84    LogP,
85    Cnd,
86    Def,
87    TexReg2RGB,
88    TexDP3Tex,
89    TexM3x2Depth,
90    TexDP3,
91    TexM3x3,
92    TexDepth,
93    Cmp,
94    Bem,
95    Dp2Add,
96    Dsx,
97    Dsy,
98    TexLdd,
99    SetP,
100    TexLdl,
101    BreakP,
102    Phase,
103    Comment,
104    End,
105    Unknown(u16),
106}
107
108impl Opcode {
109    pub fn from_u16(v: u16) -> Self {
110        match v {
111            0 => Self::Nop,
112            1 => Self::Mov,
113            2 => Self::Add,
114            3 => Self::Sub,
115            4 => Self::Mad,
116            5 => Self::Mul,
117            6 => Self::Rcp,
118            7 => Self::Rsq,
119            8 => Self::Dp3,
120            9 => Self::Dp4,
121            10 => Self::Min,
122            11 => Self::Max,
123            12 => Self::Slt,
124            13 => Self::Sge,
125            14 => Self::Exp,
126            15 => Self::Log,
127            16 => Self::Lit,
128            17 => Self::Dst,
129            18 => Self::Lrp,
130            19 => Self::Frc,
131            20 => Self::M4x4,
132            21 => Self::M4x3,
133            22 => Self::M3x4,
134            23 => Self::M3x3,
135            24 => Self::M3x2,
136            25 => Self::Call,
137            26 => Self::CallNZ,
138            27 => Self::Loop,
139            28 => Self::Ret,
140            29 => Self::EndLoop,
141            30 => Self::Label,
142            31 => Self::Dcl,
143            32 => Self::Pow,
144            33 => Self::Crs,
145            34 => Self::Sgn,
146            35 => Self::Abs,
147            36 => Self::Nrm,
148            37 => Self::SinCos,
149            38 => Self::Rep,
150            39 => Self::EndRep,
151            40 => Self::If,
152            41 => Self::IfC,
153            42 => Self::Else,
154            43 => Self::EndIf,
155            44 => Self::Break,
156            45 => Self::BreakC,
157            46 => Self::MovA,
158            47 => Self::DefB,
159            48 => Self::DefI,
160            64 => Self::TexCoord,
161            65 => Self::TexKill,
162            66 => Self::Tex,
163            67 => Self::TexBem,
164            68 => Self::TexBeml,
165            69 => Self::TexReg2AR,
166            70 => Self::TexReg2GB,
167            71 => Self::TexM3x2Pad,
168            72 => Self::TexM3x2Tex,
169            73 => Self::TexM3x3Pad,
170            74 => Self::TexM3x3Tex,
171            75 => Self::TexM3x3Diff,
172            76 => Self::TexM3x3Spec,
173            77 => Self::TexM3x3VSpec,
174            78 => Self::ExpP,
175            79 => Self::LogP,
176            80 => Self::Cnd,
177            81 => Self::Def,
178            82 => Self::TexReg2RGB,
179            83 => Self::TexDP3Tex,
180            84 => Self::TexM3x2Depth,
181            85 => Self::TexDP3,
182            86 => Self::TexM3x3,
183            87 => Self::TexDepth,
184            88 => Self::Cmp,
185            89 => Self::Bem,
186            90 => Self::Dp2Add,
187            91 => Self::Dsx,
188            92 => Self::Dsy,
189            93 => Self::TexLdd,
190            94 => Self::SetP,
191            95 => Self::TexLdl,
192            96 => Self::BreakP,
193            0xfffd => Self::Phase,
194            0xfffe => Self::Comment,
195            0xffff => Self::End,
196            x => Self::Unknown(x),
197        }
198    }
199
200    pub fn code(self) -> u16 {
201        match self {
202            Self::Nop => 0,
203            Self::Mov => 1,
204            Self::Add => 2,
205            Self::Sub => 3,
206            Self::Mad => 4,
207            Self::Mul => 5,
208            Self::Rcp => 6,
209            Self::Rsq => 7,
210            Self::Dp3 => 8,
211            Self::Dp4 => 9,
212            Self::Min => 10,
213            Self::Max => 11,
214            Self::Slt => 12,
215            Self::Sge => 13,
216            Self::Exp => 14,
217            Self::Log => 15,
218            Self::Lit => 16,
219            Self::Dst => 17,
220            Self::Lrp => 18,
221            Self::Frc => 19,
222            Self::M4x4 => 20,
223            Self::M4x3 => 21,
224            Self::M3x4 => 22,
225            Self::M3x3 => 23,
226            Self::M3x2 => 24,
227            Self::Call => 25,
228            Self::CallNZ => 26,
229            Self::Loop => 27,
230            Self::Ret => 28,
231            Self::EndLoop => 29,
232            Self::Label => 30,
233            Self::Dcl => 31,
234            Self::Pow => 32,
235            Self::Crs => 33,
236            Self::Sgn => 34,
237            Self::Abs => 35,
238            Self::Nrm => 36,
239            Self::SinCos => 37,
240            Self::Rep => 38,
241            Self::EndRep => 39,
242            Self::If => 40,
243            Self::IfC => 41,
244            Self::Else => 42,
245            Self::EndIf => 43,
246            Self::Break => 44,
247            Self::BreakC => 45,
248            Self::MovA => 46,
249            Self::DefB => 47,
250            Self::DefI => 48,
251            Self::TexCoord => 64,
252            Self::TexKill => 65,
253            Self::Tex => 66,
254            Self::TexBem => 67,
255            Self::TexBeml => 68,
256            Self::TexReg2AR => 69,
257            Self::TexReg2GB => 70,
258            Self::TexM3x2Pad => 71,
259            Self::TexM3x2Tex => 72,
260            Self::TexM3x3Pad => 73,
261            Self::TexM3x3Tex => 74,
262            Self::TexM3x3Diff => 75,
263            Self::TexM3x3Spec => 76,
264            Self::TexM3x3VSpec => 77,
265            Self::ExpP => 78,
266            Self::LogP => 79,
267            Self::Cnd => 80,
268            Self::Def => 81,
269            Self::TexReg2RGB => 82,
270            Self::TexDP3Tex => 83,
271            Self::TexM3x2Depth => 84,
272            Self::TexDP3 => 85,
273            Self::TexM3x3 => 86,
274            Self::TexDepth => 87,
275            Self::Cmp => 88,
276            Self::Bem => 89,
277            Self::Dp2Add => 90,
278            Self::Dsx => 91,
279            Self::Dsy => 92,
280            Self::TexLdd => 93,
281            Self::SetP => 94,
282            Self::TexLdl => 95,
283            Self::BreakP => 96,
284            Self::Phase => 0xfffd,
285            Self::Comment => 0xfffe,
286            Self::End => 0xffff,
287            Self::Unknown(x) => x,
288        }
289    }
290
291    pub fn mnemonic(self) -> &'static str {
292        match self {
293            Self::Nop => "nop",
294            Self::Mov => "mov",
295            Self::Add => "add",
296            Self::Sub => "sub",
297            Self::Mad => "mad",
298            Self::Mul => "mul",
299            Self::Rcp => "rcp",
300            Self::Rsq => "rsq",
301            Self::Dp3 => "dp3",
302            Self::Dp4 => "dp4",
303            Self::Min => "min",
304            Self::Max => "max",
305            Self::Slt => "slt",
306            Self::Sge => "sge",
307            Self::Exp => "exp",
308            Self::Log => "log",
309            Self::Lit => "lit",
310            Self::Dst => "dst",
311            Self::Lrp => "lrp",
312            Self::Frc => "frc",
313            Self::M4x4 => "m4x4",
314            Self::M4x3 => "m4x3",
315            Self::M3x4 => "m3x4",
316            Self::M3x3 => "m3x3",
317            Self::M3x2 => "m3x2",
318            Self::Call => "call",
319            Self::CallNZ => "callnz",
320            Self::Loop => "loop",
321            Self::Ret => "ret",
322            Self::EndLoop => "endloop",
323            Self::Label => "label",
324            Self::Dcl => "dcl",
325            Self::Pow => "pow",
326            Self::Crs => "crs",
327            Self::Sgn => "sgn",
328            Self::Abs => "abs",
329            Self::Nrm => "nrm",
330            Self::SinCos => "sincos",
331            Self::Rep => "rep",
332            Self::EndRep => "endrep",
333            Self::If => "if",
334            Self::IfC => "ifc",
335            Self::Else => "else",
336            Self::EndIf => "endif",
337            Self::Break => "break",
338            Self::BreakC => "breakc",
339            Self::MovA => "mova",
340            Self::DefB => "defb",
341            Self::DefI => "defi",
342            Self::TexCoord => "texcoord",
343            Self::TexKill => "texkill",
344            Self::Tex => "texld",
345            Self::TexBem => "texbem",
346            Self::TexBeml => "texbeml",
347            Self::TexReg2AR => "texreg2ar",
348            Self::TexReg2GB => "texreg2gb",
349            Self::TexM3x2Pad => "texm3x2pad",
350            Self::TexM3x2Tex => "texm3x2tex",
351            Self::TexM3x3Pad => "texm3x3pad",
352            Self::TexM3x3Tex => "texm3x3tex",
353            Self::TexM3x3Diff => "texm3x3diff",
354            Self::TexM3x3Spec => "texm3x3spec",
355            Self::TexM3x3VSpec => "texm3x3vspec",
356            Self::ExpP => "expp",
357            Self::LogP => "logp",
358            Self::Cnd => "cnd",
359            Self::Def => "def",
360            Self::TexReg2RGB => "texreg2rgb",
361            Self::TexDP3Tex => "texdp3tex",
362            Self::TexM3x2Depth => "texm3x2depth",
363            Self::TexDP3 => "texdp3",
364            Self::TexM3x3 => "texm3x3",
365            Self::TexDepth => "texdepth",
366            Self::Cmp => "cmp",
367            Self::Bem => "bem",
368            Self::Dp2Add => "dp2add",
369            Self::Dsx => "dsx",
370            Self::Dsy => "dsy",
371            Self::TexLdd => "texldd",
372            Self::SetP => "setp",
373            Self::TexLdl => "texldl",
374            Self::BreakP => "breakp",
375            Self::Phase => "phase",
376            Self::Comment => "comment",
377            Self::End => "end",
378            Self::Unknown(_) => "unknown",
379        }
380    }
381
382    pub fn has_destination(self) -> bool {
383        matches!(
384            self,
385            Self::Abs
386                | Self::Add
387                | Self::Bem
388                | Self::Cmp
389                | Self::Cnd
390                | Self::Crs
391                | Self::Dcl
392                | Self::Def
393                | Self::DefB
394                | Self::DefI
395                | Self::Dp2Add
396                | Self::Dp3
397                | Self::Dp4
398                | Self::Dst
399                | Self::Dsx
400                | Self::Dsy
401                | Self::Exp
402                | Self::ExpP
403                | Self::Frc
404                | Self::Lit
405                | Self::Log
406                | Self::LogP
407                | Self::Lrp
408                | Self::M3x2
409                | Self::M3x3
410                | Self::M3x4
411                | Self::M4x3
412                | Self::M4x4
413                | Self::Mad
414                | Self::Max
415                | Self::Min
416                | Self::Mov
417                | Self::MovA
418                | Self::Mul
419                | Self::Nrm
420                | Self::Pow
421                | Self::Rcp
422                | Self::Rsq
423                | Self::SetP
424                | Self::Sge
425                | Self::Sgn
426                | Self::SinCos
427                | Self::Slt
428                | Self::Sub
429                | Self::Tex
430                | Self::TexBem
431                | Self::TexBeml
432                | Self::TexCoord
433                | Self::TexDepth
434                | Self::TexDP3
435                | Self::TexDP3Tex
436                | Self::TexKill
437                | Self::TexLdd
438                | Self::TexLdl
439                | Self::TexM3x2Depth
440                | Self::TexM3x2Pad
441                | Self::TexM3x2Tex
442                | Self::TexM3x3
443                | Self::TexM3x3Diff
444                | Self::TexM3x3Pad
445                | Self::TexM3x3Spec
446                | Self::TexM3x3Tex
447                | Self::TexM3x3VSpec
448                | Self::TexReg2AR
449                | Self::TexReg2GB
450                | Self::TexReg2RGB
451        )
452    }
453}
454
455#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
456pub enum RegisterType {
457    Temp,
458    Input,
459    Const,
460    Texture,
461    RastOut,
462    AttrOut,
463    Output,
464    ConstInt,
465    ColorOut,
466    DepthOut,
467    Sampler,
468    Const2,
469    Const3,
470    Const4,
471    ConstBool,
472    Loop,
473    TempFloat16,
474    MiscType,
475    Label,
476    Predicate,
477    Unknown(u8),
478}
479
480impl RegisterType {
481    pub fn from_param_token(token: u32) -> Self {
482        let raw = (((token >> 28) & 0x7) | ((token >> 8) & 0x18)) as u8;
483        match raw {
484            0 => Self::Temp,
485            1 => Self::Input,
486            2 => Self::Const,
487            3 => Self::Texture,
488            4 => Self::RastOut,
489            5 => Self::AttrOut,
490            6 => Self::Output,
491            7 => Self::ConstInt,
492            8 => Self::ColorOut,
493            9 => Self::DepthOut,
494            10 => Self::Sampler,
495            11 => Self::Const2,
496            12 => Self::Const3,
497            13 => Self::Const4,
498            14 => Self::ConstBool,
499            15 => Self::Loop,
500            16 => Self::TempFloat16,
501            17 => Self::MiscType,
502            18 => Self::Label,
503            19 => Self::Predicate,
504            x => Self::Unknown(x),
505        }
506    }
507
508    pub fn asm_prefix(self) -> &'static str {
509        match self {
510            Self::Temp => "r",
511            Self::Input => "v",
512            Self::Const => "c",
513            Self::Texture => "t",
514            Self::RastOut => "o",
515            Self::AttrOut => "oD",
516            Self::Output => "o",
517            Self::ConstInt => "i",
518            Self::ColorOut => "oC",
519            Self::DepthOut => "oDepth",
520            Self::Sampler => "s",
521            Self::ConstBool => "b",
522            Self::Loop => "aL",
523            Self::TempFloat16 => "r",
524            Self::MiscType => "v",
525            Self::Label => "l",
526            Self::Predicate => "p",
527            Self::Const2 | Self::Const3 | Self::Const4 => "c",
528            Self::Unknown(_) => "u",
529        }
530    }
531}
532
533#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
534pub struct RegisterKey {
535    pub ty: RegisterType,
536    pub number: u16,
537}
538
539#[derive(Debug, Clone, Copy, PartialEq, Eq)]
540pub enum SourceModifier {
541    None,
542    Negate,
543    Bias,
544    BiasAndNegate,
545    Sign,
546    SignAndNegate,
547    Complement,
548    X2,
549    X2AndNegate,
550    DivideByZ,
551    DivideByW,
552    Abs,
553    AbsAndNegate,
554    Not,
555    Unknown(u8),
556}
557
558impl SourceModifier {
559    pub fn from_param_token(token: u32) -> Self {
560        match ((token >> 24) & 0xf) as u8 {
561            0 => Self::None,
562            1 => Self::Negate,
563            2 => Self::Bias,
564            3 => Self::BiasAndNegate,
565            4 => Self::Sign,
566            5 => Self::SignAndNegate,
567            6 => Self::Complement,
568            7 => Self::X2,
569            8 => Self::X2AndNegate,
570            9 => Self::DivideByZ,
571            10 => Self::DivideByW,
572            11 => Self::Abs,
573            12 => Self::AbsAndNegate,
574            13 => Self::Not,
575            x => Self::Unknown(x),
576        }
577    }
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Eq)]
581pub struct ResultModifier {
582    pub saturate: bool,
583    pub partial_precision: bool,
584    pub centroid: bool,
585    pub raw: u8,
586}
587
588impl ResultModifier {
589    pub fn from_dest_token(token: u32) -> Self {
590        let raw = ((token >> 20) & 0xf) as u8;
591        Self {
592            saturate: (raw & 1) != 0,
593            partial_precision: (raw & 2) != 0,
594            centroid: (raw & 4) != 0,
595            raw,
596        }
597    }
598}
599
600#[derive(Debug, Clone, Copy, PartialEq, Eq)]
601pub enum SamplerTextureType {
602    Unknown,
603    TwoD,
604    Cube,
605    Volume,
606}
607
608impl SamplerTextureType {
609    pub fn from_decl_token(token: u32) -> Self {
610        match ((token >> 27) & 0xf) as u8 {
611            2 => Self::TwoD,
612            3 => Self::Cube,
613            4 => Self::Volume,
614            _ => Self::Unknown,
615        }
616    }
617
618    pub fn asm_name(self) -> &'static str {
619        match self {
620            Self::TwoD => "2d",
621            Self::Cube => "cube",
622            Self::Volume => "volume",
623            Self::Unknown => "unknown",
624        }
625    }
626
627    pub fn hlsl_dim(self) -> usize {
628        match self {
629            Self::Cube | Self::Volume => 3,
630            Self::TwoD | Self::Unknown => 2,
631        }
632    }
633}
634
635#[derive(Debug, Clone, Copy, PartialEq, Eq)]
636pub enum DeclUsage {
637    Position,
638    BlendWeight,
639    BlendIndices,
640    Normal,
641    PSize,
642    TexCoord,
643    Tangent,
644    Binormal,
645    TessFactor,
646    PositionT,
647    Color,
648    Fog,
649    Depth,
650    Sample,
651    Unknown(u8),
652}
653
654impl DeclUsage {
655    pub fn from_decl_token(token: u32) -> Self {
656        match (token & 0x1f) as u8 {
657            0 => Self::Position,
658            1 => Self::BlendWeight,
659            2 => Self::BlendIndices,
660            3 => Self::Normal,
661            4 => Self::PSize,
662            5 => Self::TexCoord,
663            6 => Self::Tangent,
664            7 => Self::Binormal,
665            8 => Self::TessFactor,
666            9 => Self::PositionT,
667            10 => Self::Color,
668            11 => Self::Fog,
669            12 => Self::Depth,
670            13 => Self::Sample,
671            x => Self::Unknown(x),
672        }
673    }
674
675    pub fn semantic_prefix(self) -> &'static str {
676        match self {
677            Self::Position => "POSITION",
678            Self::BlendWeight => "BLENDWEIGHT",
679            Self::BlendIndices => "BLENDINDICES",
680            Self::Normal => "NORMAL",
681            Self::PSize => "PSIZE",
682            Self::TexCoord => "TEXCOORD",
683            Self::Tangent => "TANGENT",
684            Self::Binormal => "BINORMAL",
685            Self::TessFactor => "TESSFACTOR",
686            Self::PositionT => "POSITIONT",
687            Self::Color => "COLOR",
688            Self::Fog => "FOG",
689            Self::Depth => "DEPTH",
690            Self::Sample => "SAMPLE",
691            Self::Unknown(_) => "TEXCOORD",
692        }
693    }
694}
695
696#[derive(Debug, Clone)]
697pub struct Instruction {
698    pub offset: usize,
699    pub token: u32,
700    pub opcode: Opcode,
701    pub params: Vec<u32>,
702}
703
704impl Instruction {
705    pub fn comparison(self: &Self) -> u8 {
706        ((self.token >> 16) & 7) as u8
707    }
708
709    pub fn texld_controls(self: &Self) -> u8 {
710        ((self.token >> 16) & 3) as u8
711    }
712
713    pub fn dest_param_index(&self) -> usize {
714        if self.opcode == Opcode::Dcl { 1 } else { 0 }
715    }
716
717    pub fn dest_token(&self) -> Option<u32> {
718        self.params.get(self.dest_param_index()).copied()
719    }
720
721    pub fn dest_register(&self) -> Option<RegisterKey> {
722        let t = self.dest_token()?;
723        Some(RegisterKey { ty: RegisterType::from_param_token(t), number: (t & 0x7ff) as u16 })
724    }
725
726    pub fn dest_write_mask(&self) -> u8 {
727        let Some(t) = self.dest_token() else { return 0xf; };
728        let mask = ((t >> 16) & 0xf) as u8;
729        if mask == 0 { 0xf } else { mask }
730    }
731
732    pub fn dest_modifier(&self) -> ResultModifier {
733        ResultModifier::from_dest_token(self.dest_token().unwrap_or(0))
734    }
735
736    pub fn source_register(&self, param_index: usize) -> Option<RegisterKey> {
737        let t = *self.params.get(param_index)?;
738        Some(RegisterKey { ty: RegisterType::from_param_token(t), number: (t & 0x7ff) as u16 })
739    }
740
741    pub fn source_modifier(&self, param_index: usize) -> SourceModifier {
742        self.params.get(param_index).copied().map(SourceModifier::from_param_token).unwrap_or(SourceModifier::None)
743    }
744
745    pub fn source_swizzle(&self, param_index: usize) -> [usize; 4] {
746        let t = self.params.get(param_index).copied().unwrap_or(0);
747        let swz = ((t >> 16) & 0xff) as usize;
748        [swz & 3, (swz >> 2) & 3, (swz >> 4) & 3, (swz >> 6) & 3]
749    }
750
751    pub fn decl_usage(&self) -> DeclUsage {
752        self.params.first().copied().map(DeclUsage::from_decl_token).unwrap_or(DeclUsage::TexCoord)
753    }
754
755    pub fn decl_index(&self) -> u8 {
756        self.params.first().map(|v| ((v >> 16) & 0x0f) as u8).unwrap_or(0)
757    }
758
759    pub fn decl_sampler_type(&self) -> SamplerTextureType {
760        self.params.first().copied().map(SamplerTextureType::from_decl_token).unwrap_or(SamplerTextureType::Unknown)
761    }
762
763    pub fn get_float_param(&self, index: usize) -> f32 {
764        f32::from_bits(*self.params.get(index).unwrap_or(&0))
765    }
766
767    pub fn get_int_param(&self, index: usize) -> i32 {
768        *self.params.get(index).unwrap_or(&0) as i32
769    }
770}
771
772#[derive(Debug, Clone)]
773pub struct ShaderModel {
774    pub kind: ShaderKind,
775    pub major: u8,
776    pub minor: u8,
777    pub instructions: Vec<Instruction>,
778}
779
780#[derive(Debug, Clone)]
781pub enum ShaderParseError {
782    TooShort,
783    BadVersion(u32),
784    Truncated { offset: usize, need: usize, len: usize },
785    MissingEnd,
786}
787
788impl fmt::Display for ShaderParseError {
789    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
790        match self {
791            Self::TooShort => write!(f, "shader bytecode is too short"),
792            Self::BadVersion(v) => write!(f, "unsupported shader version token 0x{v:08x}"),
793            Self::Truncated { offset, need, len } => write!(f, "truncated shader token stream at 0x{offset:x}: need {need} bytes, len {len}"),
794            Self::MissingEnd => write!(f, "shader token stream has no END token"),
795        }
796    }
797}
798
799impl std::error::Error for ShaderParseError {}
800
801fn read_u32(data: &[u8], off: usize) -> Result<u32, ShaderParseError> {
802    let b = data.get(off..off + 4).ok_or(ShaderParseError::Truncated { offset: off, need: 4, len: data.len() })?;
803    Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
804}
805
806pub fn parse_shader(data: &[u8]) -> Result<ShaderModel, ShaderParseError> {
807    if data.len() < 4 {
808        return Err(ShaderParseError::TooShort);
809    }
810    let version = read_u32(data, 0)?;
811    let minor = (version & 0xff) as u8;
812    let major = ((version >> 8) & 0xff) as u8;
813    let kind = match version & 0xffff_0000 {
814        0xfffe_0000 => ShaderKind::Vertex,
815        0xffff_0000 => ShaderKind::Pixel,
816        _ => return Err(ShaderParseError::BadVersion(version)),
817    };
818
819    let mut instructions = Vec::new();
820    let mut off = 4usize;
821    while off + 4 <= data.len() {
822        let token = read_u32(data, off)?;
823        let opcode = Opcode::from_u16((token & 0xffff) as u16);
824        let param_count = if opcode == Opcode::Comment {
825            ((token >> 16) & 0x7fff) as usize
826        } else if major == 1 {
827            fixed_size_param_count(opcode)
828        } else {
829            ((token >> 24) & 0x0f) as usize
830        };
831        let params_start = off + 4;
832        let params_bytes = param_count.saturating_mul(4);
833        if params_start + params_bytes > data.len() {
834            return Err(ShaderParseError::Truncated { offset: off, need: 4 + params_bytes, len: data.len() });
835        }
836        let mut params = Vec::with_capacity(param_count);
837        for p in 0..param_count {
838            params.push(read_u32(data, params_start + p * 4)?);
839        }
840        instructions.push(Instruction { offset: off, token, opcode, params });
841        off = params_start + params_bytes;
842        if opcode == Opcode::End {
843            return Ok(ShaderModel { kind, major, minor, instructions });
844        }
845    }
846
847    Err(ShaderParseError::MissingEnd)
848}
849
850fn fixed_size_param_count(opcode: Opcode) -> usize {
851    match opcode {
852        Opcode::Comment => 0,
853        Opcode::Def => 5,
854        Opcode::TexCoord | Opcode::TexKill | Opcode::Tex | Opcode::TexBem | Opcode::TexBeml | Opcode::TexReg2AR | Opcode::TexReg2GB | Opcode::TexM3x2Pad | Opcode::TexM3x2Tex | Opcode::TexM3x3Pad | Opcode::TexM3x3Tex | Opcode::TexM3x3Diff | Opcode::TexM3x3Spec | Opcode::TexM3x3VSpec | Opcode::TexReg2RGB | Opcode::TexDP3Tex | Opcode::TexM3x2Depth | Opcode::TexDP3 | Opcode::TexM3x3 | Opcode::TexDepth => 2,
855        Opcode::Dcl => 2,
856        Opcode::End | Opcode::Nop | Opcode::Phase | Opcode::Ret | Opcode::Else | Opcode::EndIf | Opcode::EndLoop | Opcode::EndRep | Opcode::Break => 0,
857        _ if opcode.has_destination() => 1 + num_inputs(opcode),
858        _ => num_inputs(opcode),
859    }
860}
861
862pub fn num_inputs(opcode: Opcode) -> usize {
863    match opcode {
864        Opcode::Abs | Opcode::CallNZ | Opcode::Dsx | Opcode::Dsy | Opcode::Exp | Opcode::ExpP | Opcode::Frc | Opcode::Lit | Opcode::Log | Opcode::LogP | Opcode::Loop | Opcode::Mov | Opcode::MovA | Opcode::Nrm | Opcode::Rcp | Opcode::Rsq | Opcode::SinCos | Opcode::TexKill | Opcode::If | Opcode::Rep => 1,
865        Opcode::Add | Opcode::Bem | Opcode::Crs | Opcode::Dp3 | Opcode::Dp4 | Opcode::Dst | Opcode::M3x2 | Opcode::M3x3 | Opcode::M3x4 | Opcode::M4x3 | Opcode::M4x4 | Opcode::Max | Opcode::Min | Opcode::Mul | Opcode::Pow | Opcode::SetP | Opcode::Sge | Opcode::Slt | Opcode::Sub | Opcode::Tex | Opcode::TexLdd | Opcode::TexLdl | Opcode::BreakC | Opcode::IfC => 2,
866        Opcode::Cmp | Opcode::Cnd | Opcode::Dp2Add | Opcode::Lrp | Opcode::Mad | Opcode::Sgn => 3,
867        _ => 0,
868    }
869}
870
871pub fn mask_len(mask: u8) -> usize {
872    (0..4).filter(|i| (mask & (1 << i)) != 0).count().max(1)
873}
874
875pub fn mask_suffix(mask: u8) -> String {
876    let m = if mask == 0 { 0xf } else { mask };
877    if m == 0xf {
878        String::new()
879    } else {
880        let mut s = String::from(".");
881        for (i, c) in ['x', 'y', 'z', 'w'].iter().enumerate() {
882            if (m & (1 << i)) != 0 {
883                s.push(*c);
884            }
885        }
886        s
887    }
888}
889
890pub fn swizzle_suffix(swizzle: [usize; 4], count: usize) -> String {
891    let count = count.clamp(1, 4);
892    let identity = [0usize, 1, 2, 3];
893    if count == 4 && swizzle == identity {
894        return String::new();
895    }
896    if count < 4 && swizzle[..count] == identity[..count] {
897        return String::new();
898    }
899    let names = ['x', 'y', 'z', 'w'];
900    let mut s = String::from(".");
901    for i in 0..count {
902        s.push(names[swizzle[i]]);
903    }
904    s
905}
906
907pub fn asm_register_name(reg: RegisterKey) -> String {
908    match reg.ty {
909        RegisterType::RastOut => match reg.number {
910            0 => "oPos".to_string(),
911            1 => "oFog".to_string(),
912            2 => "oPts".to_string(),
913            _ => format!("o{}", reg.number),
914        },
915        RegisterType::DepthOut => "oDepth".to_string(),
916        RegisterType::MiscType => match reg.number {
917            0 => "vPos".to_string(),
918            1 => "vFace".to_string(),
919            _ => format!("vMisc{}", reg.number),
920        },
921        _ => format!("{}{}", reg.ty.asm_prefix(), reg.number),
922    }
923}
924
925fn format_source_param(inst: &Instruction, param_index: usize, count: usize) -> String {
926    let Some(reg) = inst.source_register(param_index) else { return "<?>".to_string(); };
927    let base = asm_register_name(reg);
928    let swz = swizzle_suffix(inst.source_swizzle(param_index), count);
929    let value = format!("{base}{swz}");
930    match inst.source_modifier(param_index) {
931        SourceModifier::None => value,
932        SourceModifier::Negate => format!("-{value}"),
933        SourceModifier::Abs => format!("abs({value})"),
934        SourceModifier::AbsAndNegate => format!("-abs({value})"),
935        SourceModifier::Bias => format!("{value}_bias"),
936        SourceModifier::BiasAndNegate => format!("-{value}_bias"),
937        SourceModifier::Sign => format!("{value}_bx2"),
938        SourceModifier::SignAndNegate => format!("-{value}_bx2"),
939        SourceModifier::Complement => format!("1-{value}"),
940        SourceModifier::X2 => format!("{value}_x2"),
941        SourceModifier::X2AndNegate => format!("-{value}_x2"),
942        SourceModifier::DivideByZ => format!("{value}_dz"),
943        SourceModifier::DivideByW => format!("{value}_dw"),
944        SourceModifier::Not => format!("!{value}"),
945        SourceModifier::Unknown(_) => value,
946    }
947}
948
949fn format_dest_param(inst: &Instruction) -> String {
950    let Some(reg) = inst.dest_register() else { return "<?>".to_string(); };
951    format!("{}{}", asm_register_name(reg), mask_suffix(inst.dest_write_mask()))
952}
953
954fn format_decl(inst: &Instruction) -> String {
955    let Some(reg) = inst.dest_register() else { return "dcl".to_string(); };
956    if reg.ty == RegisterType::Sampler {
957        return format!("dcl_{} {}", inst.decl_sampler_type().asm_name(), asm_register_name(reg));
958    }
959    let mut sem = inst.decl_usage().semantic_prefix().to_ascii_lowercase();
960    let idx = inst.decl_index();
961    if idx != 0 || inst.decl_usage() == DeclUsage::TexCoord || inst.decl_usage() == DeclUsage::Color {
962        sem.push_str(&idx.to_string());
963    }
964    format!("dcl_{} {}", sem, asm_register_name(reg))
965}
966
967fn format_def(inst: &Instruction) -> String {
968    let Some(reg) = inst.dest_register() else { return "def".to_string(); };
969    match inst.opcode {
970        Opcode::Def => format!(
971            "def {}, {:.9}, {:.9}, {:.9}, {:.9}",
972            asm_register_name(reg),
973            inst.get_float_param(1),
974            inst.get_float_param(2),
975            inst.get_float_param(3),
976            inst.get_float_param(4)
977        ),
978        Opcode::DefI => format!(
979            "defi {}, {}, {}, {}, {}",
980            asm_register_name(reg),
981            inst.get_int_param(1),
982            inst.get_int_param(2),
983            inst.get_int_param(3),
984            inst.get_int_param(4)
985        ),
986        Opcode::DefB => format!("defb {}, {}", asm_register_name(reg), inst.get_int_param(1)),
987        _ => String::new(),
988    }
989}
990
991pub fn format_instruction_asm(inst: &Instruction) -> Option<String> {
992    match inst.opcode {
993        Opcode::Comment => None,
994        Opcode::End => Some("end".to_string()),
995        Opcode::Dcl => Some(format_decl(inst)),
996        Opcode::Def | Opcode::DefI | Opcode::DefB => Some(format_def(inst)),
997        Opcode::Nop => Some("nop".to_string()),
998        Opcode::If | Opcode::Rep | Opcode::Loop => Some(format!("{} {}", inst.opcode.mnemonic(), format_source_param(inst, 0, 1))),
999        Opcode::IfC | Opcode::BreakC => Some(format!("{} {}, {}", inst.opcode.mnemonic(), format_source_param(inst, 0, 4), format_source_param(inst, 1, 4))),
1000        Opcode::Else | Opcode::EndIf | Opcode::Break | Opcode::EndLoop | Opcode::EndRep | Opcode::Ret | Opcode::Phase => Some(inst.opcode.mnemonic().to_string()),
1001        _ if inst.opcode.has_destination() => {
1002            let dest = format_dest_param(inst);
1003            let mut parts = vec![dest];
1004            let first_src = if inst.opcode == Opcode::Dcl { 2 } else { 1 };
1005            let count = mask_len(inst.dest_write_mask());
1006            for i in first_src..inst.params.len() {
1007                parts.push(format_source_param(inst, i, count));
1008            }
1009            Some(format!("{} {}", inst.opcode.mnemonic(), parts.join(", ")))
1010        }
1011        _ => Some(inst.opcode.mnemonic().to_string()),
1012    }
1013}
1014
1015pub fn disassemble(data: &[u8]) -> String {
1016    match parse_shader(data) {
1017        Ok(shader) => disassemble_model(&shader),
1018        Err(_) => String::new(),
1019    }
1020}
1021
1022pub fn disassemble_model(shader: &ShaderModel) -> String {
1023    let mut out = String::new();
1024    out.push_str(&format!("{}_{}_{}\n", shader.kind.profile_prefix(), shader.major, shader.minor));
1025    for inst in &shader.instructions {
1026        if let Some(line) = format_instruction_asm(inst) {
1027            out.push_str(&line);
1028            out.push('\n');
1029        }
1030    }
1031    out
1032}