tjs2dec\decompile/
decode.rs

1use anyhow::{Result, bail};
2
3use crate::model::Tjs2Object;
4use crate::vmcodes::vm;
5
6/// A decoded instruction. `words[0]` is the opcode; the remaining entries are operands.
7#[derive(Debug, Clone)]
8pub struct Insn {
9    pub pc: usize,
10    pub op: i32,
11    pub size: usize,
12    pub words: Vec<i32>,
13}
14
15impl Insn {
16    pub fn mnemonic(&self) -> &'static str {
17        vm::name(self.op)
18    }
19
20    pub fn operands(&self) -> &[i32] {
21        &self.words[1..]
22    }
23}
24
25/// Decode an entire object's code area into a linear instruction list.
26pub fn decode_object(obj: &Tjs2Object) -> Result<Vec<Insn>> {
27    let mut out = Vec::new();
28    let code = &obj.code;
29    let mut pc: usize = 0;
30    while pc < code.len() {
31        let insn = decode_one(code, pc)?;
32        pc += insn.size;
33        out.push(insn);
34    }
35    Ok(out)
36}
37
38pub fn decode_one(code: &[i32], pc: usize) -> Result<Insn> {
39    if pc >= code.len() {
40        bail!("pc out of range: {} (len={})", pc, code.len());
41    }
42    let op = code[pc];
43
44    // Sizes are derived from the same patterns as `disasm.rs`.
45    let size: usize = if op == vm::VM_CONST {
46        3
47    } else if matches!(
48        op,
49        vm::VM_CP
50            | vm::VM_CEQ
51            | vm::VM_CDEQ
52            | vm::VM_CLT
53            | vm::VM_CGT
54            | vm::VM_CHKINS
55            | vm::VM_ADDCI
56            | vm::VM_CHGTHIS
57    ) {
58        3
59    } else if matches!(
60        op,
61        vm::VM_CL
62            | vm::VM_SRV
63            | vm::VM_GLOBAL
64            | vm::VM_THROW
65            | vm::VM_TT
66            | vm::VM_TF
67            | vm::VM_SETF
68            | vm::VM_SETNF
69            | vm::VM_LNOT
70            | vm::VM_BNOT
71            | vm::VM_ASC
72            | vm::VM_CHR
73            | vm::VM_NUM
74            | vm::VM_CHS
75            | vm::VM_INV
76            | vm::VM_CHKINV
77            | vm::VM_TYPEOF
78            | vm::VM_EVAL
79            | vm::VM_EEXP
80            | vm::VM_INT
81            | vm::VM_REAL
82            | vm::VM_STR
83            | vm::VM_OCTET
84    ) {
85        2
86    } else if op == vm::VM_CCL {
87        3
88    } else if op == vm::VM_JF || op == vm::VM_JNF || op == vm::VM_JMP {
89        2
90    } else if op == vm::VM_ENTRY {
91        3
92    } else if matches!(
93        op,
94        vm::VM_RET | vm::VM_NOP | vm::VM_NF | vm::VM_EXTRY | vm::VM_REGMEMBER | vm::VM_DEBUGGER
95    ) {
96        1
97    } else if op == vm::VM_SETP || op == vm::VM_GETP {
98        3
99    } else if op == vm::VM_DELD || op == vm::VM_TYPEOFD {
100        4
101    } else if op == vm::VM_DELI || op == vm::VM_TYPEOFI {
102        4
103    } else if op == vm::VM_GPD || op == vm::VM_GPDS {
104        4
105    } else if matches!(op, vm::VM_SPD | vm::VM_SPDE | vm::VM_SPDEH | vm::VM_SPDS) {
106        4
107    } else if op == vm::VM_GPI || op == vm::VM_GPIS {
108        4
109    } else if op == vm::VM_SPI || op == vm::VM_SPIE || op == vm::VM_SPIS {
110        4
111    } else if (vm::VM_INC..=vm::VM_INCP).contains(&op) {
112        // INC / INCPD / INCPI / INCP
113        match op - vm::VM_INC {
114            0 => 2,
115            1 | 2 => 4,
116            3 => 3,
117            _ => 1,
118        }
119    } else if (vm::VM_DEC..=vm::VM_DECP).contains(&op) {
120        // DEC / DECPD / DECPI / DECP
121        match op - vm::VM_DEC {
122            0 => 2,
123            1 | 2 => 4,
124            3 => 3,
125            _ => 1,
126        }
127    } else if is_op2_prop(op) {
128        // OP2 with property access variants: base..=base+3.
129        let base = op2_prop_base(op);
130        match op - base {
131            0 => 3,
132            1 | 2 => 5,
133            3 => 4,
134            _ => 1,
135        }
136    } else if op == vm::VM_CALL || op == vm::VM_CALLD || op == vm::VM_CALLI || op == vm::VM_NEW {
137        decode_call_size(code, pc)?
138    } else {
139        1
140    };
141
142    ensure(code, pc, size)?;
143    let mut words = Vec::with_capacity(size);
144    for j in 0..size {
145        words.push(code[pc + j]);
146    }
147    Ok(Insn {
148        pc,
149        op,
150        size,
151        words,
152    })
153}
154
155fn is_op2_prop(op: i32) -> bool {
156    matches!(
157        op,
158        // Each of these is the base of a 4-variant family: x/xPD/xPI/xP
159        _ if (vm::VM_LOR..=vm::VM_LOR + 3).contains(&op)
160            || (vm::VM_LAND..=vm::VM_LAND + 3).contains(&op)
161            || (vm::VM_BOR..=vm::VM_BOR + 3).contains(&op)
162            || (vm::VM_BXOR..=vm::VM_BXOR + 3).contains(&op)
163            || (vm::VM_BAND..=vm::VM_BAND + 3).contains(&op)
164            || (vm::VM_SAR..=vm::VM_SAR + 3).contains(&op)
165            || (vm::VM_SAL..=vm::VM_SAL + 3).contains(&op)
166            || (vm::VM_SR..=vm::VM_SR + 3).contains(&op)
167            || (vm::VM_ADD..=vm::VM_ADD + 3).contains(&op)
168            || (vm::VM_SUB..=vm::VM_SUB + 3).contains(&op)
169            || (vm::VM_MOD..=vm::VM_MOD + 3).contains(&op)
170            || (vm::VM_DIV..=vm::VM_DIV + 3).contains(&op)
171            || (vm::VM_IDIV..=vm::VM_IDIV + 3).contains(&op)
172            || (vm::VM_MUL..=vm::VM_MUL + 3).contains(&op)
173    )
174}
175
176fn op2_prop_base(op: i32) -> i32 {
177    // Return the family base for an opcode known to be within a family.
178    for base in [
179        vm::VM_LOR,
180        vm::VM_LAND,
181        vm::VM_BOR,
182        vm::VM_BXOR,
183        vm::VM_BAND,
184        vm::VM_SAR,
185        vm::VM_SAL,
186        vm::VM_SR,
187        vm::VM_ADD,
188        vm::VM_SUB,
189        vm::VM_MOD,
190        vm::VM_DIV,
191        vm::VM_IDIV,
192        vm::VM_MUL,
193    ] {
194        if (base..=base + 3).contains(&op) {
195            return base;
196        }
197    }
198    op
199}
200
201fn decode_call_size(code: &[i32], pc: usize) -> Result<usize> {
202    // Mirror `disasm::format_call` size logic.
203    // Layout:
204    // CALL:  op, dst, func, argc, [args...]
205    // CALLD: op, dst, obj, member_data, func, argc, ...
206    // CALLI: op, dst, obj, member_reg,  func, argc, ...
207    // NEW:   op, dst, func, argc, ...
208    let op = code[pc];
209    let header = match op {
210        x if x == vm::VM_CALL => 4,
211        x if x == vm::VM_NEW => 4,
212        _ => 5,
213    };
214    ensure(code, pc, header)?;
215    let argc = code[pc + header - 1];
216
217    if argc == -1 {
218        Ok(header)
219    } else if argc == -2 {
220        ensure(code, pc, header + 1)?;
221        let num = code[pc + header] as usize;
222        // +1 for num, and 2*num for (type,value) pairs.
223        Ok(header + 1 + num * 2)
224    } else {
225        let num = argc as usize;
226        Ok(header + num)
227    }
228}
229
230fn ensure(code: &[i32], pc: usize, need: usize) -> Result<()> {
231    if pc + need > code.len() {
232        bail!(
233            "truncated instruction at {}: need {}, code_len {}",
234            pc,
235            need,
236            code.len()
237        );
238    }
239    Ok(())
240}