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#[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 }
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 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}