mirror of
https://github.com/CPunch/LuaDecompy.git
synced 2024-12-04 04:43:54 +00:00
lp: support OP_CLOSURE, boilerplate function/proto support
This commit is contained in:
parent
b28edcba1d
commit
3be45f156a
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
example.*
|
example.*
|
||||||
__pycache__
|
__pycache__
|
||||||
|
NOTES.md
|
||||||
|
111
README.md
111
README.md
@ -12,70 +12,95 @@ Lua has a relatively small instruction set (only 38 different opcodes!). This ma
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
> cat example.lua && luac5.1 -o example.luac example.lua
|
> cat example.lua && luac5.1 -o example.luac example.lua
|
||||||
local tbl = {"He", "llo", " ", "Wo", "rld", "!"}
|
local printMsg = function(append)
|
||||||
local str = ""
|
local tbl = {"He", "llo", " ", "Wo"}
|
||||||
|
local str = ""
|
||||||
|
|
||||||
for i = 1, #tbl do
|
for i = 1, #tbl do
|
||||||
str = str .. tbl[i]
|
str = str .. tbl[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
print(str .. append)
|
||||||
end
|
end
|
||||||
|
|
||||||
print(str)
|
printMsg("rld!")
|
||||||
> python main.py example.luac
|
> python main.py example.luac
|
||||||
example.luac
|
example.luac
|
||||||
|
|
||||||
==== [[example.lua's constants]] ====
|
==== [[example.lua's constants]] ====
|
||||||
|
|
||||||
|
0: [STRING] rld!
|
||||||
|
|
||||||
|
==== [[example.lua's locals]] ====
|
||||||
|
|
||||||
|
R[0]: printMsg
|
||||||
|
|
||||||
|
==== [[example.lua's dissassembly]] ====
|
||||||
|
|
||||||
|
[ 0] CLOSURE : R[0] 0 ;
|
||||||
|
[ 1] MOVE : 1 0 0 ; move R[0] into R[1]
|
||||||
|
[ 2] LOADK : R[2] K[0] ; load "rld!" into R[2]
|
||||||
|
[ 3] CALL : 1 2 1 ;
|
||||||
|
[ 4] RETURN : 0 1 0 ;
|
||||||
|
|
||||||
|
==== [[example.lua's protos]] ====
|
||||||
|
|
||||||
|
|
||||||
|
==== [['s constants]] ====
|
||||||
|
|
||||||
0: [STRING] He
|
0: [STRING] He
|
||||||
1: [STRING] llo
|
1: [STRING] llo
|
||||||
2: [STRING]
|
2: [STRING]
|
||||||
3: [STRING] Wo
|
3: [STRING] Wo
|
||||||
4: [STRING] rld
|
4: [STRING]
|
||||||
5: [STRING] !
|
5: [NUMBER] 1.0
|
||||||
6: [STRING]
|
6: [STRING] print
|
||||||
7: [NUMBER] 1.0
|
|
||||||
8: [STRING] print
|
|
||||||
|
|
||||||
==== [[example.lua's locals]] ====
|
==== [['s locals]] ====
|
||||||
|
|
||||||
R[0]: tbl
|
R[0]: append
|
||||||
R[1]: str
|
R[1]: tbl
|
||||||
R[2]: (for index)
|
R[2]: str
|
||||||
R[3]: (for limit)
|
R[3]: (for index)
|
||||||
R[4]: (for step)
|
R[4]: (for limit)
|
||||||
R[5]: i
|
R[5]: (for step)
|
||||||
|
R[6]: i
|
||||||
|
|
||||||
==== [[example.lua's dissassembly]] ====
|
==== [['s dissassembly]] ====
|
||||||
|
|
||||||
[ 0] NEWTABLE : 0 6 0 ;
|
[ 0] NEWTABLE : 1 4 0 ;
|
||||||
[ 1] LOADK : R[1] K[0] ; load "He" into R[1]
|
[ 1] LOADK : R[2] K[0] ; load "He" into R[2]
|
||||||
[ 2] LOADK : R[2] K[1] ; load "llo" into R[2]
|
[ 2] LOADK : R[3] K[1] ; load "llo" into R[3]
|
||||||
[ 3] LOADK : R[3] K[2] ; load " " into R[3]
|
[ 3] LOADK : R[4] K[2] ; load " " into R[4]
|
||||||
[ 4] LOADK : R[4] K[3] ; load "Wo" into R[4]
|
[ 4] LOADK : R[5] K[3] ; load "Wo" into R[5]
|
||||||
[ 5] LOADK : R[5] K[4] ; load "rld" into R[5]
|
[ 5] SETLIST : 1 4 1 ;
|
||||||
[ 6] LOADK : R[6] K[5] ; load "!" into R[6]
|
[ 6] LOADK : R[2] K[4] ; load "" into R[2]
|
||||||
[ 7] SETLIST : 0 6 1 ;
|
[ 7] LOADK : R[3] K[5] ; load 1 into R[3]
|
||||||
[ 8] LOADK : R[1] K[6] ; load "" into R[1]
|
[ 8] LEN : 4 1 0 ;
|
||||||
[ 9] LOADK : R[2] K[7] ; load 1 into R[2]
|
[ 9] LOADK : R[5] K[5] ; load 1 into R[5]
|
||||||
[ 10] LEN : 3 0 0 ;
|
[ 10] FORPREP : R[3] 3 ;
|
||||||
[ 11] LOADK : R[4] K[7] ; load 1 into R[4]
|
[ 11] MOVE : 7 2 0 ; move R[2] into R[7]
|
||||||
[ 12] FORPREP : R[2] 3 ;
|
[ 12] GETTABLE : R[8] 1 R[6] ;
|
||||||
[ 13] MOVE : 6 1 0 ; move R[1] into R[6]
|
[ 13] CONCAT : 2 7 8 ; concat 2 values from R[7] to R[8], store into R[2]
|
||||||
[ 14] GETTABLE : R[7] 0 R[5] ;
|
[ 14] FORLOOP : R[3] -4 ;
|
||||||
[ 15] CONCAT : 1 6 7 ; concat 2 values from R[6] to R[7], store into R[1]
|
[ 15] GETGLOBAL : R[3] K[6] ; move _G["print"] into R[3]
|
||||||
[ 16] FORLOOP : R[2] -4 ;
|
[ 16] MOVE : 4 2 0 ; move R[2] into R[4]
|
||||||
[ 17] GETGLOBAL : R[2] K[8] ; move _G["print"] into R[2]
|
[ 17] MOVE : 5 0 0 ; move R[0] into R[5]
|
||||||
[ 18] MOVE : 3 1 0 ; move R[1] into R[3]
|
[ 18] CONCAT : 4 4 5 ; concat 2 values from R[4] to R[5], store into R[4]
|
||||||
[ 19] CALL : 2 2 1 ;
|
[ 19] CALL : 3 2 1 ;
|
||||||
[ 20] RETURN : 0 1 0 ;
|
[ 20] RETURN : 0 1 0 ;
|
||||||
|
|
||||||
==== [[example.lua's pseudo-code]] ====
|
==== [[example.lua's pseudo-code]] ====
|
||||||
|
|
||||||
local tbl = {"He", "llo", " ", "Wo", "rld", "!", }
|
local printMsg = function(append)
|
||||||
local str = ""
|
local tbl = {"He", "llo", " ", "Wo", }
|
||||||
for i = 1, #tbl, 1 do
|
local str = ""
|
||||||
str = str .. tbl[i]
|
for i = 1, #tbl, 1 do
|
||||||
|
str = str .. tbl[i]
|
||||||
|
end
|
||||||
|
print(str .. append)
|
||||||
end
|
end
|
||||||
print(str)
|
|
||||||
|
|
||||||
|
printMsg("rld!")
|
||||||
|
|
||||||
```
|
```
|
36
lparser.py
36
lparser.py
@ -6,8 +6,6 @@
|
|||||||
An experimental bytecode decompiler.
|
An experimental bytecode decompiler.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from operator import concat
|
|
||||||
from subprocess import call
|
|
||||||
from lundump import Chunk, Constant, Instruction, Opcodes, whichRK, readRKasK
|
from lundump import Chunk, Constant, Instruction, Opcodes, whichRK, readRKasK
|
||||||
|
|
||||||
class _Scope:
|
class _Scope:
|
||||||
@ -41,7 +39,7 @@ def isValidLocal(ident: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
class LuaDecomp:
|
class LuaDecomp:
|
||||||
def __init__(self, chunk: Chunk):
|
def __init__(self, chunk: Chunk, headChunk: bool = True, scopeOffset: int = 0):
|
||||||
self.chunk = chunk
|
self.chunk = chunk
|
||||||
self.pc = 0
|
self.pc = 0
|
||||||
self.scope: list[_Scope] = []
|
self.scope: list[_Scope] = []
|
||||||
@ -50,6 +48,8 @@ class LuaDecomp:
|
|||||||
self.locals = {}
|
self.locals = {}
|
||||||
self.traceback = {}
|
self.traceback = {}
|
||||||
self.unknownLocalCount = 0
|
self.unknownLocalCount = 0
|
||||||
|
self.headChunk = headChunk
|
||||||
|
self.scopeOffset = scopeOffset # number of scopes this chunk/proto is in
|
||||||
self.src: str = ""
|
self.src: str = ""
|
||||||
|
|
||||||
# configurations!
|
# configurations!
|
||||||
@ -59,6 +59,20 @@ class LuaDecomp:
|
|||||||
|
|
||||||
self.__loadLocals()
|
self.__loadLocals()
|
||||||
|
|
||||||
|
if not self.headChunk:
|
||||||
|
functionProto = "function("
|
||||||
|
|
||||||
|
# define params
|
||||||
|
for i in range(self.chunk.numParams):
|
||||||
|
# add param to function prototype (also make a local in the register if it doesn't exist)
|
||||||
|
functionProto += ("%s, " if i+1 < self.chunk.numParams else "%s") % self.__makeLocalIdentifier(i)
|
||||||
|
|
||||||
|
# mark local as defined
|
||||||
|
self.__addSetTraceback(i)
|
||||||
|
functionProto += ")"
|
||||||
|
|
||||||
|
self.__startScope(functionProto, 0, len(self.chunk.instructions))
|
||||||
|
|
||||||
# parse instructions
|
# parse instructions
|
||||||
while self.pc < len(self.chunk.instructions):
|
while self.pc < len(self.chunk.instructions):
|
||||||
self.parseInstr()
|
self.parseInstr()
|
||||||
@ -67,12 +81,18 @@ class LuaDecomp:
|
|||||||
# end the scope (if we're supposed too)
|
# end the scope (if we're supposed too)
|
||||||
self.__checkScope()
|
self.__checkScope()
|
||||||
|
|
||||||
print("\n==== [[" + str(self.chunk.name) + "'s pseudo-code]] ====\n")
|
if not self.headChunk:
|
||||||
|
self.__endScope()
|
||||||
|
|
||||||
|
def getPseudoCode(self) -> str:
|
||||||
|
fullSrc = ""
|
||||||
|
|
||||||
for line in self.lines:
|
for line in self.lines:
|
||||||
if self.annotateLines:
|
if self.annotateLines:
|
||||||
print("-- PC: %d to PC: %d" % (line.startPC, line.endPC))
|
fullSrc += "-- PC: %d to PC: %d\n" % (line.startPC, line.endPC)
|
||||||
print(((' ' * self.indexWidth) * line.scope) + line.src)
|
fullSrc += ((' ' * self.indexWidth) * (line.scope + self.scopeOffset)) + line.src + "\n"
|
||||||
|
|
||||||
|
return fullSrc
|
||||||
|
|
||||||
# =======================================[[ Helpers ]]=========================================
|
# =======================================[[ Helpers ]]=========================================
|
||||||
|
|
||||||
@ -179,7 +199,6 @@ class LuaDecomp:
|
|||||||
return self.locals[indx]
|
return self.locals[indx]
|
||||||
|
|
||||||
def __newLocal(self, indx: int, expr: str) -> None:
|
def __newLocal(self, indx: int, expr: str) -> None:
|
||||||
# TODO: grab identifier from chunk(?)
|
|
||||||
self.__makeLocalIdentifier(indx)
|
self.__makeLocalIdentifier(indx)
|
||||||
|
|
||||||
self.__addExpr("local " + self.locals[indx] + " = " + expr)
|
self.__addExpr("local " + self.locals[indx] + " = " + expr)
|
||||||
@ -408,5 +427,8 @@ class LuaDecomp:
|
|||||||
for i in range(numElems):
|
for i in range(numElems):
|
||||||
self.__addExpr("%s[%d] = %s" % (ident, (startAt + i + 1), self.__getReg(instr.A + i + 1)))
|
self.__addExpr("%s[%d] = %s" % (ident, (startAt + i + 1), self.__getReg(instr.A + i + 1)))
|
||||||
self.__endStatement()
|
self.__endStatement()
|
||||||
|
elif instr.opcode == Opcodes.CLOSURE:
|
||||||
|
proto = LuaDecomp(self.chunk.protos[instr.B], headChunk=False, scopeOffset=len(self.scope))
|
||||||
|
self.__setReg(instr.A, proto.getPseudoCode())
|
||||||
else:
|
else:
|
||||||
raise Exception("unsupported instruction: %s" % instr.toString())
|
raise Exception("unsupported instruction: %s" % instr.toString())
|
6
main.py
6
main.py
@ -7,4 +7,8 @@ print(sys.argv[1])
|
|||||||
chunk = lc.loadFile(sys.argv[1])
|
chunk = lc.loadFile(sys.argv[1])
|
||||||
|
|
||||||
lc.print_dissassembly()
|
lc.print_dissassembly()
|
||||||
lp = lparser.LuaDecomp(chunk)
|
|
||||||
|
lp = lparser.LuaDecomp(chunk)
|
||||||
|
|
||||||
|
print("\n==== [[" + str(chunk.name) + "'s pseudo-code]] ====\n")
|
||||||
|
print(lp.getPseudoCode())
|
Loading…
Reference in New Issue
Block a user