Compare commits

..

No commits in common. "3be45f156a57247d9036a0bde648f4d702cb3a9a" and "bc4e762e26785ae4db93c3b757118960b841f3e9" have entirely different histories.

4 changed files with 53 additions and 105 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
example.* example.*
__pycache__ __pycache__
NOTES.md

111
README.md
View File

@ -12,95 +12,70 @@ 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 printMsg = function(append) local tbl = {"He", "llo", " ", "Wo", "rld", "!"}
local tbl = {"He", "llo", " ", "Wo"} local str = ""
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
printMsg("rld!") print(str)
> 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] 4: [STRING] rld
5: [NUMBER] 1.0 5: [STRING] !
6: [STRING] print 6: [STRING]
7: [NUMBER] 1.0
8: [STRING] print
==== [['s locals]] ==== ==== [[example.lua's locals]] ====
R[0]: append R[0]: tbl
R[1]: tbl R[1]: str
R[2]: str R[2]: (for index)
R[3]: (for index) R[3]: (for limit)
R[4]: (for limit) R[4]: (for step)
R[5]: (for step) R[5]: i
R[6]: i
==== [['s dissassembly]] ==== ==== [[example.lua's dissassembly]] ====
[ 0] NEWTABLE : 1 4 0 ; [ 0] NEWTABLE : 0 6 0 ;
[ 1] LOADK : R[2] K[0] ; load "He" into R[2] [ 1] LOADK : R[1] K[0] ; load "He" into R[1]
[ 2] LOADK : R[3] K[1] ; load "llo" into R[3] [ 2] LOADK : R[2] K[1] ; load "llo" into R[2]
[ 3] LOADK : R[4] K[2] ; load " " into R[4] [ 3] LOADK : R[3] K[2] ; load " " into R[3]
[ 4] LOADK : R[5] K[3] ; load "Wo" into R[5] [ 4] LOADK : R[4] K[3] ; load "Wo" into R[4]
[ 5] SETLIST : 1 4 1 ; [ 5] LOADK : R[5] K[4] ; load "rld" into R[5]
[ 6] LOADK : R[2] K[4] ; load "" into R[2] [ 6] LOADK : R[6] K[5] ; load "!" into R[6]
[ 7] LOADK : R[3] K[5] ; load 1 into R[3] [ 7] SETLIST : 0 6 1 ;
[ 8] LEN : 4 1 0 ; [ 8] LOADK : R[1] K[6] ; load "" into R[1]
[ 9] LOADK : R[5] K[5] ; load 1 into R[5] [ 9] LOADK : R[2] K[7] ; load 1 into R[2]
[ 10] FORPREP : R[3] 3 ; [ 10] LEN : 3 0 0 ;
[ 11] MOVE : 7 2 0 ; move R[2] into R[7] [ 11] LOADK : R[4] K[7] ; load 1 into R[4]
[ 12] GETTABLE : R[8] 1 R[6] ; [ 12] FORPREP : R[2] 3 ;
[ 13] CONCAT : 2 7 8 ; concat 2 values from R[7] to R[8], store into R[2] [ 13] MOVE : 6 1 0 ; move R[1] into R[6]
[ 14] FORLOOP : R[3] -4 ; [ 14] GETTABLE : R[7] 0 R[5] ;
[ 15] GETGLOBAL : R[3] K[6] ; move _G["print"] into R[3] [ 15] CONCAT : 1 6 7 ; concat 2 values from R[6] to R[7], store into R[1]
[ 16] MOVE : 4 2 0 ; move R[2] into R[4] [ 16] FORLOOP : R[2] -4 ;
[ 17] MOVE : 5 0 0 ; move R[0] into R[5] [ 17] GETGLOBAL : R[2] K[8] ; move _G["print"] into R[2]
[ 18] CONCAT : 4 4 5 ; concat 2 values from R[4] to R[5], store into R[4] [ 18] MOVE : 3 1 0 ; move R[1] into R[3]
[ 19] CALL : 3 2 1 ; [ 19] CALL : 2 2 1 ;
[ 20] RETURN : 0 1 0 ; [ 20] RETURN : 0 1 0 ;
==== [[example.lua's pseudo-code]] ==== ==== [[example.lua's pseudo-code]] ====
local printMsg = function(append) local tbl = {"He", "llo", " ", "Wo", "rld", "!", }
local tbl = {"He", "llo", " ", "Wo", } local str = ""
local str = "" for i = 1, #tbl, 1 do
for i = 1, #tbl, 1 do str = str .. tbl[i]
str = str .. tbl[i]
end
print(str .. append)
end end
print(str)
printMsg("rld!")
``` ```

View File

@ -6,6 +6,8 @@
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:
@ -28,18 +30,18 @@ class _Line:
def isValidLocal(ident: str) -> bool: def isValidLocal(ident: str) -> bool:
# has to start with an alpha or _ # has to start with an alpha or _
if ident[0] not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_": if ident[0] not in "abcdefghijklmnopqrstuvwxyz_":
return False return False
# then it can be alphanum or _ # then it can be alphanum or _
for c in ident[1:]: for c in ident[1:]:
if c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_": if c not in "abcdefghijklmnopqrstuvwxyz1234567890_":
return False return False
return True return True
class LuaDecomp: class LuaDecomp:
def __init__(self, chunk: Chunk, headChunk: bool = True, scopeOffset: int = 0): def __init__(self, chunk: Chunk):
self.chunk = chunk self.chunk = chunk
self.pc = 0 self.pc = 0
self.scope: list[_Scope] = [] self.scope: list[_Scope] = []
@ -48,8 +50,6 @@ 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,20 +59,6 @@ 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()
@ -81,18 +67,12 @@ class LuaDecomp:
# end the scope (if we're supposed too) # end the scope (if we're supposed too)
self.__checkScope() self.__checkScope()
if not self.headChunk: print("\n==== [[" + str(self.chunk.name) + "'s pseudo-code]] ====\n")
self.__endScope()
def getPseudoCode(self) -> str:
fullSrc = ""
for line in self.lines: for line in self.lines:
if self.annotateLines: if self.annotateLines:
fullSrc += "-- PC: %d to PC: %d\n" % (line.startPC, line.endPC) print("-- PC: %d to PC: %d" % (line.startPC, line.endPC))
fullSrc += ((' ' * self.indexWidth) * (line.scope + self.scopeOffset)) + line.src + "\n" print(((' ' * self.indexWidth) * line.scope) + line.src)
return fullSrc
# =======================================[[ Helpers ]]========================================= # =======================================[[ Helpers ]]=========================================
@ -199,6 +179,7 @@ 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)
@ -427,8 +408,5 @@ 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())

View File

@ -7,8 +7,4 @@ 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())