lua_decompile/
lib.rs

1#![allow(dead_code)]
2
3use std::slice::IterMut;
4
5use itertools::{Itertools, MultiPeek};
6use lua_bytecode::{
7    Bytecode, Proto,
8    constant::Constant,
9    opcode::{Instruction, Opcode},
10};
11
12#[cfg(feature = "lua51")]
13use lua_bytecode::lua51::LuaBytecode;
14#[cfg(feature = "lua51")]
15use lua_bytecode::opcode::{LuaInstruction, LuaOpcode};
16
17const VARIABLE_NAME: &str = "r";
18const CONSTANT_INDEX: u16 = 256;
19
20#[derive(Clone, Debug)]
21enum InstructionHistory {
22    LuaInstruction,
23}
24
25#[derive(Clone, Default, Debug)]
26struct DecompilerContext {
27    indentation: u32,
28}
29
30#[derive(Clone, Default, Debug)]
31pub struct Decompiler {
32    output: String,
33    blocks: Vec<Block>,
34    history: Vec<InstructionHistory>,
35    context: DecompilerContext,
36    counter: u64,
37}
38
39#[derive(Clone, Default, Debug)]
40enum LuaValue {
41    #[default]
42    Nil,
43    Bool(bool),
44    Number(f64),
45    String(Vec<u8>),
46}
47
48impl LuaValue {
49    fn from(constant: Constant) -> Self {
50        match constant {
51            Constant::Nil => LuaValue::Nil,
52            Constant::Bool(value) => LuaValue::Bool(value),
53            Constant::Number(value) => LuaValue::Number(value),
54            Constant::String(value) => LuaValue::String(value),
55        }
56    }
57}
58
59impl std::fmt::Display for LuaValue {
60    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
61        match self {
62            LuaValue::Nil => write!(f, "nil"),
63            LuaValue::Number(value) => write!(f, "{value}"),
64            LuaValue::Bool(value) => write!(f, "{value}"),
65            LuaValue::String(value) => {
66                write!(f, "\"{}\"", String::from_utf8_lossy(value).to_owned())
67            }
68        }
69    }
70}
71
72#[derive(Clone, Default, Debug)]
73struct Assignment {
74    name: String,
75}
76
77#[derive(Clone, Default, Debug)]
78struct IfStatement {}
79
80#[derive(Clone, Debug)]
81enum Statement {
82    If(IfStatement),
83    Assignment(Assignment),
84}
85
86// TODO: in the future we should be using an ast to re-create our lua code
87#[derive(Clone, Default, Debug)]
88struct Block {
89    statements: Vec<Statement>,
90}
91
92enum VariableOrConstant {
93    Constant(LuaValue),
94    Variable(String),
95}
96
97impl std::fmt::Display for VariableOrConstant {
98    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
99        match self {
100            VariableOrConstant::Constant(value) => write!(f, "{}", value),
101            VariableOrConstant::Variable(string) => write!(f, "{}", string),
102        }
103    }
104}
105
106impl VariableOrConstant {
107    fn from(proto: &Proto, index: u32) -> Self {
108        if index >= CONSTANT_INDEX as u32 {
109            VariableOrConstant::Constant(LuaValue::from(
110                proto.constants[(index - CONSTANT_INDEX as u32) as usize].clone(),
111            ))
112        } else {
113            VariableOrConstant::Variable(format!("{VARIABLE_NAME}{index}"))
114        }
115    }
116}
117
118fn opcode_operator(opcode: &Opcode) -> &str {
119    match opcode {
120        Opcode::LuaOpcode(op) => match op {
121            LuaOpcode::Add => "+",
122            LuaOpcode::Sub => "-",
123            LuaOpcode::Mul => "*",
124            LuaOpcode::Div => "/",
125            LuaOpcode::Mod => "%",
126            LuaOpcode::Pow => "^",
127
128            LuaOpcode::Unm => "-",
129            LuaOpcode::Not => "not",
130            LuaOpcode::Len => "#",
131            LuaOpcode::Concat => "..",
132
133            LuaOpcode::Eq => "==",
134            LuaOpcode::Lt => "<",
135            LuaOpcode::Le => "<=",
136
137            _ => "?",
138        },
139
140        _ => todo!(),
141    }
142}
143
144fn is_opcode_condtional(opcode: &Opcode) -> bool {
145    match opcode {
146        Opcode::LuaOpcode(op) => match op {
147            LuaOpcode::Test | LuaOpcode::Eq | LuaOpcode::Lt | LuaOpcode::Le => true,
148            _ => false,
149        },
150    }
151}
152
153fn math_operation(output: &mut String, proto: &Proto, instruction: &Instruction) {
154    let a = instruction.a();
155    let b = VariableOrConstant::from(proto, instruction.b());
156    let c = VariableOrConstant::from(proto, instruction.c());
157
158    let binding = instruction.opcode();
159    let sign = opcode_operator(&binding);
160    output.push_str(&format!("{VARIABLE_NAME}{a} = {b} {sign} {c}\n"));
161}
162
163impl Decompiler {
164    pub fn decompile(data: &[u8]) -> Result<String, String> {
165        let bytecode = <Bytecode as LuaBytecode>::from(data)?;
166        let mut decompiler = Decompiler::default();
167
168        let main_proto = bytecode
169            .protos
170            .get(bytecode.main_proto_id as usize)
171            .unwrap();
172
173        decompiler.decompile_proto(main_proto)?;
174        Ok(decompiler.output)
175    }
176
177    pub fn decompile_proto(self: &mut Self, proto: &Proto) -> Result<(), String> {
178        let mut binding = proto.instructions.clone();
179        let mut instructions = binding.iter_mut().multipeek();
180
181        self.decompile_block(proto, &mut instructions, None)?;
182        Ok(())
183    }
184
185    pub fn decompile_block(
186        self: &mut Self,
187        proto: &Proto,
188        instructions: &mut MultiPeek<IterMut<Instruction>>,
189        block_size: Option<u64>,
190    ) -> Result<(), String> {
191        let count = self.counter;
192
193        loop {
194            if let Some(i) = block_size {
195                if self.counter - count == i {
196                    break;
197                }
198            }
199
200            let instruction = match instructions.next() {
201                None => break,
202                Some(instruction) => instruction,
203            };
204
205            self.output
206                .push_str(&"\t".repeat(self.context.indentation as usize));
207
208            self.counter += 1;
209            match instruction.opcode() {
210                Opcode::LuaOpcode(op) => match op {
211                    LuaOpcode::Move => {
212                        let a = instruction.a();
213                        let b = instruction.b();
214                        self.output
215                            .push_str(&format!("{VARIABLE_NAME}{a} = {VARIABLE_NAME}{b}\n"));
216                    }
217
218                    LuaOpcode::LoadK => {
219                        let a = instruction.a();
220                        let index = instruction.bx();
221                        let constant = LuaValue::from(proto.constants[index as usize].clone());
222
223                        self.output
224                            .push_str(&format!("{VARIABLE_NAME}{a} = {constant}\n"));
225                    }
226
227                    LuaOpcode::LoadBool => {
228                        let a = instruction.a();
229                        let value = instruction.b() > 0;
230                        self.output
231                            .push_str(&format!("{VARIABLE_NAME}{a} = {value}\n"));
232                    }
233
234                    LuaOpcode::LoadNil => todo!(),
235                    LuaOpcode::GetUpval => todo!(),
236
237                    LuaOpcode::GetGlobal => {
238                        let a = instruction.a();
239                        let index = instruction.bx();
240                        let constant = LuaValue::from(proto.constants[index as usize].clone());
241
242                        let global = match constant {
243                            LuaValue::String(string) => {
244                                String::from_utf8_lossy(&string).to_string()
245                            }
246                            _ => unreachable!(),
247                        };
248
249                        self.output
250                            .push_str(&format!("{VARIABLE_NAME}{a} = {global}\n"));
251                    }
252
253                    LuaOpcode::GetTable => todo!(),
254                    LuaOpcode::SetGlobal => todo!(),
255                    LuaOpcode::SetUpval => todo!(),
256                    LuaOpcode::SetTable => todo!(),
257                    LuaOpcode::NewTable => todo!(),
258                    LuaOpcode::Self_ => todo!(),
259
260                    LuaOpcode::Add
261                    | LuaOpcode::Sub
262                    | LuaOpcode::Mul
263                    | LuaOpcode::Div
264                    | LuaOpcode::Mod
265                    | LuaOpcode::Pow => math_operation(&mut self.output, proto, instruction),
266
267                    LuaOpcode::Unm | LuaOpcode::Not | LuaOpcode::Len => {
268                        let sign = instruction.opcode();
269                        let sign = opcode_operator(&sign);
270
271                        let a = instruction.a();
272                        let b = instruction.b();
273                        self.output
274                            .push_str(&format!("{VARIABLE_NAME}{a} = {sign}{VARIABLE_NAME}{b}\n"));
275                    }
276
277                    LuaOpcode::Concat => {
278                        let a = instruction.a();
279                        self.output.push_str(&format!("{VARIABLE_NAME}{a} = "));
280
281                        let b = instruction.b();
282                        let c = instruction.c();
283
284                        for i in b..c + 1 {
285                            self.output.push_str(&format!("{VARIABLE_NAME}{i}"));
286                            if i < c {
287                                self.output.push_str(" .. ");
288                            }
289                        }
290
291                        self.output.push_str("\n");
292                    }
293
294                    LuaOpcode::Jmp => {
295                        todo!();
296                        // positive jmps by themselves should skip the next instructions
297                        //let sbx = instruction.sbx();
298
299                        //for _ in 0..sbx {
300                        //    instructions.next();
301                        //}
302                    }
303
304                    LuaOpcode::Test | LuaOpcode::Eq | LuaOpcode::Lt | LuaOpcode::Le => {
305                        self.if_condition(proto, instruction, instructions, false)?;
306                    }
307
308                    LuaOpcode::TestSet => todo!(),
309
310                    LuaOpcode::Call => {
311                        self.function_call(instruction);
312                    }
313
314                    LuaOpcode::TailCall => todo!(),
315
316                    LuaOpcode::Return => {
317                        let a = instruction.a();
318                        let b = instruction.b();
319
320                        if b > 1 {
321                            self.output.push_str("return ");
322                        }
323
324                        for i in 0..b - 1 {
325                            let x = a + i;
326                            self.output.push_str(&format!("{VARIABLE_NAME}{x}"));
327                            if i < b - 2 {
328                                self.output.push_str(", ");
329                            }
330                        }
331                    }
332
333                    LuaOpcode::ForLoop => todo!(),
334                    LuaOpcode::ForPrep => {
335                        self.for_loop(proto, instruction, instructions)?;
336                    }
337
338                    LuaOpcode::TForLoop => todo!(),
339                    LuaOpcode::SetList => todo!(),
340                    LuaOpcode::Close => todo!(),
341                    LuaOpcode::Closure => todo!(),
342                    LuaOpcode::Vararg => todo!(),
343                },
344
345                _ => todo!(),
346            }
347        }
348
349        Ok(())
350    }
351
352    fn function_call(&mut self, instruction: &mut Instruction) {
353        let callee = instruction.a();
354        let argument_count = instruction.b();
355
356        let r = instruction.c();
357        for i in (0..r - 1).rev() {
358            let x = r - i;
359            self.output.push_str(&format!("{VARIABLE_NAME}{x}"));
360            if i > 0 {
361                self.output.push_str(", ");
362            }
363        }
364
365        if r > 1 {
366            self.output.push_str(" = ");
367        }
368
369        self.output.push_str(&format!("{VARIABLE_NAME}{callee}("));
370
371        let x = callee + argument_count;
372        for i in callee + 1..x {
373            self.output.push_str(&format!("{VARIABLE_NAME}{i}"));
374            if i != x - 1 {
375                self.output.push_str(", ");
376            }
377        }
378
379        self.output.push_str(")\n");
380    }
381
382    fn if_condition(
383        &mut self,
384        proto: &Proto,
385        instruction: &mut Instruction,
386        instructions: &mut MultiPeek<IterMut<Instruction>>,
387        else_if_continuation: bool,
388    ) -> Result<(), String> {
389        let opcode = instruction.opcode();
390
391        let jump = instructions.next().unwrap();
392        assert_eq!(jump.opcode(), Opcode::LuaOpcode(LuaOpcode::Jmp));
393
394        self.counter += 1;
395        let mut jump_count = jump.sbx();
396
397        let mut condition = String::new();
398        let mut condition_buffer = vec![(instruction, jump)];
399
400        // if there are multiple TEST + JMP instructions in a row
401        // it's a inlined conditional, i.e
402        // ```lua
403        // if ([x.. and y..]) then end
404        // ```
405
406        loop {
407            match instructions.peek() {
408                Some(instruction) => {
409                    if !is_opcode_condtional(&instruction.opcode()) {
410                        break;
411                    }
412                }
413                None => break,
414            }
415
416            match instructions.peek() {
417                Some(instruction) => match instruction.opcode() {
418                    Opcode::LuaOpcode(op) => match op {
419                        LuaOpcode::Jmp => {}
420                        _ => break,
421                    },
422                },
423                None => break,
424            }
425
426            let test = instructions.next().unwrap();
427            let jump = instructions.next().unwrap();
428            assert_eq!(jump.opcode(), Opcode::LuaOpcode(LuaOpcode::Jmp));
429
430            jump_count = jump.sbx();
431            self.counter += 2;
432
433            condition_buffer.push((test, jump));
434        }
435
436        let count = condition_buffer.len();
437        for (index, (test, jump)) in condition_buffer.iter().enumerate() {
438            let index = index + 1;
439            let r = test.a();
440
441            if matches!(test.opcode(), Opcode::LuaOpcode(LuaOpcode::Test))
442                && (jump.sbx() / 2) as usize == count - index
443            {
444                condition.push_str(&format!("{VARIABLE_NAME}{r} or "));
445            } else {
446                match test.opcode() {
447                    Opcode::LuaOpcode(op) => match op {
448                        LuaOpcode::Test => {
449                            let c = test.c() != 0;
450
451                            let not = if c { "not " } else { "" };
452                            let and = if index != count { " and " } else { "" };
453                            condition.push_str(&format!("{}{}{}{}", not, VARIABLE_NAME, r, and));
454                        }
455
456                        LuaOpcode::Eq | LuaOpcode::Lt | LuaOpcode::Le => {
457                            let sign = opcode_operator(&opcode);
458
459                            let a = test.a();
460                            let b = VariableOrConstant::from(proto, test.b());
461                            let c = VariableOrConstant::from(proto, test.c());
462
463                            let is_even = jump.sbx() % 2 == 0;
464                            let post_operator = if index != count {
465                                if a == 0 && is_even { " and " } else { " or " }
466                            } else {
467                                ""
468                            };
469
470                            condition.push_str(&format!("{b} {sign} {c}{post_operator}"));
471                        }
472
473                        _ => unreachable!(),
474                    },
475                }
476            }
477        }
478
479        let keyword = if !else_if_continuation {
480            "if"
481        } else {
482            "elseif"
483        };
484
485        self.output
486            .push_str(&format!("{keyword} {condition} then\n"));
487
488        instructions.reset_peek();
489        for _ in 0..jump_count - 1 {
490            instructions.peek();
491        }
492
493        let has_else = if let Some(peak) = instructions.peek() {
494            matches!(peak.opcode(), Opcode::LuaOpcode(LuaOpcode::Jmp))
495        } else {
496            false
497        };
498
499        if has_else {
500            jump_count -= 1;
501        }
502
503        self.context.indentation += 1;
504        self.decompile_block(proto, instructions, Some(jump_count as u64))?;
505
506        let end_statement = if has_else {
507            let jump = instructions.next().unwrap();
508            self.counter += 1;
509
510            let has_else_if = if let Some(peek) = instructions.peek() {
511                is_opcode_condtional(&peek.opcode())
512            } else {
513                false
514            };
515
516            if has_else_if {
517                self.context.indentation -= 1;
518                self.if_condition(proto, instructions.next().unwrap(), instructions, true)?;
519            } else {
520                self.output.push_str("else\n");
521                self.decompile_block(proto, instructions, Some(jump.sbx() as u64))?;
522            }
523
524            !has_else_if
525        } else {
526            true
527        };
528
529        if end_statement {
530            self.output.push_str("end\n");
531            self.context.indentation -= 1;
532        }
533
534        Ok(())
535    }
536
537    fn for_loop(
538        &mut self,
539        proto: &Proto,
540        instruction: &mut Instruction,
541        instructions: &mut MultiPeek<IterMut<'_, Instruction>>,
542    ) -> Result<(), String> {
543        let r = instruction.a();
544        let r0 = format!("{VARIABLE_NAME}{}", r + 0);
545        let r1 = format!("{VARIABLE_NAME}{}", r + 1);
546        let r2 = format!("{VARIABLE_NAME}{}", r + 2);
547
548        self.output
549            .push_str(&format!("for i = {r0}, {r1}, {r2} do\n"));
550
551        self.context.indentation += 1;
552        self.decompile_block(proto, instructions, Some(instruction.sbx() as u64))?;
553        self.context.indentation -= 1;
554
555        let close = instructions.next().unwrap();
556        assert_eq!(close.opcode(), Opcode::LuaOpcode(LuaOpcode::ForLoop));
557
558        self.output.push_str("end\n");
559
560        Ok(())
561    }
562}