home *** CD-ROM | disk | FTP | other *** search
/ Xentax forum attachments archive / xentax.7z / 14707 / Spellforce050818_2.7z / luadec.py < prev    next >
Encoding:
Python Source  |  2018-08-01  |  5.3 KB  |  177 lines

  1. """
  2.  
  3. Primitive Lua disassembler.
  4.  
  5. The Lua scripts are interesting for their data as well as their logic.  For
  6. example, sql_building.lua maps building ids to models.  Therefore, it would
  7. be useful to write a basic decompiler for scripts that do nothing more than
  8. returning a table.
  9.  
  10. The disassembler can be improved easily by adding line numbers, labels for
  11. jump instructions, string tables, and so on.
  12.  
  13. """
  14.  
  15. import io
  16. import struct
  17.  
  18.  
  19. class LuaOpType:
  20.     NONE = 0
  21.     U = 1
  22.     S = 2
  23.     AB = 3
  24.     J = 4
  25.     K = 5
  26.     L = 6
  27.     N = 7
  28.  
  29.  
  30. OPS = (
  31.     ('END', LuaOpType.NONE),
  32.     ('RETURN', LuaOpType.U),
  33.     ('CALL', LuaOpType.AB),
  34.     ('TAILCALL', LuaOpType.AB),
  35.     ('PUSHNIL', LuaOpType.U),
  36.     ('POP', LuaOpType.U),
  37.     ('PUSHINT', LuaOpType.S),
  38.     ('PUSHSTRING', LuaOpType.K),
  39.     ('PUSHNUM', LuaOpType.N),
  40.     ('PUSHNEGNUM', LuaOpType.N),
  41.     ('PUSHUPVALUE', LuaOpType.U),
  42.     ('GETLOCAL', LuaOpType.L),
  43.     ('GETGLOBAL', LuaOpType.K),
  44.     ('GETTABLE', LuaOpType.NONE),
  45.     ('GETDOTTED', LuaOpType.K),
  46.     ('GETINDEXED', LuaOpType.L),
  47.     ('PUSHSELF', LuaOpType.K),
  48.     ('CREATETABLE', LuaOpType.U),
  49.     ('SETLOCAL', LuaOpType.L),
  50.     ('SETGLOBAL', LuaOpType.K),
  51.     ('SETTABLE', LuaOpType.AB),
  52.     ('SETLIST', LuaOpType.AB),
  53.     ('SETMAP', LuaOpType.U),
  54.     ('ADD', LuaOpType.NONE),
  55.     ('ADDI', LuaOpType.S),
  56.     ('SUB', LuaOpType.NONE),
  57.     ('MULT', LuaOpType.NONE),
  58.     ('DIV', LuaOpType.NONE),
  59.     ('POW', LuaOpType.NONE),
  60.     ('CONCAT', LuaOpType.U),
  61.     ('MINUS', LuaOpType.NONE),
  62.     ('NOT', LuaOpType.NONE),
  63.     ('JMPNE', LuaOpType.J),
  64.     ('JMPEQ', LuaOpType.J),
  65.     ('JMPLT', LuaOpType.J),
  66.     ('JMPLE', LuaOpType.J),
  67.     ('JMPGT', LuaOpType.J),
  68.     ('JMPGE', LuaOpType.J),
  69.     ('JMPT', LuaOpType.J),
  70.     ('JMPF', LuaOpType.J),
  71.     ('JMPONT', LuaOpType.J),
  72.     ('JMPONF', LuaOpType.J),
  73.     ('JMP', LuaOpType.J),
  74.     ('PUSHNILJMP', LuaOpType.NONE),
  75.     ('FORPREP', LuaOpType.J),
  76.     ('FORLOOP', LuaOpType.J),
  77.     ('LFORPREP', LuaOpType.J),
  78.     ('LFORLOOP', LuaOpType.J),
  79.     ('CLOSURE', LuaOpType.AB)
  80. )
  81.  
  82.  
  83. def read_string(ins):
  84.     length, = struct.unpack('<L', ins.read(4))
  85.     return ins.read(length).decode('latin-1')
  86.  
  87.  
  88. def read_lua_number(ins):
  89.     return struct.unpack('<d', ins.read(8))[0]
  90.  
  91.  
  92. def read_uint32(ins):
  93.     return struct.unpack('<L', ins.read(4))[0]
  94.  
  95.  
  96. def read_array(ins, elemtype):
  97.     count, = struct.unpack('<L', ins.read(4))
  98.     return [elemtype(ins) for _ in range(count)]
  99.  
  100.  
  101. class LuaFileHeader:
  102.     def __init__(self, ins):
  103.         self.magic, self.version, self.endianness, self.size_int, \
  104.             self.size_size_t, self.size_instruction,\
  105.             self.test_size_instruction, self.test_size_op, self.test_size_b, \
  106.             self.size_number, self.test_fp_number = \
  107.             struct.unpack('<4s9Bd', ins.read(21))
  108.  
  109.  
  110. class LuaLocalVariable:
  111.     def __init__(self, ins):
  112.         self.name = read_string(ins)
  113.         self.start_pc, self.end_pc = struct.unpack('<2L', ins.read(8))
  114.  
  115.  
  116. class LuaCodeChunk:
  117.     def __init__(self, ins):
  118.         self.source = read_string(ins)
  119.         self.line_number, self.num_params, isvarargs, self.max_stack_size = \
  120.             struct.unpack('<2LbL', ins.read(13))
  121.         self.isvarargs = isvarargs == 1
  122.         self.local_vars = read_array(ins, LuaLocalVariable)
  123.         self.line_info = read_array(ins, read_uint32)
  124.         self.strings = read_array(ins, read_string)
  125.         self.numbers = read_array(ins, read_lua_number)
  126.         self.functions = read_array(ins, LuaCodeChunk)
  127.         self.instructions = read_array(ins, read_uint32)
  128.  
  129.  
  130. class LuaFile:
  131.     def __init__(self, data):
  132.         ins = io.BytesIO(data)
  133.         self.header = LuaFileHeader(ins)
  134.         self.root_code_chunk = LuaCodeChunk(ins)
  135.  
  136.  
  137. def disassemble_function(c, lines, depth=0):
  138.     """Disassemble LuaCodeChunk c into lines."""
  139.     spacer = '    ' * depth
  140.     lines.append('{}Function {}'.format(spacer, c.source))
  141.     fmt = '{}locals: {} strings: {} numbers: {} functions: {}'
  142.     lines.append(fmt.format(spacer, len(c.local_vars), len(c.strings),
  143.                             len(c.numbers), len(c.functions)))
  144.     for instruction in c.instructions:
  145.         opcode = instruction & 0x3f
  146.         raw_args = instruction >> 6
  147.         opname, optype = OPS[opcode]
  148.         if optype == LuaOpType.NONE:
  149.             args = ''
  150.         elif optype == LuaOpType.U:
  151.             args = '{}'.format(raw_args)
  152.         elif optype == LuaOpType.S:
  153.             args = '{}'.format(raw_args - (0x7fffffff >> 6))
  154.         elif optype == LuaOpType.AB:
  155.             args = '{} {}'.format(raw_args >> 9, raw_args & 0x1ff)
  156.         elif optype == LuaOpType.K:
  157.             args = c.strings[raw_args]
  158.         elif optype == LuaOpType.J:
  159.             args = '@{}'.format(raw_args - (0x7fffffff >> 6))
  160.         elif optype == LuaOpType.L:
  161.             args = '{}'.format(raw_args)
  162.         elif optype == LuaOpType.N:
  163.             args = '{}'.format(c.numbers[raw_args])
  164.         else:
  165.             raise ValueError('bad lua optype')
  166.         lines.append('{}{} {}'.format(spacer, opname, args))
  167.  
  168.     for f in c.functions:
  169.         disassemble_function(f, lines, depth + 1)
  170.  
  171.  
  172. def disassemble(data):
  173.     f = LuaFile(data)
  174.     lines = []
  175.     disassemble_function(f.root_code_chunk, lines)
  176.     return '\n'.join(lines)
  177.