unidecompiler-plugin-lua 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unidecompiler_plugin_lua/__init__.py +2 -0
- unidecompiler_plugin_lua/chunk54.py +587 -0
- unidecompiler_plugin_lua/lifter.py +1631 -0
- unidecompiler_plugin_lua/luac.py +558 -0
- unidecompiler_plugin_lua/normalize.py +241 -0
- unidecompiler_plugin_lua/plugin.py +108 -0
- unidecompiler_plugin_lua/simulation.py +78 -0
- unidecompiler_plugin_lua/support.py +15 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/METADATA +25 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/RECORD +13 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/WHEEL +5 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/entry_points.txt +2 -0
- unidecompiler_plugin_lua-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import struct
|
|
5
|
+
|
|
6
|
+
from unidecompiler_plugin_lua.luac import (
|
|
7
|
+
LuaChunk,
|
|
8
|
+
LuaConstantListing,
|
|
9
|
+
LuacDecodeError,
|
|
10
|
+
LuacHeader,
|
|
11
|
+
LuaFunctionListing,
|
|
12
|
+
LuaInstructionListing,
|
|
13
|
+
LuaLocalListing,
|
|
14
|
+
LuaUpvalueListing,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
LUA_SIGNATURE = b"\x1bLua"
|
|
19
|
+
LUA_54_VERSION = 0x54
|
|
20
|
+
LUAC_DATA = b"\x19\x93\r\n\x1a\n"
|
|
21
|
+
LUAC_INT = 0x5678
|
|
22
|
+
LUAC_NUM = 370.5
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Lua54ChunkError(LuacDecodeError):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class _ParsedFunction:
|
|
31
|
+
kind: str
|
|
32
|
+
source: str
|
|
33
|
+
line_start: int
|
|
34
|
+
line_end: int
|
|
35
|
+
param_count: int
|
|
36
|
+
slot_count: int
|
|
37
|
+
instructions: tuple[LuaInstructionListing, ...]
|
|
38
|
+
constants: tuple[LuaConstantListing, ...]
|
|
39
|
+
locals: tuple[LuaLocalListing, ...]
|
|
40
|
+
upvalues: tuple[LuaUpvalueListing, ...]
|
|
41
|
+
child_count: int
|
|
42
|
+
children: tuple["_ParsedFunction", ...]
|
|
43
|
+
inferred_name: str | None = None
|
|
44
|
+
child_function_names: tuple[str, ...] = ()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _Reader:
|
|
48
|
+
def __init__(self, data: bytes) -> None:
|
|
49
|
+
self.data = data
|
|
50
|
+
self.offset = 0
|
|
51
|
+
|
|
52
|
+
def read(self, size: int) -> bytes:
|
|
53
|
+
end = self.offset + size
|
|
54
|
+
if end > len(self.data):
|
|
55
|
+
raise Lua54ChunkError("truncated chunk")
|
|
56
|
+
result = self.data[self.offset:end]
|
|
57
|
+
self.offset = end
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
def byte(self) -> int:
|
|
61
|
+
return self.read(1)[0]
|
|
62
|
+
|
|
63
|
+
def varint(self) -> int:
|
|
64
|
+
value = 0
|
|
65
|
+
while True:
|
|
66
|
+
byte = self.byte()
|
|
67
|
+
value = (value << 7) | (byte & 0x7F)
|
|
68
|
+
if byte & 0x80:
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
def unpack(self, fmt: str):
|
|
72
|
+
fmt = "<" + fmt
|
|
73
|
+
return struct.unpack(fmt, self.read(struct.calcsize(fmt)))[0]
|
|
74
|
+
|
|
75
|
+
def lua_integer(self, size: int) -> int:
|
|
76
|
+
if size == 8:
|
|
77
|
+
return int(self.unpack("q"))
|
|
78
|
+
if size == 4:
|
|
79
|
+
return int(self.unpack("i"))
|
|
80
|
+
raise Lua54ChunkError(f"unsupported lua integer size: {size}")
|
|
81
|
+
|
|
82
|
+
def lua_number(self, size: int) -> float:
|
|
83
|
+
if size == 8:
|
|
84
|
+
return float(self.unpack("d"))
|
|
85
|
+
if size == 4:
|
|
86
|
+
return float(self.unpack("f"))
|
|
87
|
+
raise Lua54ChunkError(f"unsupported lua number size: {size}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
_OPCODES = (
|
|
91
|
+
"MOVE", "LOADI", "LOADF", "LOADK", "LOADKX", "LOADFALSE", "LFALSESKIP", "LOADTRUE",
|
|
92
|
+
"LOADNIL", "GETUPVAL", "SETUPVAL", "GETTABUP", "GETTABLE", "GETI", "GETFIELD",
|
|
93
|
+
"SETTABUP", "SETTABLE", "SETI", "SETFIELD", "NEWTABLE", "SELF", "ADDI", "ADDK",
|
|
94
|
+
"SUBK", "MULK", "MODK", "POWK", "DIVK", "IDIVK", "BANDK", "BORK", "BXORK",
|
|
95
|
+
"SHRI", "SHLI", "ADD", "SUB", "MUL", "MOD", "POW", "DIV", "IDIV", "BAND",
|
|
96
|
+
"BOR", "BXOR", "SHL", "SHR", "MMBIN", "MMBINI", "MMBINK", "UNM", "BNOT", "NOT",
|
|
97
|
+
"LEN", "CONCAT", "CLOSE", "TBC", "JMP", "EQ", "LT", "LE", "EQK", "EQI", "LTI",
|
|
98
|
+
"LEI", "GTI", "GEI", "TEST", "TESTSET", "CALL", "TAILCALL", "RETURN", "RETURN0",
|
|
99
|
+
"RETURN1", "FORLOOP", "FORPREP", "TFORPREP", "TFORCALL", "TFORLOOP", "SETLIST",
|
|
100
|
+
"CLOSURE", "VARARG", "VARARGPREP", "EXTRAARG",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def decode_lua54_chunk(data: bytes, filename: str | None = None) -> LuaChunk:
|
|
105
|
+
reader = _Reader(data)
|
|
106
|
+
header = _read_header(reader)
|
|
107
|
+
reader.byte()
|
|
108
|
+
parsed = _read_function(reader, parent_source=None, kind="main")
|
|
109
|
+
functions = _flatten_and_infer(parsed)
|
|
110
|
+
return LuaChunk(
|
|
111
|
+
header=header,
|
|
112
|
+
raw=data,
|
|
113
|
+
filename=filename,
|
|
114
|
+
functions=tuple(_to_listing(function) for function in functions),
|
|
115
|
+
decoder_id="lua54-binary",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _read_header(reader: _Reader) -> LuacHeader:
|
|
120
|
+
if reader.read(4) != LUA_SIGNATURE:
|
|
121
|
+
raise Lua54ChunkError("missing Lua chunk signature")
|
|
122
|
+
version = reader.byte()
|
|
123
|
+
if version != LUA_54_VERSION:
|
|
124
|
+
raise Lua54ChunkError(f"unsupported Lua bytecode version 0x{version:02x}")
|
|
125
|
+
fmt = reader.byte()
|
|
126
|
+
if fmt != 0:
|
|
127
|
+
raise Lua54ChunkError(f"unsupported Lua chunk format: {fmt}")
|
|
128
|
+
if reader.read(6) != LUAC_DATA:
|
|
129
|
+
raise Lua54ChunkError("corrupted Lua 5.4 chunk data")
|
|
130
|
+
instruction_size = reader.byte()
|
|
131
|
+
integer_size = reader.byte()
|
|
132
|
+
number_size = reader.byte()
|
|
133
|
+
if reader.lua_integer(integer_size) != LUAC_INT:
|
|
134
|
+
raise Lua54ChunkError("integer format mismatch")
|
|
135
|
+
if reader.lua_number(number_size) != LUAC_NUM:
|
|
136
|
+
raise Lua54ChunkError("number format mismatch")
|
|
137
|
+
return LuacHeader(
|
|
138
|
+
version=version,
|
|
139
|
+
format=fmt,
|
|
140
|
+
little_endian=True,
|
|
141
|
+
int_size=integer_size,
|
|
142
|
+
size_t_size=None,
|
|
143
|
+
instruction_size=instruction_size,
|
|
144
|
+
lua_number_size=number_size,
|
|
145
|
+
integral_numbers=False,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _read_function(reader: _Reader, parent_source: str | None, kind: str) -> _ParsedFunction:
|
|
150
|
+
source = _read_string(reader) or parent_source or "<chunk>"
|
|
151
|
+
line_start = reader.varint()
|
|
152
|
+
line_end = reader.varint()
|
|
153
|
+
param_count = reader.byte()
|
|
154
|
+
reader.byte()
|
|
155
|
+
slot_count = reader.byte()
|
|
156
|
+
instructions = _read_code(reader)
|
|
157
|
+
constants = _read_constants(reader)
|
|
158
|
+
upvalue_descriptors = _read_upvalues(reader)
|
|
159
|
+
children = _read_children(reader, source)
|
|
160
|
+
line_info, locals_, upvalue_names = _read_debug(reader)
|
|
161
|
+
locals_ = _assign_local_register_slots(locals_, tuple(instructions), param_count)
|
|
162
|
+
upvalues = tuple(
|
|
163
|
+
LuaUpvalueListing(
|
|
164
|
+
index=index,
|
|
165
|
+
name=upvalue_names[index] if index < len(upvalue_names) else None,
|
|
166
|
+
instack=descriptor[0],
|
|
167
|
+
slot=descriptor[1],
|
|
168
|
+
kind=descriptor[2],
|
|
169
|
+
)
|
|
170
|
+
for index, descriptor in enumerate(upvalue_descriptors)
|
|
171
|
+
)
|
|
172
|
+
return _ParsedFunction(
|
|
173
|
+
kind=kind,
|
|
174
|
+
source=source,
|
|
175
|
+
line_start=line_start,
|
|
176
|
+
line_end=line_end,
|
|
177
|
+
param_count=param_count,
|
|
178
|
+
slot_count=slot_count,
|
|
179
|
+
instructions=tuple(
|
|
180
|
+
LuaInstructionListing(
|
|
181
|
+
pc=instruction.pc,
|
|
182
|
+
line=_line_for_pc(line_start, line_info, instruction.pc),
|
|
183
|
+
opcode=instruction.opcode,
|
|
184
|
+
operands=instruction.operands,
|
|
185
|
+
comment=_target_comment(instruction),
|
|
186
|
+
)
|
|
187
|
+
for instruction in instructions
|
|
188
|
+
),
|
|
189
|
+
constants=constants,
|
|
190
|
+
locals=locals_,
|
|
191
|
+
upvalues=upvalues,
|
|
192
|
+
child_count=len(children),
|
|
193
|
+
children=children,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _read_code(reader: _Reader) -> tuple[LuaInstructionListing, ...]:
|
|
198
|
+
count = reader.varint()
|
|
199
|
+
instructions: list[LuaInstructionListing] = []
|
|
200
|
+
for pc in range(1, count + 1):
|
|
201
|
+
raw = reader.unpack("I")
|
|
202
|
+
opcode = _OPCODES[raw & 0x7F]
|
|
203
|
+
instructions.append(
|
|
204
|
+
LuaInstructionListing(
|
|
205
|
+
pc=pc,
|
|
206
|
+
line=None,
|
|
207
|
+
opcode=opcode,
|
|
208
|
+
operands=_decode_operands(opcode, raw),
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
return tuple(instructions)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _decode_operands(opcode: str, raw: int) -> tuple[str, ...]:
|
|
215
|
+
a = (raw >> 7) & 0xFF
|
|
216
|
+
k = (raw >> 15) & 0x1
|
|
217
|
+
b = (raw >> 16) & 0xFF
|
|
218
|
+
c = (raw >> 24) & 0xFF
|
|
219
|
+
sc = c - 127
|
|
220
|
+
sb = b - 127
|
|
221
|
+
bx = (raw >> 15) & 0x1FFFF
|
|
222
|
+
sx = bx - ((1 << 16) - 1)
|
|
223
|
+
sj = ((raw >> 7) & 0x1FFFFFF) - ((1 << 24) - 1)
|
|
224
|
+
if opcode in {"LOADI", "LOADF"}:
|
|
225
|
+
return (str(a), str(sx))
|
|
226
|
+
if opcode in {"FORLOOP", "FORPREP", "TFORPREP"}:
|
|
227
|
+
return (str(a), str(bx))
|
|
228
|
+
if opcode == "JMP":
|
|
229
|
+
return (str(sj),)
|
|
230
|
+
if opcode in {"RETURN0"}:
|
|
231
|
+
return ()
|
|
232
|
+
if opcode in {"RETURN1", "LOADFALSE", "LFALSESKIP", "LOADTRUE", "VARARGPREP", "TBC", "CLOSE"}:
|
|
233
|
+
return (str(a),)
|
|
234
|
+
if opcode in {"LOADK", "CLOSURE"}:
|
|
235
|
+
return (str(a), str(bx))
|
|
236
|
+
if opcode in {"ADDI", "SHRI", "SHLI"}:
|
|
237
|
+
return (str(a), str(b), str(sc))
|
|
238
|
+
if opcode in {"EQI", "LTI", "LEI", "GTI", "GEI"}:
|
|
239
|
+
return (str(a), str(sb), str(k))
|
|
240
|
+
if opcode in {"EQ", "LT", "LE", "EQK"}:
|
|
241
|
+
# These comparison opcodes use the iABCk layout. The final operand
|
|
242
|
+
# is the k flag, not the ordinary C field.
|
|
243
|
+
return (str(a), str(b), str(k))
|
|
244
|
+
if opcode == "TEST":
|
|
245
|
+
return (str(a), str(k))
|
|
246
|
+
if opcode == "TESTSET":
|
|
247
|
+
return (str(a), str(b), str(k))
|
|
248
|
+
if opcode == "CONCAT":
|
|
249
|
+
return (str(a), str(b))
|
|
250
|
+
if opcode == "MMBINI":
|
|
251
|
+
return (str(a), str(sb), str(c), str(k))
|
|
252
|
+
if opcode in {"MMBINK", "NEWTABLE"}:
|
|
253
|
+
return (str(a), str(b), str(c), str(k))
|
|
254
|
+
if opcode in {"SETTABLE", "SETI", "SETFIELD"}:
|
|
255
|
+
return (str(a), str(b), f"{c}k" if k else str(c))
|
|
256
|
+
return tuple(str(part) for part in (a, b, c))
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _read_constants(reader: _Reader) -> tuple[LuaConstantListing, ...]:
|
|
260
|
+
count = reader.varint()
|
|
261
|
+
constants: list[LuaConstantListing] = []
|
|
262
|
+
for index in range(count):
|
|
263
|
+
tag = reader.byte()
|
|
264
|
+
if tag == 0:
|
|
265
|
+
constants.append(LuaConstantListing(index=index, kind="N", value=None))
|
|
266
|
+
elif tag == 1:
|
|
267
|
+
constants.append(LuaConstantListing(index=index, kind="b", value=False))
|
|
268
|
+
elif tag == 17:
|
|
269
|
+
constants.append(LuaConstantListing(index=index, kind="b", value=True))
|
|
270
|
+
elif tag == 3:
|
|
271
|
+
constants.append(LuaConstantListing(index=index, kind="I", value=reader.lua_integer(8)))
|
|
272
|
+
elif tag == 19:
|
|
273
|
+
constants.append(LuaConstantListing(index=index, kind="F", value=reader.lua_number(8)))
|
|
274
|
+
elif tag in {4, 20}:
|
|
275
|
+
constants.append(LuaConstantListing(index=index, kind="S", value=_read_string(reader)))
|
|
276
|
+
else:
|
|
277
|
+
raise Lua54ChunkError(f"unsupported Lua 5.4 constant tag: {tag}")
|
|
278
|
+
return tuple(constants)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _read_upvalues(reader: _Reader) -> tuple[tuple[int, int, int], ...]:
|
|
282
|
+
return tuple((reader.byte(), reader.byte(), reader.byte()) for _ in range(reader.varint()))
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _read_children(reader: _Reader, source: str) -> tuple[_ParsedFunction, ...]:
|
|
286
|
+
return tuple(_read_function(reader, source, "function") for _ in range(reader.varint()))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _read_debug(
|
|
290
|
+
reader: _Reader,
|
|
291
|
+
) -> tuple[tuple[int, ...], tuple[LuaLocalListing, ...], tuple[str | None, ...]]:
|
|
292
|
+
line_info = tuple(_signed_byte(reader.byte()) for _ in range(reader.varint()))
|
|
293
|
+
abs_count = reader.varint()
|
|
294
|
+
for _ in range(abs_count):
|
|
295
|
+
reader.varint()
|
|
296
|
+
reader.varint()
|
|
297
|
+
locals_: list[LuaLocalListing] = []
|
|
298
|
+
for slot in range(reader.varint()):
|
|
299
|
+
locals_.append(
|
|
300
|
+
LuaLocalListing(
|
|
301
|
+
slot=slot,
|
|
302
|
+
name=_read_string(reader) or f"local{slot}",
|
|
303
|
+
start_pc=reader.varint() + 1,
|
|
304
|
+
end_pc=reader.varint() + 1,
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
upvalue_names = tuple(_read_string(reader) for _ in range(reader.varint()))
|
|
308
|
+
return line_info, tuple(locals_), upvalue_names
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _assign_local_register_slots(
|
|
312
|
+
locals_: tuple[LuaLocalListing, ...],
|
|
313
|
+
instructions: tuple[LuaInstructionListing, ...],
|
|
314
|
+
param_count: int,
|
|
315
|
+
) -> tuple[LuaLocalListing, ...]:
|
|
316
|
+
assigned: list[LuaLocalListing] = []
|
|
317
|
+
for index, local in enumerate(locals_):
|
|
318
|
+
slot = _infer_local_register_slot(local, locals_[:index], instructions, param_count, index)
|
|
319
|
+
assigned.append(
|
|
320
|
+
LuaLocalListing(
|
|
321
|
+
slot=slot,
|
|
322
|
+
name=local.name,
|
|
323
|
+
start_pc=local.start_pc,
|
|
324
|
+
end_pc=local.end_pc,
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
return tuple(assigned)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _infer_local_register_slot(
|
|
331
|
+
local: LuaLocalListing,
|
|
332
|
+
previous_locals: tuple[LuaLocalListing, ...],
|
|
333
|
+
instructions: tuple[LuaInstructionListing, ...],
|
|
334
|
+
param_count: int,
|
|
335
|
+
ordinal: int,
|
|
336
|
+
) -> int:
|
|
337
|
+
if local.start_pc <= 1 and ordinal < param_count:
|
|
338
|
+
return ordinal
|
|
339
|
+
|
|
340
|
+
current = _instruction_at_pc(instructions, local.start_pc)
|
|
341
|
+
if current is not None and current.opcode == "FORPREP" and current.operands:
|
|
342
|
+
same_for_state = sum(
|
|
343
|
+
1
|
|
344
|
+
for previous in previous_locals
|
|
345
|
+
if previous.start_pc == local.start_pc and previous.name == "(for state)"
|
|
346
|
+
)
|
|
347
|
+
if local.name == "(for state)" and same_for_state < 3:
|
|
348
|
+
return int(current.operands[0]) + same_for_state
|
|
349
|
+
|
|
350
|
+
prior = _instruction_at_pc(instructions, local.start_pc - 1)
|
|
351
|
+
if prior is not None and prior.opcode == "FORPREP" and prior.operands and local.name != "(for state)":
|
|
352
|
+
same_visible = sum(
|
|
353
|
+
1
|
|
354
|
+
for previous in previous_locals
|
|
355
|
+
if previous.start_pc == local.start_pc and previous.name != "(for state)"
|
|
356
|
+
)
|
|
357
|
+
return int(prior.operands[0]) + 3 + same_visible
|
|
358
|
+
|
|
359
|
+
write = _nearest_prior_register_write(local.start_pc, instructions)
|
|
360
|
+
if write is not None:
|
|
361
|
+
base, width = write
|
|
362
|
+
same_start = sum(1 for previous in previous_locals if previous.start_pc == local.start_pc)
|
|
363
|
+
if same_start < width:
|
|
364
|
+
return base + same_start
|
|
365
|
+
return base
|
|
366
|
+
|
|
367
|
+
return ordinal
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _instruction_at_pc(
|
|
371
|
+
instructions: tuple[LuaInstructionListing, ...],
|
|
372
|
+
pc: int,
|
|
373
|
+
) -> LuaInstructionListing | None:
|
|
374
|
+
for instruction in instructions:
|
|
375
|
+
if instruction.pc == pc:
|
|
376
|
+
return instruction
|
|
377
|
+
return None
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _nearest_prior_register_write(
|
|
381
|
+
start_pc: int,
|
|
382
|
+
instructions: tuple[LuaInstructionListing, ...],
|
|
383
|
+
) -> tuple[int, int] | None:
|
|
384
|
+
for instruction in reversed(tuple(instruction for instruction in instructions if instruction.pc < start_pc)):
|
|
385
|
+
write = _register_write(instruction)
|
|
386
|
+
if write is not None:
|
|
387
|
+
return write
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _register_write(instruction: LuaInstructionListing) -> tuple[int, int] | None:
|
|
392
|
+
if not instruction.operands or not instruction.operands[0].lstrip("-").isdigit():
|
|
393
|
+
return None
|
|
394
|
+
base = int(instruction.operands[0])
|
|
395
|
+
if instruction.opcode == "LOADNIL" and len(instruction.operands) >= 2:
|
|
396
|
+
return base, int(instruction.operands[1]) + 1
|
|
397
|
+
if instruction.opcode == "CALL" and len(instruction.operands) >= 3:
|
|
398
|
+
count = int(instruction.operands[2]) - 1
|
|
399
|
+
return base, max(1, count)
|
|
400
|
+
if instruction.opcode == "SELF":
|
|
401
|
+
return base, 2
|
|
402
|
+
if instruction.opcode == "VARARG" and len(instruction.operands) >= 2:
|
|
403
|
+
count = int(instruction.operands[1]) - 1
|
|
404
|
+
return base, max(1, count)
|
|
405
|
+
if instruction.opcode in _REGISTER_DEST_OPS:
|
|
406
|
+
return base, 1
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
_REGISTER_DEST_OPS = frozenset(
|
|
411
|
+
{
|
|
412
|
+
"ADDI",
|
|
413
|
+
"ADDK",
|
|
414
|
+
"ADD",
|
|
415
|
+
"BAND",
|
|
416
|
+
"BANDK",
|
|
417
|
+
"BNOT",
|
|
418
|
+
"BOR",
|
|
419
|
+
"BORK",
|
|
420
|
+
"BXOR",
|
|
421
|
+
"BXORK",
|
|
422
|
+
"CLOSURE",
|
|
423
|
+
"CONCAT",
|
|
424
|
+
"DIV",
|
|
425
|
+
"DIVK",
|
|
426
|
+
"GETFIELD",
|
|
427
|
+
"GETI",
|
|
428
|
+
"GETTABLE",
|
|
429
|
+
"GETTABUP",
|
|
430
|
+
"GETUPVAL",
|
|
431
|
+
"IDIV",
|
|
432
|
+
"IDIVK",
|
|
433
|
+
"LEN",
|
|
434
|
+
"LOADFALSE",
|
|
435
|
+
"LOADF",
|
|
436
|
+
"LOADI",
|
|
437
|
+
"LOADK",
|
|
438
|
+
"LOADKX",
|
|
439
|
+
"LOADTRUE",
|
|
440
|
+
"MOD",
|
|
441
|
+
"MODK",
|
|
442
|
+
"MUL",
|
|
443
|
+
"MULK",
|
|
444
|
+
"NEWTABLE",
|
|
445
|
+
"NOT",
|
|
446
|
+
"POW",
|
|
447
|
+
"POWK",
|
|
448
|
+
"SHL",
|
|
449
|
+
"SHLI",
|
|
450
|
+
"SHR",
|
|
451
|
+
"SHRI",
|
|
452
|
+
"SUB",
|
|
453
|
+
"SUBK",
|
|
454
|
+
"UNM",
|
|
455
|
+
}
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _read_string(reader: _Reader) -> str | None:
|
|
460
|
+
size = reader.varint()
|
|
461
|
+
if size == 0:
|
|
462
|
+
return None
|
|
463
|
+
return reader.read(size - 1).decode("utf-8", errors="replace")
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _flatten_and_infer(root: _ParsedFunction) -> tuple[_ParsedFunction, ...]:
|
|
467
|
+
functions: list[_ParsedFunction] = []
|
|
468
|
+
|
|
469
|
+
def visit(function: _ParsedFunction, inferred_name: str | None) -> None:
|
|
470
|
+
named = _replace_name(function, inferred_name)
|
|
471
|
+
parent_index = len(functions)
|
|
472
|
+
functions.append(named)
|
|
473
|
+
child_names = _child_names(named)
|
|
474
|
+
final_child_names: list[str] = []
|
|
475
|
+
for index, child in enumerate(named.children):
|
|
476
|
+
child_name = child_names.get(index) or f"<function_{len(functions)}>"
|
|
477
|
+
final_child_names.append(child_name)
|
|
478
|
+
visit(child, child_name)
|
|
479
|
+
functions[parent_index] = _replace_child_function_names(
|
|
480
|
+
named,
|
|
481
|
+
tuple(final_child_names),
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
visit(root, "<chunk>")
|
|
485
|
+
return tuple(functions)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _replace_name(function: _ParsedFunction, name: str | None) -> _ParsedFunction:
|
|
489
|
+
return _ParsedFunction(
|
|
490
|
+
kind=function.kind,
|
|
491
|
+
source=function.source,
|
|
492
|
+
line_start=function.line_start,
|
|
493
|
+
line_end=function.line_end,
|
|
494
|
+
param_count=function.param_count,
|
|
495
|
+
slot_count=function.slot_count,
|
|
496
|
+
instructions=function.instructions,
|
|
497
|
+
constants=function.constants,
|
|
498
|
+
locals=function.locals,
|
|
499
|
+
upvalues=function.upvalues,
|
|
500
|
+
child_count=function.child_count,
|
|
501
|
+
children=function.children,
|
|
502
|
+
inferred_name=name,
|
|
503
|
+
child_function_names=function.child_function_names,
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _replace_child_function_names(function: _ParsedFunction, names: tuple[str, ...]) -> _ParsedFunction:
|
|
508
|
+
return _ParsedFunction(
|
|
509
|
+
kind=function.kind,
|
|
510
|
+
source=function.source,
|
|
511
|
+
line_start=function.line_start,
|
|
512
|
+
line_end=function.line_end,
|
|
513
|
+
param_count=function.param_count,
|
|
514
|
+
slot_count=function.slot_count,
|
|
515
|
+
instructions=function.instructions,
|
|
516
|
+
constants=function.constants,
|
|
517
|
+
locals=function.locals,
|
|
518
|
+
upvalues=function.upvalues,
|
|
519
|
+
child_count=function.child_count,
|
|
520
|
+
children=function.children,
|
|
521
|
+
inferred_name=function.inferred_name,
|
|
522
|
+
child_function_names=names,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _child_names(function: _ParsedFunction) -> dict[int, str]:
|
|
527
|
+
names: dict[int, str] = {}
|
|
528
|
+
for instruction in function.instructions:
|
|
529
|
+
if instruction.opcode != "CLOSURE" or len(instruction.operands) < 2:
|
|
530
|
+
continue
|
|
531
|
+
register = int(instruction.operands[0])
|
|
532
|
+
child_index = int(instruction.operands[1])
|
|
533
|
+
local = _local_for_register(function.locals, register, instruction.pc + 1)
|
|
534
|
+
if local is not None:
|
|
535
|
+
names[child_index] = local.name
|
|
536
|
+
return names
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _to_listing(function: _ParsedFunction) -> LuaFunctionListing:
|
|
540
|
+
return LuaFunctionListing(
|
|
541
|
+
kind=function.kind,
|
|
542
|
+
source=function.source,
|
|
543
|
+
line_start=function.line_start,
|
|
544
|
+
line_end=function.line_end,
|
|
545
|
+
instruction_count=len(function.instructions),
|
|
546
|
+
param_count=function.param_count,
|
|
547
|
+
slot_count=function.slot_count,
|
|
548
|
+
upvalue_count=len(function.upvalues),
|
|
549
|
+
local_count=len(function.locals),
|
|
550
|
+
constant_count=len(function.constants),
|
|
551
|
+
child_function_count=function.child_count,
|
|
552
|
+
instructions=function.instructions,
|
|
553
|
+
constants=function.constants,
|
|
554
|
+
locals=function.locals,
|
|
555
|
+
upvalues=function.upvalues,
|
|
556
|
+
inferred_name=function.inferred_name,
|
|
557
|
+
child_function_names=function.child_function_names,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def _local_for_register(locals_: tuple[LuaLocalListing, ...], register: int, pc: int) -> LuaLocalListing | None:
|
|
562
|
+
for local in locals_:
|
|
563
|
+
if local.slot == register and local.start_pc <= pc < local.end_pc:
|
|
564
|
+
return local
|
|
565
|
+
return None
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def _line_for_pc(line_start: int, line_info: tuple[int, ...], pc: int) -> int | None:
|
|
569
|
+
if 1 <= pc <= len(line_info):
|
|
570
|
+
return line_start + sum(line_info[:pc])
|
|
571
|
+
return None
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _target_comment(instruction: LuaInstructionListing) -> str | None:
|
|
575
|
+
if instruction.opcode not in {"JMP", "FORLOOP", "FORPREP", "TFORPREP"} or not instruction.operands:
|
|
576
|
+
return None
|
|
577
|
+
try:
|
|
578
|
+
offset = int(instruction.operands[-1])
|
|
579
|
+
except ValueError:
|
|
580
|
+
return None
|
|
581
|
+
if instruction.opcode == "FORLOOP":
|
|
582
|
+
return f"to {instruction.pc + 1 - offset}"
|
|
583
|
+
return f"to {instruction.pc + 1 + offset}"
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _signed_byte(value: int) -> int:
|
|
587
|
+
return value - 256 if value > 127 else value
|