scummvm/tools/delua.cpp
2010-02-04 19:43:50 +00:00

1309 lines
30 KiB
C++

/* Residual - A 3D game interpreter
*
* 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 <common/sys.h>
#include <common/file.h>
#include <common/str.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <iostream>
#include <sstream>
#include <map>
#include <queue>
#include <stack>
#include <list>
#include <set>
#include <engines/grim/lua/lua.h>
#include <engines/grim/lua/lundump.h>
#include <engines/grim/lua/lopcodes.h>
#include <engines/grim/lua/lzio.h>
#include <engines/grim/lua/lobject.h>
#include <engines/grim/localize.h>
#include <engines/grim/resource.h>
namespace Grim {
class Actor;
class Color;
class ResourceLoader;
class GrimEngine;
// hacks below for shutup linker
int g_flags = 0;
GrimEngine *g_grim = NULL;
ResourceLoader *g_resourceloader = NULL;
LuaFile *ResourceLoader::openNewStreamLuaFile(const char *filename) const { return NULL; }
Actor *check_actor(int num) { return NULL; }
Color *check_color(int num) { return NULL; }
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();
}
}
}
return "";
}
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 (strlen(text->str) == 0)
return false;
if (isdigit(text->str[0]))
return false;
if (text->str[0] >= '0' && text->str[0] <= '9')
return false;
for (unsigned int i = 0; i < strlen(text->str); 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);
if (translateStrings)
str = g_localizer->localize(str.c_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<StringExpr *>(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<StringExpr *>(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<SelfExpr *>(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<Expression *, Expression *> mapping;
typedef std::list<mapping> 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<StringExpr *>(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<Expression *> 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<byte *> *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<Expression *> 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<VarExpr *>(e);
if (v != NULL && v->name == "<extra result>")
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<IndexExpr *>(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<byte *, byte *> 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<VarExpr *>(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, "<self>"));
// 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<ArrayExpr *>(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<ArrayExpr *>(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<FuncExpr *>(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, "<extra result>"));
}
}
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<byte *> 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) {
Grim::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) {
exit(1);
}
fseek(f, 0, SEEK_END);
int size = ftell(f);
fseek(f, 0, SEEK_SET);
char *buff = new char[size];
fread(buff, size, 1, f);
fclose(f);
Grim::lua_open();
Grim::ZIO z;
Grim::luaZ_mopen(&z, buff, size, "(buffer)");
Grim::TProtoFunc *tf = Grim::luaU_undump1(&z);
Grim::decompile(std::cout, tf, "", NULL, 0);
delete[] buff;
Grim::lua_close();
return 0;
}