tjs2dec/
disasm.rs

1use anyhow::{Result, bail};
2
3use crate::model::{ConstPools, Tjs2File, Tjs2Object};
4use crate::vmcodes::vm;
5
6const FAT_NORMAL: i32 = 0;
7const FAT_EXPAND: i32 = 1;
8const FAT_UNNAMED_EXPAND: i32 = 2;
9
10pub fn disassemble_file(file: &Tjs2File) -> Result<String> {
11    let mut out = String::new();
12    out.push_str(&format!("toplevel: {}\n", file.toplevel));
13    out.push_str(&format!("objects: {}\n\n", file.objects.len()));
14
15    for obj in &file.objects {
16        out.push_str(&format_object_header(obj));
17        out.push('\n');
18        out.push_str(&disassemble_object(obj, &file.const_pools)?);
19        out.push('\n');
20    }
21
22    Ok(out)
23}
24
25fn format_object_header(obj: &Tjs2Object) -> String {
26    let name = obj.name.as_deref().unwrap_or("<anonymous>");
27    format!(
28        "== object {}: name={} context_type={} parent={} ==",
29        obj.index,
30        quote(name),
31        obj.context_type,
32        obj.parent
33    )
34}
35
36pub fn disassemble_object(obj: &Tjs2Object, pools: &ConstPools) -> Result<String> {
37    let mut out = String::new();
38    let code = &obj.code;
39
40    let mut i: usize = 0;
41    while i < code.len() {
42        let (msg, comment, size) = format_insn(code, i, obj, pools)?;
43        out.push_str(&format!("{:08} {}", i, msg));
44        if !comment.is_empty() {
45            out.push_str("\t// ");
46            out.push_str(&comment);
47        }
48        out.push('\n');
49        i += size;
50    }
51    Ok(out)
52}
53
54fn format_insn(
55    code: &[i32],
56    i: usize,
57    obj: &Tjs2Object,
58    pools: &ConstPools,
59) -> Result<(String, String, usize)> {
60    let op = code[i];
61    let mut msg = String::new();
62    let mut com = String::new();
63    let mut size: usize = 1;
64
65    // Helpers
66    let reg = |x: i32| -> i32 { x };
67    let data_idx = |x: i32| -> i32 { x };
68    let target = |rel: i32| -> i32 { rel + i as i32 };
69
70    // const
71    if op == vm::VM_CONST {
72        ensure(code, i, 3)?;
73        let dst = reg(code[i + 1]);
74        let src = data_idx(code[i + 2]);
75        msg = format!("const %{}, *{}", dst, src);
76        if let Some(v) = obj.data.get(src as usize) {
77            com = format!("*{} = {}", src, v.to_readable(pools));
78        }
79        size = 3;
80        return Ok((msg, com, size));
81    }
82
83    // OP2 no property access
84    for (base, name) in [
85        (vm::VM_CP, "cp"),
86        (vm::VM_CEQ, "ceq"),
87        (vm::VM_CDEQ, "cdeq"),
88        (vm::VM_CLT, "clt"),
89        (vm::VM_CGT, "cgt"),
90        (vm::VM_CHKINS, "chkins"),
91        (vm::VM_ADDCI, "addci"),
92        (vm::VM_CHGTHIS, "chgthis"),
93    ] {
94        if op == base {
95            ensure(code, i, 3)?;
96            msg = format!("{} %{}, %{}", name, reg(code[i + 1]), reg(code[i + 2]));
97            size = 3;
98            return Ok((msg, com, size));
99        }
100    }
101
102    // cl / srv / global / throw: one reg
103    for (base, name) in [
104        (vm::VM_CL, "cl"),
105        (vm::VM_SRV, "srv"),
106        (vm::VM_GLOBAL, "global"),
107        (vm::VM_THROW, "throw"),
108        (vm::VM_TT, "tt"),
109        (vm::VM_TF, "tf"),
110        (vm::VM_SETF, "setf"),
111        (vm::VM_SETNF, "setnf"),
112        (vm::VM_LNOT, "lnot"),
113        (vm::VM_BNOT, "bnot"),
114        (vm::VM_ASC, "asc"),
115        (vm::VM_CHR, "chr"),
116        (vm::VM_NUM, "num"),
117        (vm::VM_CHS, "chs"),
118        (vm::VM_INV, "inv"),
119        (vm::VM_CHKINV, "chkinv"),
120        (vm::VM_TYPEOF, "typeof"),
121        (vm::VM_EVAL, "eval"),
122        (vm::VM_EEXP, "eexp"),
123        (vm::VM_INT, "int"),
124        (vm::VM_REAL, "real"),
125        (vm::VM_STR, "str"),
126        (vm::VM_OCTET, "octet"),
127    ] {
128        if op == base {
129            ensure(code, i, 2)?;
130            msg = format!("{} %{}", name, reg(code[i + 1]));
131            size = 2;
132            return Ok((msg, com, size));
133        }
134    }
135
136    if op == vm::VM_CCL {
137        ensure(code, i, 3)?;
138        let r0 = reg(code[i + 1]);
139        let cnt = code[i + 2];
140        msg = format!("ccl %{}-%{}", r0, r0 + cnt - 1);
141        size = 3;
142        return Ok((msg, com, size));
143    }
144
145    // JF/JNF/JMP (one code operand)
146    for (base, name) in [(vm::VM_JF, "jf"), (vm::VM_JNF, "jnf"), (vm::VM_JMP, "jmp")] {
147        if op == base {
148            ensure(code, i, 2)?;
149            msg = format!("{} {:09}", name, target(code[i + 1]));
150            size = 2;
151            return Ok((msg, com, size));
152        }
153    }
154
155    // entry
156    if op == vm::VM_ENTRY {
157        ensure(code, i, 3)?;
158        msg = format!("entry {:09}, %{}", target(code[i + 1]), reg(code[i + 2]));
159        size = 3;
160        return Ok((msg, com, size));
161    }
162
163    // RET / NOP / NF / EXTRY / REGMEMBER / DEBUGGER
164    for (base, name) in [
165        (vm::VM_RET, "ret"),
166        (vm::VM_NOP, "nop"),
167        (vm::VM_NF, "nf"),
168        (vm::VM_EXTRY, "extry"),
169        (vm::VM_REGMEMBER, "regmember"),
170        (vm::VM_DEBUGGER, "debugger"),
171    ] {
172        if op == base {
173            msg = name.to_string();
174            size = 1;
175            return Ok((msg, com, size));
176        }
177    }
178
179    // SETP / GETP
180    if op == vm::VM_SETP || op == vm::VM_GETP {
181        ensure(code, i, 3)?;
182        let n = if op == vm::VM_SETP { "setp" } else { "getp" };
183        msg = format!("{} %{}, %{}", n, reg(code[i + 1]), reg(code[i + 2]));
184        size = 3;
185        return Ok((msg, com, size));
186    }
187
188    // deld/typeofd
189    if op == vm::VM_DELD || op == vm::VM_TYPEOFD {
190        ensure(code, i, 4)?;
191        let n = if op == vm::VM_DELD { "deld" } else { "typeofd" };
192        let a = reg(code[i + 1]);
193        let b = reg(code[i + 2]);
194        let c = data_idx(code[i + 3]);
195        msg = format!("{} %{}, %{}.*{}", n, a, b, c);
196        if let Some(v) = obj.data.get(c as usize) {
197            com = format!("*{} = {}", c, v.to_readable(pools));
198        }
199        size = 4;
200        return Ok((msg, com, size));
201    }
202
203    // deli/typeofi
204    if op == vm::VM_DELI || op == vm::VM_TYPEOFI {
205        ensure(code, i, 4)?;
206        let n = if op == vm::VM_DELI { "deli" } else { "typeofi" };
207        msg = format!(
208            "{} %{}, %{}.%{}",
209            n,
210            reg(code[i + 1]),
211            reg(code[i + 2]),
212            reg(code[i + 3])
213        );
214        size = 4;
215        return Ok((msg, com, size));
216    }
217
218    // gpd/gpds
219    if op == vm::VM_GPD || op == vm::VM_GPDS {
220        ensure(code, i, 4)?;
221        let n = if op == vm::VM_GPD { "gpd" } else { "gpds" };
222        let a = reg(code[i + 1]);
223        let b = reg(code[i + 2]);
224        let c = data_idx(code[i + 3]);
225        msg = format!("{} %{}, %{}.*{}", n, a, b, c);
226        if let Some(v) = obj.data.get(c as usize) {
227            com = format!("*{} = {}", c, v.to_readable(pools));
228        }
229        size = 4;
230        return Ok((msg, com, size));
231    }
232
233    // spd/spde/spdeh/spds
234    if op == vm::VM_SPD || op == vm::VM_SPDE || op == vm::VM_SPDEH || op == vm::VM_SPDS {
235        ensure(code, i, 4)?;
236        let n = match op {
237            x if x == vm::VM_SPD => "spd",
238            x if x == vm::VM_SPDE => "spde",
239            x if x == vm::VM_SPDEH => "spdeh",
240            _ => "spds",
241        };
242        let a = reg(code[i + 1]);
243        let b = data_idx(code[i + 2]);
244        let c = reg(code[i + 3]);
245        msg = format!("{} %{}.*{}, %{}", n, a, b, c);
246        if let Some(v) = obj.data.get(b as usize) {
247            com = format!("*{} = {}", b, v.to_readable(pools));
248        }
249        size = 4;
250        return Ok((msg, com, size));
251    }
252
253    // gpi/gpis
254    if op == vm::VM_GPI || op == vm::VM_GPIS {
255        ensure(code, i, 4)?;
256        let n = if op == vm::VM_GPI { "gpi" } else { "gpis" };
257        msg = format!(
258            "{} %{}, %{}.%{}",
259            n,
260            reg(code[i + 1]),
261            reg(code[i + 2]),
262            reg(code[i + 3])
263        );
264        size = 4;
265        return Ok((msg, com, size));
266    }
267
268    // spi/spie/spis
269    if op == vm::VM_SPI || op == vm::VM_SPIE || op == vm::VM_SPIS {
270        ensure(code, i, 4)?;
271        let n = match op {
272            x if x == vm::VM_SPI => "spi",
273            x if x == vm::VM_SPIE => "spie",
274            _ => "spis",
275        };
276        msg = format!(
277            "{} %{}.%{}, %{}",
278            n,
279            reg(code[i + 1]),
280            reg(code[i + 2]),
281            reg(code[i + 3])
282        );
283        size = 4;
284        return Ok((msg, com, size));
285    }
286
287    // inc/dec with property access variants
288    if let Some((m, c, s)) = op1_prop(code, i, obj, pools, vm::VM_INC, "inc")? {
289        return Ok((m, c, s));
290    }
291    if let Some((m, c, s)) = op1_prop(code, i, obj, pools, vm::VM_DEC, "dec")? {
292        return Ok((m, c, s));
293    }
294
295    // binary ops with property access variants
296    for (base, name) in [
297        (vm::VM_LOR, "lor"),
298        (vm::VM_LAND, "land"),
299        (vm::VM_BOR, "bor"),
300        (vm::VM_BXOR, "bxor"),
301        (vm::VM_BAND, "band"),
302        (vm::VM_SAR, "sar"),
303        (vm::VM_SAL, "sal"),
304        (vm::VM_SR, "sr"),
305        (vm::VM_ADD, "add"),
306        (vm::VM_SUB, "sub"),
307        (vm::VM_MOD, "mod"),
308        (vm::VM_DIV, "div"),
309        (vm::VM_IDIV, "idiv"),
310        (vm::VM_MUL, "mul"),
311    ] {
312        if let Some((m, c, s)) = op2_prop(code, i, obj, pools, base, name)? {
313            return Ok((m, c, s));
314        }
315    }
316
317    // call / calld / calli / new
318    if op == vm::VM_CALL || op == vm::VM_CALLD || op == vm::VM_CALLI || op == vm::VM_NEW {
319        return format_call(code, i, obj, pools);
320    }
321
322    // add missing simple opcodes
323    if op == vm::VM_REGMEMBER || op == vm::VM_DEBUGGER {
324        msg = vm::name(op).to_lowercase();
325        size = 1;
326        return Ok((msg, com, size));
327    }
328
329    // Fallback
330    msg = format!("unknown instruction {}", op);
331    Ok((msg, com, 1))
332}
333
334fn op2_prop(
335    code: &[i32],
336    i: usize,
337    obj: &Tjs2Object,
338    pools: &ConstPools,
339    base: i32,
340    name: &str,
341) -> Result<Option<(String, String, usize)>> {
342    let op = code[i];
343    if op < base || op > base + 3 {
344        return Ok(None);
345    }
346    match op - base {
347        0 => {
348            ensure(code, i, 3)?;
349            Ok(Some((
350                format!("{} %{}, %{}", name, code[i + 1], code[i + 2]),
351                String::new(),
352                3,
353            )))
354        }
355        1 => {
356            ensure(code, i, 5)?;
357            let a = code[i + 1];
358            let b = code[i + 2];
359            let c = code[i + 3];
360            let d = code[i + 4];
361            let mut com = String::new();
362            if let Some(v) = obj.data.get(c as usize) {
363                com = format!("*{} = {}", c, v.to_readable(pools));
364            }
365            Ok(Some((
366                format!("{}pd %{}, %{}.*{}, %{}", name, a, b, c, d),
367                com,
368                5,
369            )))
370        }
371        2 => {
372            ensure(code, i, 5)?;
373            Ok(Some((
374                format!(
375                    "{}pi %{}, %{}.%{}, %{}",
376                    name,
377                    code[i + 1],
378                    code[i + 2],
379                    code[i + 3],
380                    code[i + 4]
381                ),
382                String::new(),
383                5,
384            )))
385        }
386        3 => {
387            ensure(code, i, 4)?;
388            Ok(Some((
389                format!(
390                    "{}p %{}, %{}, %{}",
391                    name,
392                    code[i + 1],
393                    code[i + 2],
394                    code[i + 3]
395                ),
396                String::new(),
397                4,
398            )))
399        }
400        _ => Ok(None),
401    }
402}
403
404fn op1_prop(
405    code: &[i32],
406    i: usize,
407    obj: &Tjs2Object,
408    pools: &ConstPools,
409    base: i32,
410    name: &str,
411) -> Result<Option<(String, String, usize)>> {
412    let op = code[i];
413    if op < base || op > base + 3 {
414        return Ok(None);
415    }
416    match op - base {
417        0 => {
418            ensure(code, i, 2)?;
419            Ok(Some((
420                format!("{} %{}", name, code[i + 1]),
421                String::new(),
422                2,
423            )))
424        }
425        1 => {
426            ensure(code, i, 4)?;
427            let a = code[i + 1];
428            let b = code[i + 2];
429            let c = code[i + 3];
430            let mut com = String::new();
431            if let Some(v) = obj.data.get(c as usize) {
432                com = format!("*{} = {}", c, v.to_readable(pools));
433            }
434            Ok(Some((format!("{}pd %{}, %{}.*{}", name, a, b, c), com, 4)))
435        }
436        2 => {
437            ensure(code, i, 4)?;
438            Ok(Some((
439                format!(
440                    "{}pi %{}, %{}.%{}",
441                    name,
442                    code[i + 1],
443                    code[i + 2],
444                    code[i + 3]
445                ),
446                String::new(),
447                4,
448            )))
449        }
450        3 => {
451            ensure(code, i, 3)?;
452            Ok(Some((
453                format!("{}p %{}, %{}", name, code[i + 1], code[i + 2]),
454                String::new(),
455                3,
456            )))
457        }
458        _ => Ok(None),
459    }
460}
461
462fn format_call(
463    code: &[i32],
464    i: usize,
465    obj: &Tjs2Object,
466    pools: &ConstPools,
467) -> Result<(String, String, usize)> {
468    let op = code[i];
469    // Base layout:
470    // CALL:  op, dst, func, argc, [args...]
471    // CALLD: op, dst, obj, member_data, func, argc, ...
472    // CALLI: op, dst, obj, member_reg,  func, argc, ...
473    // NEW:   op, dst, func, argc, ...
474    let (prefix, st, member_data_index_opt): (&str, usize, Option<i32>) = match op {
475        x if x == vm::VM_CALL => ("call", 4, None),
476        x if x == vm::VM_NEW => ("new", 4, None),
477        x if x == vm::VM_CALLD => ("calld", 5, Some(code[i + 3])),
478        _ => ("calli", 5, None),
479    };
480
481    // Header
482    let mut msg = String::new();
483    match op {
484        x if x == vm::VM_CALL => {
485            ensure(code, i, 4)?;
486            msg = format!("call %{}, %{}(", code[i + 1], code[i + 2]);
487        }
488        x if x == vm::VM_NEW => {
489            ensure(code, i, 4)?;
490            msg = format!("new %{}, %{}(", code[i + 1], code[i + 2]);
491        }
492        x if x == vm::VM_CALLD => {
493            ensure(code, i, 5)?;
494            msg = format!("calld %{}, %{}.*{}(", code[i + 1], code[i + 2], code[i + 3]);
495        }
496        _ => {
497            ensure(code, i, 5)?;
498            msg = format!("calli %{}, %{}.%{}(", code[i + 1], code[i + 2], code[i + 3]);
499        }
500    }
501
502    let argc = code[i + st - 1];
503    let mut size: usize;
504
505    if argc == -1 {
506        // omit args
507        size = st;
508        msg.push_str("...");
509    } else if argc == -2 {
510        // expand args
511        ensure(code, i, st + 1)?;
512        let num = code[i + st];
513        let num = num as usize;
514        size = st + 1 + num * 2;
515        for j in 0..num {
516            if j != 0 {
517                msg.push_str(", ");
518            }
519            let ty = code[i + st + 1 + j * 2];
520            let v = code[i + st + 1 + j * 2 + 1];
521            match ty {
522                FAT_NORMAL => msg.push_str(&format!("%{}", v)),
523                FAT_EXPAND => msg.push_str(&format!("%{}*", v)),
524                FAT_UNNAMED_EXPAND => msg.push('*'),
525                _ => msg.push_str(&format!("<bad-fat:{}>", ty)),
526            }
527        }
528    } else {
529        // normal args: argc registers follow
530        let num = argc as usize;
531        size = st + num;
532        for j in 0..num {
533            if j != 0 {
534                msg.push_str(", ");
535            }
536            msg.push_str(&format!("%{}", code[i + st + j]));
537        }
538    }
539
540    msg.push(')');
541
542    let mut com = String::new();
543    if op == vm::VM_CALLD {
544        // in krkrz, comment prints the member string stored in data area at code[i+3]
545        let c = code[i + 3];
546        if let Some(v) = obj.data.get(c as usize) {
547            com = format!("*{} = {}", c, v.to_readable(pools));
548        }
549    }
550
551    Ok((msg, com, size))
552}
553
554fn ensure(code: &[i32], i: usize, need: usize) -> Result<()> {
555    if i + need > code.len() {
556        bail!(
557            "truncated instruction at {}: need {}, code_len {}",
558            i,
559            need,
560            code.len()
561        );
562    }
563    Ok(())
564}
565
566fn quote(s: &str) -> String {
567    let mut out = String::new();
568    out.push('"');
569    for ch in s.chars() {
570        match ch {
571            '\\' => out.push_str("\\\\"),
572            '"' => out.push_str("\\\""),
573            '\n' => out.push_str("\\n"),
574            '\r' => out.push_str("\\r"),
575            '\t' => out.push_str("\\t"),
576            _ => out.push(ch),
577        }
578    }
579    out.push('"');
580    out
581}