lp: added NEWTABLE && SETLIST

- tables can now be (mostly) decompiled
- changed 'decompiled source' to 'pseudo-code' since the output doesn't typically match the compiled script source.
- misc. refactoring
This commit is contained in:
CPunch 2022-08-15 23:30:32 -05:00
parent 9da0d0ffbd
commit a248cc4807
3 changed files with 58 additions and 40 deletions

View File

@ -12,51 +12,48 @@ Lua has a relatively small instruction set (only 38 different opcodes!). This ma
```sh
> cat example.lua && luac5.1 -o example.luac example.lua
local total = 0
local tbl = {"Hello", "World"}
for i = 0, 9, 1 do
total = total + i
print(total)
end
print(tbl[1] .. " " .. tbl[2] .. ": " .. 2.5)
> python main.py example.luac
example.luac
==== [[example.lua's constants]] ====
0: [NUMBER] 0.0
1: [NUMBER] 9.0
2: [NUMBER] 1.0
3: [STRING] print
0: [STRING] Hello
1: [STRING] World
2: [STRING] print
3: [NUMBER] 1.0
4: [STRING]
5: [NUMBER] 2.0
6: [STRING] :
7: [NUMBER] 2.5
==== [[example.lua's locals]] ====
R[0]: total
R[1]: (for index)
R[2]: (for limit)
R[3]: (for step)
R[4]: i
R[0]: tbl
==== [[example.lua's dissassembly]] ====
[ 0] LOADK : R[0] K[0] ; load 0.0 into R[0]
[ 1] LOADK : R[1] K[0] ; load 0.0 into R[1]
[ 2] LOADK : R[2] K[1] ; load 9.0 into R[2]
[ 3] LOADK : R[3] K[2] ; load 1.0 into R[3]
[ 4] FORPREP : R[1] 4 ;
[ 5] ADD : R[0] R[0] R[4] ; add R[4] to R[0], place into R[0]
[ 6] GETGLOBAL : R[5] K[3] ; move _G["print"] into R[5]
[ 7] MOVE : 6 0 0 ; move R[0] into R[6]
[ 8] CALL : 5 2 1 ;
[ 9] FORLOOP : R[1] -5 ;
[ 10] RETURN : 0 1 0 ;
[ 0] NEWTABLE : 0 2 0 ;
[ 1] LOADK : R[1] K[0] ; load "Hello" into R[1]
[ 2] LOADK : R[2] K[1] ; load "World" into R[2]
[ 3] SETLIST : 0 2 1 ;
[ 4] GETGLOBAL : R[1] K[2] ; move _G["print"] into R[1]
[ 5] GETTABLE : R[2] 0 K[3] ;
[ 6] LOADK : R[3] K[4] ; load " " into R[3]
[ 7] GETTABLE : R[4] 0 K[5] ;
[ 8] LOADK : R[5] K[6] ; load ": " into R[5]
[ 9] LOADK : R[6] K[7] ; load 2.5 into R[6]
[ 10] CONCAT : 2 2 6 ; concat 5 values from R[2] to R[6], store into R[2]
[ 11] CALL : 1 2 1 ;
[ 12] RETURN : 0 1 0 ;
==== [[example.lua's decompiled source]] ====
local total = 0.0
for i = 0.0, 9.0, 1.0 do
total = (total + i)
print(total)
end
==== [[example.lua's pseudo-code]] ====
local tbl = {}
tbl[1] = "Hello"
tbl[2] = "World"
print(tbl[1] .. " " .. tbl[2] .. ": " .. 2.5)
```

View File

