/* Residual - Virtual machine to run LucasArts' 3D adventure games * * Residual is the legal property of its developers, whose names * are too numerous to list here. Please refer to the AUTHORS * file distributed with this source distribution. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * * $URL$ * $Id$ * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //hack below: shutup linker int g_flags = 0; ResourceLoader *g_resourceloader = 0; Driver *g_driver = 0; Common::File *ResourceLoader::openNewStream(const char *filename) const { return NULL; } // Provide debug.cpp functions which don't call SDL_Quit. void warning(const char *fmt, ...) { fprintf(stderr, "WARNING: "); va_list va; va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); fprintf(stderr, "\n"); } void error(const char *fmt, ...) { fprintf(stderr, "ERROR: "); va_list va; va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); fprintf(stderr, "\n"); exit(1); } static bool translateStrings = false; class Expression; void decompile(std::ostream &os, TProtoFunc *tf, std::string indent_str, Expression **upvals, int num_upvals); std::string localname(TProtoFunc *tf, int n) { LocVar *l = tf->locvars; if (l != NULL) for (int i = 0; i < n; i++, l++) if (l->varname == NULL) { l = NULL; break; } if (l != NULL) return l->varname->str; else { std::ostringstream s; if (n < tf->code[1]) s << "arg" << n + 1; else s << "local" << n - tf->code[1] + 1; return s.str(); } } class Expression { public: Expression(Byte *p) : pos(p) { } Byte *pos; // Position just after the expression // is pushed onto the stack virtual void print(std::ostream &os) const = 0; virtual int precedence() const { return 100; } virtual ~Expression() { } }; inline std::ostream& operator <<(std::ostream &os, const Expression &e) { e.print(os); return os; } class NumberExpr : public Expression { public: NumberExpr(Byte *p, float val) : Expression(p), value(val) { } float value; void print(std::ostream &os) const { os << value; } }; class VarExpr : public Expression { public: VarExpr(Byte *p, std::string varname) : Expression(p), name(varname) { } std::string name; void print(std::ostream &os) const { os << name; } }; class StringExpr : public Expression { public: StringExpr(Byte *p, TaggedString *txt) : Expression(p), text(txt) { } TaggedString *text; bool validIdentifier() const { if (text->u.s.len == 0) return false; if (isdigit(text->str[0])) return false; if (text->str[0] >= '0' && text->str[0] <= '9') return false; for (int i = 0; i < text->u.s.len; i++) { char c = text->str[i]; if ((! isalnum(text->str[0])) && c != '_') return false; } return true; } void print(std::ostream &os) const { static const char *specials = "\a\b\f\n\r\t\v\\\""; static const char *special_text[] = { "\\a", "\\b", "\\f", "\\n", "\\r", "\\t", "\\v", "\\\\", "\\\"" }; os << "\""; std::string str(text->str, text->u.s.len); if (translateStrings) str = g_localizer->localize(str.c_str()); for (std::string::iterator i = str.begin(); i != str.end(); i++) { unsigned char c = *i; if (strchr(specials, c)) { int i = strchr(specials, c) - specials; os << special_text[i]; } else if (! isprint(c)) os << "\\" << int(c >> 6) << int((c >> 3) & 7) << int(c & 7); else os << c; } os << "\""; } }; class FuncExpr : public Expression { public: FuncExpr(Byte *p, TProtoFunc *tf0, std::string is) : Expression(p), indent_str(is), tf(tf0), upvals(NULL), num_upvals(0) { } std::string indent_str; TProtoFunc *tf; Expression **upvals; int num_upvals; void print(std::ostream &os) const { os << "function("; for (int i = 0; i < tf->code[1]; i++) { os << localname(tf, i); if (i + 1 < tf->code[1]) os << ", "; } os << ") -- line " << tf->lineDefined << std::endl; decompile(os, tf, indent_str + std::string(4, ' '), upvals, num_upvals); os << indent_str << "end"; } ~FuncExpr() { for (int i = 0; i < num_upvals; i++) delete upvals[i]; delete[] upvals; } }; class IndexExpr : public Expression { public: IndexExpr(Byte *p, Expression *tbl, Expression *i) : Expression(p), table(tbl), index(i) { } Expression *table, *index; void print(std::ostream &os) const { table->print(os); StringExpr *field = dynamic_cast(index); if (field != NULL && field->validIdentifier()) os << "." << field->text->str; else os << "[" << *index << "]"; } ~IndexExpr() { delete table; delete index; } }; class SelfExpr : public IndexExpr { public: SelfExpr(Byte *p, Expression *tbl, StringExpr *i) : IndexExpr(p, tbl, i) { } void print(std::ostream &os) const { StringExpr *field = static_cast(index); os << *table << ":" << field->text->str; } }; class FuncCallExpr : public Expression { public: FuncCallExpr(Byte *p) : Expression(p) { } int num_args; Expression **args; Expression *func; void print(std::ostream &os) const { os << *func << "("; int i = 0; // Skip implicit self argument in a:b(c) if (dynamic_cast(func) != NULL) i = 1; for (; i < num_args; i++) { args[i]->print(os); if (i + 1 < num_args) os << ", "; } os << ")"; } ~FuncCallExpr() { for (int i = 0; i < num_args; i++) delete args[i]; delete args; delete func; } }; class ArrayExpr : public Expression { public: ArrayExpr(Byte *p) : Expression(p) { } typedef std::pair mapping; typedef std::list mapping_list; mapping_list mappings; void print(std::ostream &os) const { os << "{"; mapping_list::const_iterator i = mappings.begin(); while (i != mappings.end()) { if (i->first != NULL) { StringExpr *field = dynamic_cast(i->first); if (field != NULL && field->validIdentifier()) os << " " << field->text->str; else os << " [" << *i->first << "]"; os << " ="; } os << " " << *i->second; i++; if (i != mappings.end()) os << ","; } os << " }"; } ~ArrayExpr() { for (mapping_list::iterator i = mappings.begin(); i != mappings.end(); i++) { delete i->first; delete i->second; } } }; class BinaryExpr : public Expression { public: BinaryExpr(Byte *ps, Expression *l, Expression *r, int p, bool ra, std::string o) : Expression(ps), left(l), right(r), prec(p), right_assoc(ra), op(o) { } Expression *left, *right; int prec; bool right_assoc; std::string op; int precedence() const { return prec; } void print(std::ostream &os) const { if (left->precedence() < prec || (left->precedence() == prec && right_assoc)) os << "(" << *left << ")"; else os << *left; os << op; if (right->precedence() < prec || (right->precedence() == prec && ! right_assoc)) os << "(" << *right << ")"; else os << *right; } ~BinaryExpr() { delete left; delete right; } }; class UnaryExpr : public Expression { public: UnaryExpr(Byte *ps, Expression *a, int p, std::string o) : Expression(ps), arg(a), prec(p), op(o) { } Expression *arg; int prec; std::string op; int precedence() const { return prec; } void print(std::ostream &os) const { os << op; if (arg->precedence() < prec) os << "(" << *arg << ")"; else os << *arg; } ~UnaryExpr() { delete arg; } }; typedef std::stack ExprStack; class Decompiler { public: void decompileRange(Byte *start, Byte *end); std::ostream *os; ExprStack *stk; TProtoFunc *tf; std::string indent_str; Byte *break_pos; Expression **upvals; int num_upvals; std::multiset *local_var_defs; private: void do_multi_assign(Byte *&start); void do_binary_op(Byte *pos, int prec, bool right_assoc, std::string op); void do_unary_op(Byte *pos, int prec, std::string op); static bool is_expr_opc(Byte opc); void get_else_part(Byte *start, Byte *&if_part_end, bool &has_else, Byte *&else_part_end); }; // Scan for a series of assignments void Decompiler::do_multi_assign(Byte *&start) { std::queue results; ExprStack values; bool done; int num_tables = 0; do { int aux, opc; done = false; opc = *start++; switch (opc) { case SETLOCAL: aux = *start++; goto setlocal; case SETLOCAL0: case SETLOCAL1: case SETLOCAL2: case SETLOCAL3: case SETLOCAL4: case SETLOCAL5: case SETLOCAL6: case SETLOCAL7: aux = opc - SETLOCAL0; setlocal: results.push(new VarExpr(start, localname(tf, aux))); break; case SETGLOBAL: aux = *start++; goto setglobal; case SETGLOBAL0: case SETGLOBAL1: case SETGLOBAL2: case SETGLOBAL3: case SETGLOBAL4: case SETGLOBAL5: case SETGLOBAL6: case SETGLOBAL7: aux = opc - SETGLOBAL0; goto setglobal; case SETGLOBALW: aux = start[0] | (start[1] << 8); start += 2; setglobal: results.push(new VarExpr(start, svalue(tf->consts + aux))); break; case SETTABLE: start++; // assume offset is correct case SETTABLE0: results.push(new IndexExpr(start, NULL, NULL)); num_tables++; // this needs stuff from farther up the stack, wait until // it's available break; default: start--; done = true; } if (! done) { Expression *e = stk->top(); // Check for fake result from function calls with multiple return values VarExpr *v = dynamic_cast(e); if (v != NULL && v->name == "") delete e; else values.push(e); stk->pop(); } } while (! done); // Check for popping tables and indices if (num_tables > 0 && (*start == POP || *start == POP0 || *start == POP1)) { start++; if (start[-1] == POP) start++; } // Now get actual tables and indices from the stack, reversing // the list to the right order at the same time ExprStack results2; while (! results.empty()) { Expression *var = results.front(); results.pop(); IndexExpr *tbl = dynamic_cast(var); if (tbl != NULL) { tbl->index = stk->top(); stk->pop(); tbl->table = stk->top(); stk->pop(); } results2.push(var); } *os << indent_str; while (! results2.empty()) { Expression *var = results2.top(); results2.pop(); *os << *var; delete var; if (! results2.empty()) *os << ", "; } *os << " = "; while (! values.empty()) { Expression *val = values.top(); values.pop(); *os << *val; delete val; if (! values.empty()) *os << ", "; } *os << std::endl; } void Decompiler::do_binary_op(Byte *pos, int prec, bool right_assoc, std::string op) { Expression *right = stk->top(); stk->pop(); Expression *left = stk->top(); stk->pop(); stk->push(new BinaryExpr(pos, left, right, prec, right_assoc, op)); } void Decompiler::do_unary_op(Byte *pos, int prec, std::string op) { Expression *arg = stk->top(); stk->pop(); stk->push(new UnaryExpr(pos, arg, prec, op)); } // Provide instruction lengths to make it easy to scan through instructions int instr_lens[] = { 1, // ENDCODE 2, // PUSHNIL 1, // PUSHNIL0 2, // PUSHNUMBER 1, 1, 1, // PUSHNUMBER0..2 3, // PUSHNUMBERW 2, // PUSHCONSTANT 1, 1, 1, 1, 1, 1, 1, 1, // PUSHCONSTANT0..7 3, // PUSHCONSTANTW 2, // PUSHUPVALUE 1, 1, // PUSHUPVALUE0,1 2, // PUSHLOCAL 1, 1, 1, 1, 1, 1, 1, 1, // PUSHLOCAL0..7 2, // GETGLOBAL 1, 1, 1, 1, 1, 1, 1, 1, // GETGLOBAL0..7 3, // GETGLOBALW 1, // GETTABLE 2, // GETDOTTED 1, 1, 1, 1, 1, 1, 1, 1, // GETDOTTED0..7 3, // GETDOTTEDW 2, // PUSHSELF 1, 1, 1, 1, 1, 1, 1, 1, // PUSHSELF0..7 3, // PUSHSELFW 2, // CREATEARRAY 1, 1, // CREATEARRAY0,1 3, // CREATEARRAYW 2, // SETLOCAL 1, 1, 1, 1, 1, 1, 1, 1, // SETLOCAL0..7 2, // SETGLOBAL 1, 1, 1, 1, 1, 1, 1, 1, // SETGLOBAL0..7 3, // SETGLOBALW 1, // SETTABLE0 2, // SETTABLE 3, // SETLIST 2, // SETLIST0 4, // SETLISTW 2, // SETMAP 1, // SETMAP0 1, // EQOP 1, // NEQOP 1, // LTOP 1, // LEOP 1, // GTOP 1, // GEOP 1, // ADDOP 1, // SUBOP 1, // MULTOP 1, // DIVOP 1, // POWOP 1, // CONCOP 1, // MINUSOP 1, // NOTOP 2, 3, // ONTJMP, ONTJMPW 2, 3, // ONFJMP, ONFJMPW 2, 3, // JMP, JMPW 2, 3, // IFFJMP, IFFJMPW 2, 3, // IFTUPJMP, IFTUPJMPW 2, 3, // IFFUPJMP, IFFUPJMPW 2, // CLOSURE 1, 1, // CLOSURE0,1 3, // CALLFUNC 2, 2, // CALLFUNC0,1 2, // RETCODE 2, // SETLINE 3, // SETLINEW 2, // POP 1, 1 // POP0,1 }; bool Decompiler::is_expr_opc(Byte opc) { if (opc >= PUSHNIL && opc <= CREATEARRAYW) return true; if (opc == SETLIST0) return true; if (opc >= SETMAP && opc <= ONFJMPW) return true; if (opc >= CLOSURE && opc <= CLOSURE1) return true; if (opc == CALLFUNC1 || opc == SETLINE || opc == SETLINEW) return true; return false; } // Check for JMP or JMPW at end of "if" part void Decompiler::get_else_part(Byte *start, Byte *&if_part_end, bool &has_else, Byte *&else_part_end) { Byte *last_instr = NULL; has_else = false; else_part_end = NULL; for (Byte *instr_scan = start; instr_scan < if_part_end; instr_scan += instr_lens[*instr_scan]) last_instr = instr_scan; if (last_instr != NULL && (*last_instr == JMP || *last_instr == JMPW)) { has_else = true; else_part_end = if_part_end + last_instr[1]; if (*last_instr == JMPW) else_part_end += (last_instr[2] << 8); if_part_end = last_instr; } } void Decompiler::decompileRange(Byte *start, Byte *end) { // First, scan for IFFUPJMP, which is used for repeat/until, so // we can recognize the start of such loops. We only keep the // last value to match each address, which represents the outermost // repeat/until loop starting at that point. std::map rev_iffupjmp_map; for (Byte *scan = start; end == NULL || scan < end; scan += instr_lens[*scan]) { if (*scan == IFFUPJMP) rev_iffupjmp_map[scan + 2 - scan[1]] = scan; else if (*scan == IFFUPJMPW) rev_iffupjmp_map[scan + 3 - (scan[1] | (scan[2] << 8))] = scan; else if (*scan == ENDCODE) break; } while (end == NULL || start < end) { int locs_here = local_var_defs->count(start); if (locs_here > 0) { // There were local variable slots just pushed onto the stack // Print them out (in the second pass) // First, if there are multiple defined, it must be from // local x, y, z = f() or local a, b. So just ignore the extra // entries. for (int i = 1; i < locs_here; i++) { delete stk->top(); stk->pop(); } Expression *def = stk->top(); stk->pop(); // Print the local variable names, and at the same time push // fake values onto the stack *os << indent_str << "local "; for (int i = 0; i < locs_here; i++) { std::string locname = localname(tf, tf->code[1] + stk->size()); *os << locname; if (i + 1 < locs_here) *os << ", "; stk->push(new VarExpr(start, "<" + locname + " stack slot>")); } // Print the definition, unless it's nil VarExpr *v = dynamic_cast(def); if (v == NULL || v->name != "nil") *os << " = " << *def; *os << std::endl; } if (rev_iffupjmp_map.find(start) != rev_iffupjmp_map.end()) { // aha, do a repeat/until loop *os << indent_str << "repeat\n"; Decompiler indented_dc = *this; indented_dc.indent_str += std::string(4, ' '); indented_dc.break_pos = rev_iffupjmp_map[start]; indented_dc.break_pos += instr_lens[*indented_dc.break_pos]; indented_dc.decompileRange(start, rev_iffupjmp_map[start]); Expression *e = stk->top(); stk->pop(); *os << indent_str << "until " << *e << std::endl; delete e; start = indented_dc.break_pos; continue; } Byte opc = *start++; int aux; switch (opc) { case ENDCODE: return; case PUSHNIL: aux = *start++; goto pushnil; case PUSHNIL0: aux = 0; pushnil: for (int i = 0; i <= aux; i++) stk->push(new VarExpr(start, "nil")); // Cheat a little :) break; case PUSHNUMBER: aux = *start++; goto pushnumber; case PUSHNUMBER0: case PUSHNUMBER1: case PUSHNUMBER2: aux = opc - PUSHNUMBER0; goto pushnumber; case PUSHNUMBERW: aux = start[0] | (start[1] << 8); start += 2; pushnumber: stk->push(new NumberExpr(start, aux)); break; case PUSHCONSTANT: aux = *start++; goto pushconst; case PUSHCONSTANT0: case PUSHCONSTANT1: case PUSHCONSTANT2: case PUSHCONSTANT3: case PUSHCONSTANT4: case PUSHCONSTANT5: case PUSHCONSTANT6: case PUSHCONSTANT7: aux = opc - PUSHCONSTANT0; goto pushconst; case PUSHCONSTANTW: aux = start[0] | (start[1] << 8); start += 2; pushconst: switch (ttype(tf->consts + aux)) { case LUA_T_STRING: stk->push(new StringExpr(start, tsvalue(tf->consts + aux))); break; case LUA_T_NUMBER: stk->push(new NumberExpr(start, nvalue(tf->consts + aux))); break; case LUA_T_PROTO: stk->push(new FuncExpr(start, tfvalue(tf->consts + aux), indent_str)); break; default: *os << indent_str << "error: invalid constant type " << int(ttype(tf->consts + aux)) << std::endl; } break; case PUSHUPVALUE: aux = *start++; goto pushupvalue; case PUSHUPVALUE0: case PUSHUPVALUE1: aux = opc - PUSHUPVALUE0; pushupvalue: { if (aux >= num_upvals) { *os << indent_str << "error: invalid upvalue #" << aux << std::endl; } std::ostringstream s; s << "%" << *upvals[aux]; stk->push(new VarExpr(start, s.str())); } break; case PUSHLOCAL: aux = *start++; goto pushlocal; case PUSHLOCAL0: case PUSHLOCAL1: case PUSHLOCAL2: case PUSHLOCAL3: case PUSHLOCAL4: case PUSHLOCAL5: case PUSHLOCAL6: case PUSHLOCAL7: aux = opc - PUSHLOCAL0; pushlocal: stk->push(new VarExpr(start, localname(tf, aux))); break; case GETGLOBAL: aux = *start++; goto getglobal; case GETGLOBAL0: case GETGLOBAL1: case GETGLOBAL2: case GETGLOBAL3: case GETGLOBAL4: case GETGLOBAL5: case GETGLOBAL6: case GETGLOBAL7: aux = opc - GETGLOBAL0; goto getglobal; case GETGLOBALW: aux = start[0] | (start[1] << 8); start += 2; getglobal: stk->push(new VarExpr(start, svalue(tf->consts + aux))); break; case GETTABLE: { Expression *index = stk->top(); stk->pop(); Expression *table = stk->top(); stk->pop(); stk->push(new IndexExpr(start, table, index)); } break; case GETDOTTED: aux = *start++; goto getdotted; case GETDOTTED0: case GETDOTTED1: case GETDOTTED2: case GETDOTTED3: case GETDOTTED4: case GETDOTTED5: case GETDOTTED6: case GETDOTTED7: aux = opc - GETDOTTED0; goto getdotted; case GETDOTTEDW: aux = start[0] | (start[1] << 8); start += 2; getdotted: { Expression *tbl = stk->top(); stk->pop(); stk->push(new IndexExpr(start, tbl, new StringExpr (start, tsvalue(tf->consts + aux)))); } break; case PUSHSELF: aux = *start++; goto pushself; case PUSHSELF0: case PUSHSELF1: case PUSHSELF2: case PUSHSELF3: case PUSHSELF4: case PUSHSELF5: case PUSHSELF6: case PUSHSELF7: aux = opc - PUSHSELF0; goto pushself; case PUSHSELFW: aux = start[0] | (start[1] << 8); start += 2; pushself: { Expression *tbl = stk->top(); stk->pop(); stk->push(new SelfExpr(start, tbl, new StringExpr (start, tsvalue(tf->consts + aux)))); stk->push(new VarExpr(start, "")); // Fake value, FuncCallExpr will handle it } break; case CREATEARRAY: start++; goto createarray; case CREATEARRAY0: case CREATEARRAY1: goto createarray; case CREATEARRAYW: start += 2; createarray: stk->push(new ArrayExpr(start)); break; case SETLOCAL: case SETLOCAL0: case SETLOCAL1: case SETLOCAL2: case SETLOCAL3: case SETLOCAL4: case SETLOCAL5: case SETLOCAL6: case SETLOCAL7: case SETGLOBAL: case SETGLOBAL0: case SETGLOBAL1: case SETGLOBAL2: case SETGLOBAL3: case SETGLOBAL4: case SETGLOBAL5: case SETGLOBAL6: case SETGLOBAL7: case SETGLOBALW: case SETTABLE0: case SETTABLE: start--; do_multi_assign(start); break; case SETLIST: start++; // assume offset is correct goto setlist; case SETLISTW: start += 2; case SETLIST0: setlist: aux = *start++; { ArrayExpr::mapping_list new_mappings; for (int i = 0; i < aux; i++) { Expression *val = stk->top(); stk->pop(); new_mappings.push_front(std::make_pair((Expression *) NULL, val)); } ArrayExpr *a = dynamic_cast(stk->top()); if (a == NULL) { *os << indent_str << "error: attempt to setlist a non-array object\n"; } // Append the new list a->mappings.splice(a->mappings.end(), new_mappings); a->pos = start; } break; case SETMAP: aux = *start++; goto setmap; case SETMAP0: aux = 0; setmap: { ArrayExpr::mapping_list new_mappings; for (int i = 0; i <= aux; i++) { Expression *val = stk->top(); stk->pop(); Expression *key = stk->top(); stk->pop(); new_mappings.push_front(std::make_pair(key, val)); } ArrayExpr *a = dynamic_cast(stk->top()); if (a == NULL) { *os << indent_str << "error: attempt to setmap a non-array object\n"; } // Append the new list a->mappings.splice(a->mappings.end(), new_mappings); a->pos = start; } break; case EQOP: do_binary_op(start, 1, false, " == "); break; case NEQOP: do_binary_op(start, 1, false, " ~= "); break; case LTOP: do_binary_op(start, 1, false, " < "); break; case LEOP: do_binary_op(start, 1, false, " <= "); break; case GTOP: do_binary_op(start, 1, false, " > "); break; case GEOP: do_binary_op(start, 1, false, " >= "); break; case ADDOP: do_binary_op(start, 3, false, " + "); break; case SUBOP: do_binary_op(start, 3, false, " - "); break; case MULTOP: do_binary_op(start, 4, false, " * "); break; case DIVOP: do_binary_op(start, 4, false, " / "); break; case POWOP: do_binary_op(start, 6, true, " ^ "); break; case CONCOP: do_binary_op(start, 2, false, ".."); break; case MINUSOP: do_unary_op(start, 5, "-"); break; case NOTOP: do_unary_op(start, 5, "not "); break; case ONTJMP: aux = *start++; goto ontjmp; case ONTJMPW: aux = start[0] | (start[1] << 8); start += 2; ontjmp: // push_expr_1 ontjmp(label) push_expr_2 label: -> expr_1 || expr_2 decompileRange(start, start + aux); do_binary_op(start + aux, 0, false, " or "); start = start + aux; break; case ONFJMP: aux = *start++; goto onfjmp; case ONFJMPW: aux = start[0] | (start[1] << 8); start += 2; onfjmp: // push_expr_1 onfjmp(label) push_expr_2 label: -> expr_2 && expr_2 decompileRange(start, start + aux); do_binary_op(start + aux, 0, false, " and "); start = start + aux; break; case JMP: aux = *start++; goto jmp; case JMPW: aux = start[0] | (start[1] << 8); start += 2; jmp: { Byte *dest = start + aux; if (dest == break_pos) { *os << indent_str << "break\n"; break; } // otherwise, must be the start of a while statement Byte *while_cond_end; for (while_cond_end = dest; end == NULL || while_cond_end < end; while_cond_end += instr_lens[*while_cond_end]) if (*while_cond_end == IFTUPJMP || *while_cond_end == IFTUPJMPW) break; if (end != NULL && while_cond_end >= end) { *os << indent_str << "error: JMP not in break, while, if/else\n"; } // push the while condition onto the stack decompileRange(dest, while_cond_end); *os << indent_str << "while " << *stk->top() << " do\n"; delete stk->top(); stk->pop(); // decompile the while body Decompiler indented_dc = *this; indented_dc.indent_str += std::string(4, ' '); indented_dc.break_pos = while_cond_end + instr_lens[*while_cond_end]; indented_dc.decompileRange(start, dest); *os << indent_str << "end\n"; start = indented_dc.break_pos; } break; case IFFJMP: aux = *start++; goto iffjmp; case IFFJMPW: aux = start[0] | (start[1] << 8); start += 2; iffjmp: { // Output an if/end, if/else/end, if/elseif/else/end, ... statement Byte *if_part_end = start + aux; Decompiler indented_dc = *this; indented_dc.indent_str += std::string(4, ' '); *os << indent_str << "if " << *stk->top(); delete stk->top(); stk->pop(); *os << " then\n"; bool has_else; Byte *else_part_end; get_else_part(start, if_part_end, has_else, else_part_end); // Output the if part output_if: indented_dc.decompileRange(start, if_part_end); start = start + aux; if (has_else) { // Check whether the entire else part is a single // if or if/else statement Byte *instr_scan = start; while (is_expr_opc(*instr_scan) && (end == NULL || instr_scan < else_part_end)) instr_scan += instr_lens[*instr_scan]; if ((end == NULL || instr_scan < else_part_end) && (*instr_scan == IFFJMP || *instr_scan == IFFJMPW)) { // OK, first line will be if, check if it will go all // the way through Byte *new_start, *new_if_part_end, *new_else_part_end; bool new_has_else; if (*instr_scan == IFFJMP) { aux = instr_scan[1]; new_start = instr_scan + 2; } else { aux = instr_scan[1] | (instr_scan[2] << 8); new_start = instr_scan + 3; } new_if_part_end = new_start + aux; get_else_part(new_start, new_if_part_end, new_has_else, new_else_part_end); if (new_if_part_end == else_part_end || (new_has_else && new_else_part_end == else_part_end)) { // Yes, output an elseif decompileRange(start, instr_scan); // push condition *os << indent_str << "elseif " << *stk->top() << " then\n"; delete stk->top(); stk->pop(); start = new_start; if_part_end = new_if_part_end; has_else = new_has_else; else_part_end = new_else_part_end; goto output_if; } } *os << indent_str << "else\n"; indented_dc.decompileRange(start, else_part_end); start = else_part_end; } *os << indent_str << "end\n"; } break; case CLOSURE: aux = *start++; goto closure; case CLOSURE0: case CLOSURE1: aux = opc - CLOSURE0; closure: { FuncExpr *f = dynamic_cast(stk->top()); if (f == NULL) { *os << indent_str << "error: closure requires a function\n"; } stk->pop(); f->num_upvals = aux; f->upvals = new Expression*[aux]; for (int i = aux - 1; i >= 0; i--) { f->upvals[i] = stk->top(); stk->pop(); } stk->push(f); } break; case CALLFUNC: aux = *start++; goto callfunc; case CALLFUNC0: case CALLFUNC1: aux = opc - CALLFUNC0; callfunc: { int num_args = *start++; FuncCallExpr *e = new FuncCallExpr(start); e->num_args = num_args; e->args = new Expression*[num_args]; for (int i = num_args - 1; i >= 0; i--) { e->args[i] = stk->top(); stk->pop(); } e->func = stk->top(); stk->pop(); if (aux == 0) { *os << indent_str << *e << std::endl; delete e; } else if (aux == 1 || aux == 255) // 255 for return f() stk->push(e); else { stk->push(e); for (int i = 1; i < aux; i++) stk->push(new VarExpr(start, "")); } } break; case RETCODE: { int num_rets = stk->size() + tf->code[1] - *start++; ExprStack rets; for (int i = 0; i < num_rets; i++) { rets.push(stk->top()); stk->pop(); } *os << indent_str << "return"; for (int i = 0; i < num_rets; i++) { *os << " " << *rets.top(); delete rets.top(); rets.pop(); if (i + 1 < num_rets) *os << ","; } *os << std::endl; } break; case SETLINE: aux = *start++; goto setline; case SETLINEW: aux = start[0] | (start[1] << 8); start += 2; setline: break; // ignore line info case POP: aux = *start++; goto pop; case POP0: case POP1: aux = opc - POP0; pop: for (int i = 0; i <= aux; i++) { local_var_defs->insert(stk->top()->pos); delete stk->top(); stk->pop(); } break; default: *os << indent_str << "error: unrecognized opcode " << int(opc) << std::endl; return; } } } // Decompile the body of a function. void decompile(std::ostream &os, TProtoFunc *tf, std::string indent_str, Expression **upvals, int num_upvals) { Byte *instr = tf->code + 2; ExprStack s; std::ostringstream first_time; std::multiset loc_vars; // First, do a preliminary pass to see where local variables are defined Decompiler dc; dc.os = &first_time; dc.stk = &s; dc.tf = tf; dc.indent_str = indent_str; dc.break_pos = NULL; dc.upvals = upvals; dc.num_upvals = num_upvals; dc.local_var_defs = &loc_vars; dc.decompileRange(instr, NULL); if (s.empty() && loc_vars.empty()) { // OK, it didn't actually have any local variables. Just output // the results right now. os << first_time.str(); return; } // See where the local variables were defined. while (! s.empty()) { loc_vars.insert(s.top()->pos); delete s.top(); s.pop(); } // Now do the real decompilation dc.os = &os; dc.decompileRange(instr, NULL); while (! s.empty()) { delete s.top(); s.pop(); } } int main(int argc, char *argv[]) { int filename_pos = 1; if (argc > 1 && strcmp(argv[1], "-t") == 0) { translateStrings = true; filename_pos = 2; } if (argc != filename_pos + 1) { fprintf(stderr, "Usage: delua [-t] file.lua\n"); exit(1); } char *filename = argv[filename_pos]; FILE *f = fopen(filename, "rb"); if (f == NULL) { perror(filename); exit(1); } lua_open(); ZIO z; luaZ_Fopen(&z, f, filename); TProtoFunc *tf = luaU_undump1(&z); fclose(f); decompile(std::cout, tf, "", NULL, 0); lua_close(); return 0; }