Skip to main content

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