@ -8,7 +8,6 @@
from operator import concat
from subprocess import call
from xmlrpc.client import Boolean
from lundump import Chunk, Constant, Instruction, Opcodes, whichRK, readRKasK
class _Scope:
@ -63,7 +62,7 @@ class LuaDecomp:
# end the scope (if we're supposed too)
self.__checkScope()
print("\n==== [[" + str(self.chunk.name) + "'s decompiled source]] ====\n")
print("\n==== [[" + str(self.chunk.name) + "'s pseudo-code]] ====\n")
for line in self.lines:
if self.annotateLines:
@ -121,7 +120,7 @@ class LuaDecomp:
self.src = ""
# walks traceback, if local wasn't set before, the local needs to be defined
def __needsDefined(self, reg) -> Boolean:
def __needsDefined(self, reg) -> bool:
for _, trace in self.traceback.items():
if reg in trace.sets:
return False
@ -147,7 +146,7 @@ class LuaDecomp:
# if the top indx is a local, get it
return self.locals[indx] if indx in self.locals else self.top[indx]
def __setReg(self, indx: int, code: str) -> None:
def __setReg(self, indx: int, code: str, forceLocal: bool = False) -> None:
# if the top indx is a local, set it
if indx in self.locals:
if self.__needsDefined(indx):
@ -155,10 +154,9 @@ class LuaDecomp:
else:
self.__addExpr(self.locals[indx] + " = " + code)
self.__endStatement()
elif self.aggressiveLocals: # 'every register is a local!!'
elif self.aggressiveLocals or forceLocal: # 'every register is a local!!'
self.__newLocal(indx, code)
self.__addSetTraceback(indx)
self.top[indx] = code
@ -280,6 +278,13 @@ class LuaDecomp:
elif instr.opcode == Opcodes.SETTABLE:
self.__addExpr(self.__getReg(instr.A) + "[" + self.__readRK(instr.B) + "] = " + self.__readRK(instr.C))
self.__endStatement()
elif instr.opcode == Opcodes.NEWTABLE:
# i use forceLocal here even though i don't know *for sure* that the register is a local.
# this does help later though if the table is populated (which is 99% of the time). the other 1%
# only affects syntax and may look a little weird but is fine and equivalent non-the-less
# TODO: make this better
self.__setReg(instr.A, "{}", forceLocal=True)
elif instr.opcode == Opcodes.ADD:
self.__emitOperand(instr.A, self.__readRK(instr.B), self.__readRK(instr.C), " + ")
elif instr.opcode == Opcodes.SUB:
@ -353,5 +358,21 @@ class LuaDecomp:
elif instr.opcode == Opcodes.FORPREP:
self.__addExpr("for %s = %s, %s, %s " % (self.__getLocal(instr.A+3), self.__getReg(instr.A), self.__getReg(instr.A + 1), self.__getReg(instr.A + 2)))
self.__startScope("do", self.pc, instr.B)
elif instr.opcode == Opcodes.SETLIST:
# LFIELDS_PER_FLUSH (50) is the number of elements that *should* have been set in the list in the *last* SETLIST
# eg.
# [ 49] LOADK : R[49] K[1] ; load 0.0 into R[49]
# [ 50] LOADK : R[50] K[1] ; load 0.0 into R[50]
# [ 51] SETLIST : 0 50 1 ; sets list[1..50]
# [ 52] LOADK : R[1] K[1] ; load 0.0 into R[1]
# [ 53] SETLIST : 0 1 2 ; sets list[51..51]
numElems = instr.B
startAt = ((instr.C - 1) * 50)
ident = self.__getLocal(instr.A)
# set each index (TODO: make tables less verbose)
for i in range(numElems):
self.__addExpr("%s[%d] = %s" % (ident, (startAt + i + 1), self.__getReg(instr.A + i + 1)))
self.__endStatement()
else:
raise Exception("unsupported instruction: %s" % instr.toString())

View File

@ -152,7 +152,7 @@ class Constant:
self.data = data
def toString(self):
return "[" + self.type.name + "] " + str(self.data)
return "[%s] %s" % (self.type.name, str(self.data))
# format the constant so that it is parsable by lua
def toCode(self):
@ -164,7 +164,7 @@ class Constant:
else:
return "false"
elif self.type == ConstType.NUMBER:
return str(self.data)
return "%g" % self.data
else:
return "nil"