hcb2lua_decompiler/
lua.rs

1use std::collections::BTreeSet;
2use std::fmt::Write as _;
3use std::io::{Result as IoResult, Write};
4
5use crate::cfg::{build_cfg, BlockTerm, FunctionCfg};
6use crate::decode::{Function, Instruction, Op};
7use crate::lua_opt::BlockEmitter;
8use crate::parser::Parser;
9
10fn func_name(addr: u32) -> String {
11    format!("f_{:08X}", addr)
12}
13
14fn emit_stack_slot_get(args_count: u8, idx: i8) -> String {
15    if idx < 0 {
16        let abs = (-idx) as u8 - 2;
17        if abs <= args_count {
18            let a = (args_count - abs) as usize;
19            return format!("a{}", a);
20        }
21        return format!("a_{}", idx);
22    }
23
24    let u = idx as u8;
25    if u < args_count {
26        format!("a{}", u as usize)
27    } else {
28        let l = (u - args_count) as usize;
29        format!("l{}", l)
30    }
31}
32
33fn emit_stack_slot_set(args_count: u8, idx: i8, rhs: &str) -> String {
34    let lhs = emit_stack_slot_get(args_count, idx);
35    format!("{} = {}", lhs, rhs)
36}
37
38fn emit_global(idx: u16, non_volatile_count: u16, volatile_count: u16) -> String {
39    if idx < non_volatile_count {
40        return format!("g{}", idx);
41    }
42
43    let vbase = non_volatile_count;
44    let vlimit = non_volatile_count.saturating_add(volatile_count);
45    if idx >= vbase && idx < vlimit {
46        return format!("vg{}", idx - vbase);
47    }
48
49    format!("G[{}]", idx)
50}
51
52fn emit_global_table(idx: u16) -> String {
53    format!("GT[{}]", idx)
54}
55
56fn emit_local_table(idx: i8) -> String {
57    format!("LT[{}]", idx)
58}
59
60fn escape_lua_string(s: &str) -> String {
61    let mut out = String::new();
62    for ch in s.chars() {
63        match ch {
64            '\\' => out.push_str("\\\\"),
65            '"' => out.push_str("\\\""),
66            '\n' => out.push_str("\\n"),
67            '\r' => out.push_str("\\r"),
68            '\t' => out.push_str("\\t"),
69            _ => out.push(ch),
70        }
71    }
72    out
73}
74
75fn collect_used_frame_locals(func: &Function) -> BTreeSet<usize> {
76    let mut used_l = BTreeSet::new();
77    for inst in &func.insts {
78        match inst.op {
79            Op::PushStack(idx) | Op::PopStack(idx) => {
80                if idx >= 0 {
81                    let u = idx as u8;
82                    if u >= func.args {
83                        used_l.insert((u - func.args) as usize);
84                    }
85                }
86            }
87            _ => {}
88        }
89    }
90    used_l
91}
92
93fn scan_runtime_tables(functions: &[Function]) -> (bool, bool, bool) {
94    let mut need_g = false;
95    let mut need_gt = false;
96    let mut need_lt = false;
97
98    for func in functions {
99        for inst in &func.insts {
100            match inst.op {
101                Op::PushGlobalTable(_) | Op::PopGlobalTable(_) => need_gt = true,
102                Op::PushLocalTable(_) | Op::PopLocalTable(_) => need_lt = true,
103                _ => {}
104            }
105        }
106    }
107
108    (need_g, need_gt, need_lt)
109}
110
111fn uses_fallback_global(functions: &[Function], non_volatile_count: u16, volatile_count: u16) -> bool {
112    let limit = non_volatile_count.saturating_add(volatile_count);
113    for func in functions {
114        for inst in &func.insts {
115            match inst.op {
116                Op::PushGlobal(idx) | Op::PopGlobal(idx) if idx >= limit => return true,
117                _ => {}
118            }
119        }
120    }
121    false
122}
123
124fn emit_name_decls<W: Write>(w: &mut W, keyword: &str, prefix: &str, count: u16) -> IoResult<()> {
125    if count == 0 {
126        return Ok(());
127    }
128
129    let mut start = 0u16;
130    while start < count {
131        let end = count.min(start + 16);
132        write!(w, "{} ", keyword)?;
133        for i in start..end {
134            if i > start {
135                write!(w, ", ")?;
136            }
137            write!(w, "{}{}", prefix, i)?;
138        }
139        writeln!(w)?;
140        start = end;
141    }
142    Ok(())
143}
144
145fn emit_push_value<W: Write>(w: &mut W, indent: &str, value: &str) -> IoResult<()> {
146    writeln!(w, "{}__sp = __sp + 1", indent)?;
147    writeln!(w, "{}__stk[__sp] = {}", indent, value)
148}
149
150fn emit_binary_reduce<W: Write>(w: &mut W, indent: &str, expr: &str, op_name: &str) -> IoResult<()> {
151    writeln!(w, "{}if __sp < 2 then", indent)?;
152    writeln!(w, "{}  -- {} on short stack", indent, op_name)?;
153    writeln!(w, "{}else", indent)?;
154    writeln!(w, "{}  local __rhs = __stk[__sp]", indent)?;
155    writeln!(w, "{}  local __lhs = __stk[__sp - 1]", indent)?;
156    writeln!(w, "{}  __stk[__sp] = nil", indent)?;
157    writeln!(w, "{}  __sp = __sp - 1", indent)?;
158    writeln!(w, "{}  __stk[__sp] = {}", indent, expr)?;
159    writeln!(w, "{}end", indent)
160}
161
162fn emit_runtime_inst<W: Write>(
163    w: &mut W,
164    indent: &str,
165    func: &Function,
166    inst: &Instruction,
167    callee_args: &std::collections::BTreeMap<u32, u8>,
168    non_volatile_count: u16,
169    volatile_count: u16,
170) -> IoResult<()> {
171    match &inst.op {
172        Op::Nop | Op::InitStack { .. } => {}
173        Op::PushNil => emit_push_value(w, indent, "nil")?,
174        Op::PushTrue => emit_push_value(w, indent, "true")?,
175        Op::PushI8(v) => emit_push_value(w, indent, &format!("{}", v))?,
176        Op::PushI16(v) => emit_push_value(w, indent, &format!("{}", v))?,
177        Op::PushI32(v) => emit_push_value(w, indent, &format!("{}", v))?,
178        Op::PushF32(v) => emit_push_value(w, indent, &format!("{}", v))?,
179        Op::PushString(s0) => emit_push_value(w, indent, &format!("\"{}\"", escape_lua_string(s0)))?,
180        Op::PushTop => {
181            writeln!(w, "{}if __sp == 0 then", indent)?;
182            writeln!(w, "{}  -- push_top on empty stack", indent)?;
183            writeln!(w, "{}  __sp = 1", indent)?;
184            writeln!(w, "{}  __stk[1] = nil", indent)?;
185            writeln!(w, "{}else", indent)?;
186            writeln!(w, "{}  __sp = __sp + 1", indent)?;
187            writeln!(w, "{}  __stk[__sp] = __stk[__sp - 1]", indent)?;
188            writeln!(w, "{}end", indent)?;
189        }
190        Op::PushReturn => emit_push_value(w, indent, "__ret")?,
191        Op::PushGlobal(idx) => emit_push_value(w, indent, &emit_global(*idx, non_volatile_count, volatile_count))?,
192        Op::PopGlobal(idx) => {
193            writeln!(w, "{}if __sp == 0 then", indent)?;
194            writeln!(w, "{}  -- pop_global with empty stack", indent)?;
195            writeln!(w, "{}else", indent)?;
196            writeln!(w, "{}  {} = __stk[__sp]", indent, emit_global(*idx, non_volatile_count, volatile_count))?;
197            writeln!(w, "{}  __stk[__sp] = nil", indent)?;
198            writeln!(w, "{}  __sp = __sp - 1", indent)?;
199            writeln!(w, "{}end", indent)?;
200        }
201        Op::PushGlobalTable(idx) => {
202            writeln!(w, "{}if __sp == 0 then", indent)?;
203            writeln!(w, "{}  -- push_global_table on empty stack", indent)?;
204            writeln!(w, "{}else", indent)?;
205            writeln!(w, "{}  __stk[__sp] = {}[__stk[__sp]]", indent, emit_global_table(*idx))?;
206            writeln!(w, "{}end", indent)?;
207        }
208        Op::PopGlobalTable(idx) => {
209            writeln!(w, "{}if __sp < 2 then", indent)?;
210            writeln!(w, "{}  -- pop_global_table on short stack", indent)?;
211            writeln!(w, "{}  __sp = 0", indent)?;
212            writeln!(w, "{}else", indent)?;
213            writeln!(w, "{}  local __value = __stk[__sp]", indent)?;
214            writeln!(w, "{}  __stk[__sp] = nil", indent)?;
215            writeln!(w, "{}  __sp = __sp - 1", indent)?;
216            writeln!(w, "{}  local __key = __stk[__sp]", indent)?;
217            writeln!(w, "{}  {}[__key] = __value", indent, emit_global_table(*idx))?;
218            writeln!(w, "{}  __stk[__sp] = nil", indent)?;
219            writeln!(w, "{}  __sp = __sp - 1", indent)?;
220            writeln!(w, "{}end", indent)?;
221        }
222        Op::PushLocalTable(idx) => {
223            writeln!(w, "{}if __sp == 0 then", indent)?;
224            writeln!(w, "{}  -- push_local_table on empty stack", indent)?;
225            writeln!(w, "{}else", indent)?;
226            writeln!(w, "{}  __stk[__sp] = {}[__stk[__sp]]", indent, emit_local_table(*idx))?;
227            writeln!(w, "{}end", indent)?;
228        }
229        Op::PopLocalTable(idx) => {
230            writeln!(w, "{}if __sp < 2 then", indent)?;
231            writeln!(w, "{}  -- pop_local_table on short stack", indent)?;
232            writeln!(w, "{}  __sp = 0", indent)?;
233            writeln!(w, "{}else", indent)?;
234            writeln!(w, "{}  local __value = __stk[__sp]", indent)?;
235            writeln!(w, "{}  __stk[__sp] = nil", indent)?;
236            writeln!(w, "{}  __sp = __sp - 1", indent)?;
237            writeln!(w, "{}  local __key = __stk[__sp]", indent)?;
238            writeln!(w, "{}  {}[__key] = __value", indent, emit_local_table(*idx))?;
239            writeln!(w, "{}  __stk[__sp] = nil", indent)?;
240            writeln!(w, "{}  __sp = __sp - 1", indent)?;
241            writeln!(w, "{}end", indent)?;
242        }
243        Op::PushStack(idx) => emit_push_value(w, indent, &emit_stack_slot_get(func.args, *idx))?,
244        Op::PopStack(idx) => {
245            writeln!(w, "{}if __sp == 0 then", indent)?;
246            writeln!(w, "{}  -- pop_stack with empty stack", indent)?;
247            writeln!(w, "{}else", indent)?;
248            writeln!(w, "{}  {}", indent, emit_stack_slot_set(func.args, *idx, "__stk[__sp]"))?;
249            writeln!(w, "{}  __stk[__sp] = nil", indent)?;
250            writeln!(w, "{}  __sp = __sp - 1", indent)?;
251            writeln!(w, "{}end", indent)?;
252        }
253        Op::Neg => {
254            writeln!(w, "{}if __sp == 0 then", indent)?;
255            writeln!(w, "{}  -- neg on empty stack", indent)?;
256            writeln!(w, "{}else", indent)?;
257            writeln!(w, "{}  __stk[__sp] = -__stk[__sp]", indent)?;
258            writeln!(w, "{}end", indent)?;
259        }
260        Op::Add => emit_binary_reduce(w, indent, "__lhs + __rhs", "add")?,
261        Op::Sub => emit_binary_reduce(w, indent, "__lhs - __rhs", "sub")?,
262        Op::Mul => emit_binary_reduce(w, indent, "__lhs * __rhs", "mul")?,
263        Op::Div => emit_binary_reduce(w, indent, "__lhs / __rhs", "div")?,
264        Op::Mod => emit_binary_reduce(w, indent, "__lhs % __rhs", "mod")?,
265        Op::BitTest => emit_binary_reduce(w, indent, "(__lhs & __rhs) ~= 0", "bittest")?,
266        Op::And => emit_binary_reduce(w, indent, "(__lhs ~= nil) and (__rhs ~= nil)", "and")?,
267        Op::Or => emit_binary_reduce(w, indent, "(__lhs ~= nil) or (__rhs ~= nil)", "or")?,
268        Op::SetE => emit_binary_reduce(w, indent, "(__lhs == __rhs)", "sete")?,
269        Op::SetNE => emit_binary_reduce(w, indent, "(__lhs ~= __rhs)", "setne")?,
270        Op::SetG => emit_binary_reduce(w, indent, "(__lhs > __rhs)", "setg")?,
271        Op::SetGE => emit_binary_reduce(w, indent, "(__lhs >= __rhs)", "setge")?,
272        Op::SetL => emit_binary_reduce(w, indent, "(__lhs < __rhs)", "setl")?,
273        Op::SetLE => emit_binary_reduce(w, indent, "(__lhs <= __rhs)", "setle")?,
274        Op::Call { target } => {
275            let argc = callee_args.get(target).copied().unwrap_or(0) as usize;
276            writeln!(w, "{}if __sp < {} then", indent, argc)?;
277            writeln!(w, "{}  -- call {} with argc={} on short stack", indent, func_name(*target), argc)?;
278            writeln!(w, "{}  __ret = {}()", indent, func_name(*target))?;
279            writeln!(w, "{}  __sp = 0", indent)?;
280            writeln!(w, "{}else", indent)?;
281            let base_expr = if argc == 0 { "__sp + 1".to_string() } else { format!("__sp - {} + 1", argc) };
282            writeln!(w, "{}  local __base = {}", indent, base_expr)?;
283            let mut args_s = String::new();
284            for i in 0..argc {
285                if i > 0 { args_s.push_str(", "); }
286                write!(&mut args_s, "__stk[__base + {}]", i).ok();
287            }
288            writeln!(w, "{}  __ret = {}({})", indent, func_name(*target), args_s)?;
289            writeln!(w, "{}  for __i = __base, __sp do", indent)?;
290            writeln!(w, "{}    __stk[__i] = nil", indent)?;
291            writeln!(w, "{}  end", indent)?;
292            writeln!(w, "{}  __sp = __base - 1", indent)?;
293            writeln!(w, "{}end", indent)?;
294        }
295        Op::Syscall { id, name, args } => {
296            let argc = *args as usize;
297            writeln!(w, "{}if __sp < {} then", indent, argc)?;
298            writeln!(w, "{}  -- syscall {} (id={}) argc={} on short stack", indent, name, id, argc)?;
299            writeln!(w, "{}  __ret = {}()", indent, name)?;
300            writeln!(w, "{}  __sp = 0", indent)?;
301            writeln!(w, "{}else", indent)?;
302            let base_expr = if argc == 0 { "__sp + 1".to_string() } else { format!("__sp - {} + 1", argc) };
303            writeln!(w, "{}  local __base = {}", indent, base_expr)?;
304            let mut args_s = String::new();
305            for i in 0..argc {
306                if i > 0 { args_s.push_str(", "); }
307                write!(&mut args_s, "__stk[__base + {}]", i).ok();
308            }
309            writeln!(w, "{}  __ret = {}({})", indent, name, args_s)?;
310            writeln!(w, "{}  for __i = __base, __sp do", indent)?;
311            writeln!(w, "{}    __stk[__i] = nil", indent)?;
312            writeln!(w, "{}  end", indent)?;
313            writeln!(w, "{}  __sp = __base - 1", indent)?;
314            writeln!(w, "{}end", indent)?;
315        }
316        Op::Jmp { .. } | Op::Jz { .. } | Op::Ret | Op::RetV => {}
317        Op::Unknown(opcode) => writeln!(w, "{}-- unknown opcode 0x{:02X}", indent, opcode)?,
318    }
319    Ok(())
320}
321
322fn emit_runtime_terminator<W: Write>(w: &mut W, indent: &str, term: BlockTerm, succs: &[usize]) -> IoResult<()> {
323    match term {
324        BlockTerm::Jmp | BlockTerm::Fallthrough => {
325            if let Some(&t) = succs.get(0) {
326                writeln!(w, "{}__pc = {}", indent, t)?;
327            } else {
328                writeln!(w, "{}return", indent)?;
329            }
330        }
331        BlockTerm::Jz => {
332            writeln!(w, "{}if __sp == 0 then", indent)?;
333            writeln!(w, "{}  -- jz on empty stack", indent)?;
334            if let Some(&t) = succs.get(0) {
335                writeln!(w, "{}  __pc = {}", indent, t)?;
336            } else {
337                writeln!(w, "{}  return", indent)?;
338            }
339            writeln!(w, "{}else", indent)?;
340            writeln!(w, "{}  local __cond = __stk[__sp]", indent)?;
341            writeln!(w, "{}  __stk[__sp] = nil", indent)?;
342            writeln!(w, "{}  __sp = __sp - 1", indent)?;
343            writeln!(w, "{}  if __cond == 0 then", indent)?;
344            match succs.get(0).copied() {
345                Some(tid) => writeln!(w, "{}    __pc = {}", indent, tid)?,
346                None => writeln!(w, "{}    return", indent)?,
347            }
348            writeln!(w, "{}  else", indent)?;
349            match succs.get(1).copied() {
350                Some(fid) => writeln!(w, "{}    __pc = {}", indent, fid)?,
351                None => writeln!(w, "{}    return", indent)?,
352            }
353            writeln!(w, "{}  end", indent)?;
354            writeln!(w, "{}end", indent)?;
355        }
356        BlockTerm::Ret => writeln!(w, "{}return", indent)?,
357        BlockTerm::RetV => {
358            writeln!(w, "{}if __sp == 0 then", indent)?;
359            writeln!(w, "{}  return nil", indent)?;
360            writeln!(w, "{}else", indent)?;
361            writeln!(w, "{}  return __stk[__sp]", indent)?;
362            writeln!(w, "{}end", indent)?;
363        }
364    }
365    Ok(())
366}
367
368fn is_linear_cfg(cfg: &FunctionCfg) -> bool {
369    if cfg.blocks.is_empty() {
370        return true;
371    }
372
373    for (i, b) in cfg.blocks.iter().enumerate() {
374        match b.term {
375            BlockTerm::Jz => return false,
376            BlockTerm::Ret | BlockTerm::RetV => {
377                if i + 1 != cfg.blocks.len() {
378                    return false;
379                }
380            }
381            BlockTerm::Jmp | BlockTerm::Fallthrough => {
382                if i + 1 == cfg.blocks.len() {
383                    if !b.succs.is_empty() {
384                        return false;
385                    }
386                } else if b.succs.as_slice() != [i + 1] {
387                    return false;
388                }
389            }
390        }
391    }
392
393    true
394}
395
396fn can_chain_to(cfg: &FunctionCfg, bid: usize) -> Option<usize> {
397    let b = &cfg.blocks[bid];
398    if !matches!(b.term, BlockTerm::Jmp | BlockTerm::Fallthrough) {
399        return None;
400    }
401    if b.succs.len() != 1 {
402        return None;
403    }
404    let sid = b.succs[0];
405    if sid <= bid {
406        return None;
407    }
408    let succ = &cfg.blocks[sid];
409    if succ.preds.as_slice() != [bid] {
410        return None;
411    }
412    Some(sid)
413}
414
415fn build_block_chains(cfg: &FunctionCfg) -> Vec<Vec<usize>> {
416    let n = cfg.blocks.len();
417    let mut is_cont = vec![false; n];
418    for bid in 0..n {
419        if let Some(sid) = can_chain_to(cfg, bid) {
420            is_cont[sid] = true;
421        }
422    }
423
424    let mut seen = vec![false; n];
425    let mut chains = Vec::new();
426
427    for head in 0..n {
428        if is_cont[head] || seen[head] {
429            continue;
430        }
431        let mut chain = vec![head];
432        seen[head] = true;
433        let mut cur = head;
434        while let Some(next) = can_chain_to(cfg, cur) {
435            if seen[next] {
436                break;
437            }
438            chain.push(next);
439            seen[next] = true;
440            cur = next;
441        }
442        chains.push(chain);
443    }
444
445    for bid in 0..n {
446        if !seen[bid] {
447            chains.push(vec![bid]);
448        }
449    }
450
451    chains.sort_by_key(|chain| chain[0]);
452    chains
453}
454
455fn emit_optimized_chain(
456    out: &mut Vec<u8>,
457    func: &Function,
458    cfg: &FunctionCfg,
459    chain: &[usize],
460    callee_args: &std::collections::BTreeMap<u32, u8>,
461    used_s: &mut BTreeSet<usize>,
462    non_volatile_count: u16,
463    volatile_count: u16,
464    indent: &str,
465) {
466    if chain.is_empty() {
467        return;
468    }
469
470    let first = &cfg.blocks[chain[0]];
471    let mut be = BlockEmitter::new(
472        indent,
473        func.args,
474        callee_args,
475        used_s,
476        non_volatile_count,
477        volatile_count,
478    );
479    be.init_stack(first.in_depth);
480
481    for (pos, &bid) in chain.iter().enumerate() {
482        let b = &cfg.blocks[bid];
483        let term_idx = if matches!(b.term, BlockTerm::Jmp | BlockTerm::Jz | BlockTerm::Ret | BlockTerm::RetV) {
484            b.inst_indices.clone().last()
485        } else {
486            None
487        };
488
489        for ii in b.inst_indices.clone() {
490            if Some(ii) == term_idx {
491                continue;
492            }
493            be.emit_inst(&func.insts[ii]);
494        }
495
496        if pos + 1 == chain.len() {
497            be.emit_terminator(b.term, &b.succs);
498        }
499    }
500
501    out.extend_from_slice(be.take_output().as_bytes());
502}
503
504fn emit_optimized_linear_body<W: Write>(
505    w: &mut W,
506    func: &Function,
507    cfg: &FunctionCfg,
508    callee_args: &std::collections::BTreeMap<u32, u8>,
509    used_s: &mut BTreeSet<usize>,
510    non_volatile_count: u16,
511    volatile_count: u16,
512) -> IoResult<()> {
513    let chain = build_block_chains(cfg);
514    let mut body = Vec::new();
515    for part in &chain {
516        emit_optimized_chain(
517            &mut body,
518            func,
519            cfg,
520            part,
521            callee_args,
522            used_s,
523            non_volatile_count,
524            volatile_count,
525            "  ",
526        );
527    }
528    w.write_all(&body)?;
529    writeln!(w, "end")?;
530    writeln!(w)?;
531    Ok(())
532}
533
534fn emit_optimized_dispatcher_body<W: Write>(
535    w: &mut W,
536    func: &Function,
537    cfg: &FunctionCfg,
538    callee_args: &std::collections::BTreeMap<u32, u8>,
539    used_s: &mut BTreeSet<usize>,
540    non_volatile_count: u16,
541    volatile_count: u16,
542) -> IoResult<()> {
543    let entry_pc = cfg.blocks.first().map(|b| b.id).unwrap_or(0);
544    let chains = build_block_chains(cfg);
545    let mut body: Vec<u8> = Vec::new();
546
547    writeln!(&mut body, "  local __pc = {}", entry_pc)?;
548    writeln!(&mut body, "  while true do")?;
549
550    for (i, chain) in chains.iter().enumerate() {
551        let head = chain[0];
552        if i == 0 {
553            writeln!(&mut body, "    if __pc == {} then", head)?;
554        } else {
555            writeln!(&mut body, "    elseif __pc == {} then", head)?;
556        }
557        emit_optimized_chain(
558            &mut body,
559            func,
560            cfg,
561            chain,
562            callee_args,
563            used_s,
564            non_volatile_count,
565            volatile_count,
566            "      ",
567        );
568    }
569
570    writeln!(&mut body, "    else")?;
571    writeln!(&mut body, "      return")?;
572    writeln!(&mut body, "    end")?;
573    writeln!(&mut body, "  end")?;
574    writeln!(&mut body, "end")?;
575    writeln!(&mut body)?;
576    w.write_all(&body)?;
577    Ok(())
578}
579
580fn emit_runtime_chain<W: Write>(
581    w: &mut W,
582    func: &Function,
583    cfg: &FunctionCfg,
584    chain: &[usize],
585    callee_args: &std::collections::BTreeMap<u32, u8>,
586    non_volatile_count: u16,
587    volatile_count: u16,
588    indent: &str,
589) -> IoResult<()> {
590    for (pos, &bid) in chain.iter().enumerate() {
591        let b = &cfg.blocks[bid];
592        let term_idx = if matches!(b.term, BlockTerm::Jmp | BlockTerm::Jz | BlockTerm::Ret | BlockTerm::RetV) {
593            b.inst_indices.clone().last()
594        } else {
595            None
596        };
597
598        for ii in b.inst_indices.clone() {
599            if Some(ii) == term_idx {
600                continue;
601            }
602            emit_runtime_inst(
603                w,
604                indent,
605                func,
606                &func.insts[ii],
607                callee_args,
608                non_volatile_count,
609                volatile_count,
610            )?;
611        }
612
613        if pos + 1 == chain.len() {
614            emit_runtime_terminator(w, indent, b.term, &b.succs)?;
615        }
616    }
617    Ok(())
618}
619
620fn emit_runtime_linear_body<W: Write>(
621    w: &mut W,
622    func: &Function,
623    cfg: &FunctionCfg,
624    callee_args: &std::collections::BTreeMap<u32, u8>,
625    non_volatile_count: u16,
626    volatile_count: u16,
627) -> IoResult<()> {
628    let chains = build_block_chains(cfg);
629    for chain in &chains {
630        emit_runtime_chain(
631            w,
632            func,
633            cfg,
634            chain,
635            callee_args,
636            non_volatile_count,
637            volatile_count,
638            "  ",
639        )?;
640    }
641    writeln!(w, "end")?;
642    writeln!(w)?;
643    Ok(())
644}
645
646fn emit_runtime_dispatcher_body<W: Write>(
647    w: &mut W,
648    func: &Function,
649    cfg: &FunctionCfg,
650    callee_args: &std::collections::BTreeMap<u32, u8>,
651    non_volatile_count: u16,
652    volatile_count: u16,
653) -> IoResult<()> {
654    let entry_pc = cfg.blocks.first().map(|b| b.id).unwrap_or(0);
655    let chains = build_block_chains(cfg);
656    writeln!(w, "  local __pc = {}", entry_pc)?;
657    writeln!(w, "  while true do")?;
658
659    for (i, chain) in chains.iter().enumerate() {
660        let head = chain[0];
661        if i == 0 {
662            writeln!(w, "    if __pc == {} then", head)?;
663        } else {
664            writeln!(w, "    elseif __pc == {} then", head)?;
665        }
666        emit_runtime_chain(
667            w,
668            func,
669            cfg,
670            chain,
671            callee_args,
672            non_volatile_count,
673            volatile_count,
674            "      ",
675        )?;
676    }
677
678    writeln!(w, "    else")?;
679    writeln!(w, "      return")?;
680    writeln!(w, "    end")?;
681    writeln!(w, "  end")?;
682    writeln!(w, "end")?;
683    writeln!(w)?;
684    Ok(())
685}
686
687fn emit_function<W: Write>(
688    w: &mut W,
689    func: &Function,
690    callee_args: &std::collections::BTreeMap<u32, u8>,
691    is_entry: bool,
692    non_volatile_count: u16,
693    volatile_count: u16,
694) -> IoResult<()> {
695    let cfg = build_cfg(func, callee_args);
696    let used_l = collect_used_frame_locals(func);
697
698    let mut sig = String::new();
699    if is_entry {
700        write!(&mut sig, "function main(").ok();
701    } else {
702        write!(&mut sig, "function {}(", func_name(func.start_addr)).ok();
703    }
704    for i in 0..(func.args as usize) {
705        if i > 0 {
706            sig.push_str(", ");
707        }
708        sig.push_str(&format!("a{}", i));
709    }
710    sig.push(')');
711    writeln!(w, "{}", sig)?;
712
713    if !used_l.is_empty() {
714        write!(w, "  local ")?;
715        for (i, lidx) in used_l.iter().enumerate() {
716            if i > 0 {
717                write!(w, ", ")?;
718            }
719            write!(w, "l{}", lidx)?;
720        }
721        writeln!(w)?;
722    }
723
724    writeln!(w, "  local __ret = nil")?;
725
726    if cfg.stack_consistent {
727        let mut used_s: BTreeSet<usize> = BTreeSet::new();
728        let mut body = Vec::new();
729        if is_linear_cfg(&cfg) {
730            emit_optimized_linear_body(
731                &mut body,
732                func,
733                &cfg,
734                callee_args,
735                &mut used_s,
736                non_volatile_count,
737                volatile_count,
738            )?;
739        } else {
740            emit_optimized_dispatcher_body(
741                &mut body,
742                func,
743                &cfg,
744                callee_args,
745                &mut used_s,
746                non_volatile_count,
747                volatile_count,
748            )?;
749        }
750
751        if !used_s.is_empty() {
752            write!(w, "  local ")?;
753            for (i, sidx) in used_s.iter().enumerate() {
754                if i > 0 {
755                    write!(w, ", ")?;
756                }
757                write!(w, "S{}", sidx)?;
758            }
759            writeln!(w)?;
760        }
761
762        w.write_all(&body)?;
763    } else {
764        writeln!(w, "  local __stk = {{}}")?;
765        writeln!(w, "  local __sp = 0")?;
766        if is_linear_cfg(&cfg) {
767            emit_runtime_linear_body(
768                w,
769                func,
770                &cfg,
771                callee_args,
772                non_volatile_count,
773                volatile_count,
774            )?;
775        } else {
776            emit_runtime_dispatcher_body(
777                w,
778                func,
779                &cfg,
780                callee_args,
781                non_volatile_count,
782                volatile_count,
783            )?;
784        }
785    }
786
787    Ok(())
788}
789
790pub fn emit_lua_script<W: Write>(w: &mut W, parser: &Parser, functions: &[Function]) -> IoResult<()> {
791    writeln!(w, "-- Decompiled from HCB bytecode")?;
792    writeln!(w, "-- Title: {}", parser.get_title())?;
793    let (sw, sh) = parser.get_screen_size();
794    writeln!(w, "-- Screen: {}x{}", sw, sh)?;
795    writeln!(w)?;
796
797    let non_volatile_count = parser.get_non_volatile_global_count();
798    let volatile_count = parser.get_volatile_global_count();
799    emit_name_decls(w, "global", "g", non_volatile_count)?;
800    emit_name_decls(w, "volatile global", "vg", volatile_count)?;
801
802    let (mut need_g, need_gt, need_lt) = scan_runtime_tables(functions);
803    need_g |= uses_fallback_global(functions, non_volatile_count, volatile_count);
804    if need_g {
805        writeln!(w, "G = G or {{}}")?;
806    }
807    if need_gt {
808        writeln!(w, "GT = GT or {{}}")?;
809    }
810    if need_lt {
811        writeln!(w, "LT = LT or {{}}")?;
812    }
813    if non_volatile_count > 0 || volatile_count > 0 || need_g || need_gt || need_lt {
814        writeln!(w)?;
815    }
816
817    let mut callee_args = std::collections::BTreeMap::<u32, u8>::new();
818    for f in functions {
819        callee_args.insert(f.start_addr, f.args);
820    }
821
822    for func in functions {
823        emit_function(
824            w,
825            func,
826            &callee_args,
827            parser.get_entry_point() == func.start_addr,
828            non_volatile_count,
829            volatile_count,
830        )?;
831    }
832
833    Ok(())
834}