iris-decompiler 1.0.0
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.
- package/README.md +78 -0
- package/bin/cli.js +54 -0
- package/dist/base64.d.ts +1 -0
- package/dist/base64.js +38 -0
- package/dist/decompile_internal.d.ts +57 -0
- package/dist/decompile_internal.js +42 -0
- package/dist/decompiler.d.ts +4 -0
- package/dist/decompiler.js +2677 -0
- package/dist/disasm.d.ts +3 -0
- package/dist/disasm.js +321 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +109 -0
- package/dist/parser.d.ts +4 -0
- package/dist/parser.js +582 -0
- package/dist/types.d.ts +174 -0
- package/dist/types.js +299 -0
- package/package.json +25 -0
package/dist/disasm.d.ts
ADDED
package/dist/disasm.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.disassemble = disassemble;
|
|
4
|
+
exports.disassembleProto = disassembleProto;
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
function fmtNumber(v) {
|
|
7
|
+
if (Number.isNaN(v))
|
|
8
|
+
return "(0/0)";
|
|
9
|
+
if (!Number.isFinite(v))
|
|
10
|
+
return v < 0 ? "-(1/0)" : "(1/0)";
|
|
11
|
+
if (Math.floor(v) === v && Math.abs(v) < 1e15) {
|
|
12
|
+
return String(Math.trunc(v));
|
|
13
|
+
}
|
|
14
|
+
let s = v.toPrecision(10);
|
|
15
|
+
if (s.indexOf(".") !== -1) {
|
|
16
|
+
while (s.length > 0 && s[s.length - 1] === "0")
|
|
17
|
+
s = s.slice(0, -1);
|
|
18
|
+
if (s[s.length - 1] === ".")
|
|
19
|
+
s = s.slice(0, -1);
|
|
20
|
+
}
|
|
21
|
+
return s === "" ? "0" : s;
|
|
22
|
+
}
|
|
23
|
+
function escapeString(s) {
|
|
24
|
+
let out = '"';
|
|
25
|
+
for (let i = 0; i < s.length; i++) {
|
|
26
|
+
const c = s.charCodeAt(i);
|
|
27
|
+
switch (c) {
|
|
28
|
+
case 0x22:
|
|
29
|
+
out += '\\"';
|
|
30
|
+
break;
|
|
31
|
+
case 0x5c:
|
|
32
|
+
out += "\\\\";
|
|
33
|
+
break;
|
|
34
|
+
case 0x0a:
|
|
35
|
+
out += "\\n";
|
|
36
|
+
break;
|
|
37
|
+
case 0x0d:
|
|
38
|
+
out += "\\r";
|
|
39
|
+
break;
|
|
40
|
+
case 0x09:
|
|
41
|
+
out += "\\t";
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
if (c < 32) {
|
|
45
|
+
out += "\\" + c;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
out += s[i];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
out += '"';
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
function importPath(p, id) {
|
|
56
|
+
const count = id >>> 30;
|
|
57
|
+
const c0 = (id >>> 20) & 1023;
|
|
58
|
+
const c1 = (id >>> 10) & 1023;
|
|
59
|
+
const c2 = id & 1023;
|
|
60
|
+
const name = (idx) => {
|
|
61
|
+
if (idx < 0 || idx >= p.k.length || p.k[idx].kind !== types_1.ConstKind.String)
|
|
62
|
+
return "?";
|
|
63
|
+
return p.k[idx].str;
|
|
64
|
+
};
|
|
65
|
+
let path = name(c0);
|
|
66
|
+
if (count >= 2)
|
|
67
|
+
path += "." + name(c1);
|
|
68
|
+
if (count >= 3)
|
|
69
|
+
path += "." + name(c2);
|
|
70
|
+
return path;
|
|
71
|
+
}
|
|
72
|
+
function renderConst(os, p, c) {
|
|
73
|
+
switch (c.kind) {
|
|
74
|
+
case types_1.ConstKind.Nil:
|
|
75
|
+
os.push("nil");
|
|
76
|
+
break;
|
|
77
|
+
case types_1.ConstKind.Boolean:
|
|
78
|
+
os.push(c.boolean ? "true" : "false");
|
|
79
|
+
break;
|
|
80
|
+
case types_1.ConstKind.Number:
|
|
81
|
+
os.push(fmtNumber(c.number));
|
|
82
|
+
break;
|
|
83
|
+
case types_1.ConstKind.Integer:
|
|
84
|
+
os.push(String(c.integer));
|
|
85
|
+
break;
|
|
86
|
+
case types_1.ConstKind.String:
|
|
87
|
+
os.push(escapeString(c.str));
|
|
88
|
+
break;
|
|
89
|
+
case types_1.ConstKind.Import:
|
|
90
|
+
os.push("@import:" + p.debugname + ":" + importPath(p, c.importId));
|
|
91
|
+
break;
|
|
92
|
+
case types_1.ConstKind.Table:
|
|
93
|
+
case types_1.ConstKind.TableWithConstants: {
|
|
94
|
+
os.push("{ ");
|
|
95
|
+
for (let i = 0; i < c.table.length; i++) {
|
|
96
|
+
if (i)
|
|
97
|
+
os.push(", ");
|
|
98
|
+
const e = c.table[i];
|
|
99
|
+
if (e.key >= 0 && e.key < p.k.length)
|
|
100
|
+
renderConst(os, p, p.k[e.key]);
|
|
101
|
+
else
|
|
102
|
+
os.push("?key");
|
|
103
|
+
os.push(" = ");
|
|
104
|
+
if (e.value >= 0) {
|
|
105
|
+
if (e.value < p.k.length)
|
|
106
|
+
renderConst(os, p, p.k[e.value]);
|
|
107
|
+
else
|
|
108
|
+
os.push("?");
|
|
109
|
+
}
|
|
110
|
+
else
|
|
111
|
+
os.push("0");
|
|
112
|
+
}
|
|
113
|
+
os.push(" }");
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case types_1.ConstKind.Closure:
|
|
117
|
+
os.push("@closure[" + c.closureProto + "]");
|
|
118
|
+
break;
|
|
119
|
+
case types_1.ConstKind.Vector:
|
|
120
|
+
case types_1.ConstKind.VectorD:
|
|
121
|
+
os.push("vector(" + c.vec[0] + ", " + c.vec[1] + ", " + c.vec[2] + ")");
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
os.push("?");
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function constText(program, p, k) {
|
|
129
|
+
if (k < 0 || k >= p.k.length)
|
|
130
|
+
return "?";
|
|
131
|
+
const os = [];
|
|
132
|
+
renderConst(os, p, p.k[k]);
|
|
133
|
+
return os.join("");
|
|
134
|
+
}
|
|
135
|
+
function dumpProto(out, program, pi) {
|
|
136
|
+
const p = program.protos[pi];
|
|
137
|
+
out.push("== proto " + pi + ' "' + p.debugname + '" line ' + p.linedefined + "\n");
|
|
138
|
+
out.push("-- maxstack=" + p.maxstack + " params=" + p.numparams + " nups=" + p.nups +
|
|
139
|
+
" vararg=" + p.is_vararg + " flags=" + p.flags + "\n");
|
|
140
|
+
out.push("-- upvalue names: ");
|
|
141
|
+
for (let u = 0; u < p.upnames.length; u++) {
|
|
142
|
+
if (u)
|
|
143
|
+
out.push(", ");
|
|
144
|
+
out.push('"' + p.upnames[u] + '"');
|
|
145
|
+
}
|
|
146
|
+
out.push("\n");
|
|
147
|
+
out.push("-- constants:\n");
|
|
148
|
+
for (let k = 0; k < p.k.length; k++) {
|
|
149
|
+
const os = [];
|
|
150
|
+
renderConst(os, p, p.k[k]);
|
|
151
|
+
out.push(" [" + k + "] " + os.join("") + "\n");
|
|
152
|
+
}
|
|
153
|
+
out.push("\n");
|
|
154
|
+
out.push(" idx line insn\n");
|
|
155
|
+
let i = 0;
|
|
156
|
+
while (i < p.code.length) {
|
|
157
|
+
const insn = p.code[i];
|
|
158
|
+
const op = (0, types_1.iOp)(insn);
|
|
159
|
+
const line = [];
|
|
160
|
+
line.push(" " + (insn >>> 0).toString(16) + " ");
|
|
161
|
+
line.push(i + " ");
|
|
162
|
+
line.push((p.lineinfo.length === 0 || p.lineinfo[i] < 0) ? "-" : String(p.lineinfo[i]));
|
|
163
|
+
line.push(" " + (0, types_1.opName)(op));
|
|
164
|
+
const pushAB = () => line.push(" A=" + (0, types_1.iA)(insn));
|
|
165
|
+
const pushABC = () => line.push(" B=" + (0, types_1.iB)(insn) + " C=" + (0, types_1.iC)(insn));
|
|
166
|
+
switch (op) {
|
|
167
|
+
case types_1.Op.NOP:
|
|
168
|
+
case types_1.Op.BREAK:
|
|
169
|
+
case types_1.Op.NATIVECALL:
|
|
170
|
+
case types_1.Op.COVERAGE:
|
|
171
|
+
line.push(" D=" + (0, types_1.iE)(insn));
|
|
172
|
+
break;
|
|
173
|
+
case types_1.Op.LOADNIL:
|
|
174
|
+
case types_1.Op.LOADB:
|
|
175
|
+
case types_1.Op.MOVE:
|
|
176
|
+
case types_1.Op.GETGLOBAL:
|
|
177
|
+
case types_1.Op.GETUPVAL:
|
|
178
|
+
case types_1.Op.SETUPVAL:
|
|
179
|
+
case types_1.Op.CLOSEUPVALS:
|
|
180
|
+
case types_1.Op.NEWCLOSURE:
|
|
181
|
+
case types_1.Op.RETURN:
|
|
182
|
+
case types_1.Op.JUMP:
|
|
183
|
+
case types_1.Op.JUMPBACK:
|
|
184
|
+
case types_1.Op.JUMPIF:
|
|
185
|
+
case types_1.Op.JUMPIFNOT:
|
|
186
|
+
case types_1.Op.NOT:
|
|
187
|
+
case types_1.Op.MINUS:
|
|
188
|
+
case types_1.Op.LENGTH:
|
|
189
|
+
case types_1.Op.DUPTABLE:
|
|
190
|
+
case types_1.Op.LOADKX:
|
|
191
|
+
case types_1.Op.JUMPX:
|
|
192
|
+
case types_1.Op.PREPVARARGS:
|
|
193
|
+
case types_1.Op.GETVARARGS:
|
|
194
|
+
case types_1.Op.SETLIST:
|
|
195
|
+
case types_1.Op.NEWTABLE:
|
|
196
|
+
case types_1.Op.FORNPREP:
|
|
197
|
+
case types_1.Op.FORNLOOP:
|
|
198
|
+
case types_1.Op.FORGPREP:
|
|
199
|
+
case types_1.Op.FORGPREP_INEXT:
|
|
200
|
+
case types_1.Op.FORGPREP_NEXT:
|
|
201
|
+
case types_1.Op.FORGLOOP:
|
|
202
|
+
pushAB();
|
|
203
|
+
break;
|
|
204
|
+
case types_1.Op.GETTABLE:
|
|
205
|
+
case types_1.Op.SETTABLE:
|
|
206
|
+
case types_1.Op.GETTABLEN:
|
|
207
|
+
case types_1.Op.SETTABLEN:
|
|
208
|
+
case types_1.Op.GETTABLEKS:
|
|
209
|
+
case types_1.Op.SETTABLEKS:
|
|
210
|
+
case types_1.Op.ADD:
|
|
211
|
+
case types_1.Op.SUB:
|
|
212
|
+
case types_1.Op.MUL:
|
|
213
|
+
case types_1.Op.DIV:
|
|
214
|
+
case types_1.Op.MOD:
|
|
215
|
+
case types_1.Op.POW:
|
|
216
|
+
case types_1.Op.AND:
|
|
217
|
+
case types_1.Op.OR:
|
|
218
|
+
case types_1.Op.ADDK:
|
|
219
|
+
case types_1.Op.SUBK:
|
|
220
|
+
case types_1.Op.MULK:
|
|
221
|
+
case types_1.Op.DIVK:
|
|
222
|
+
case types_1.Op.MODK:
|
|
223
|
+
case types_1.Op.POWK:
|
|
224
|
+
case types_1.Op.ANDK:
|
|
225
|
+
case types_1.Op.ORK:
|
|
226
|
+
case types_1.Op.SUBRK:
|
|
227
|
+
case types_1.Op.DIVRK:
|
|
228
|
+
case types_1.Op.IDIV:
|
|
229
|
+
case types_1.Op.IDIVK:
|
|
230
|
+
pushABC();
|
|
231
|
+
break;
|
|
232
|
+
case types_1.Op.CONCAT:
|
|
233
|
+
pushABC();
|
|
234
|
+
break;
|
|
235
|
+
case types_1.Op.SETGLOBAL:
|
|
236
|
+
case types_1.Op.SETUPVAL:
|
|
237
|
+
line.push(" A=" + (0, types_1.iA)(insn) + " B=" + (0, types_1.iB)(insn));
|
|
238
|
+
break;
|
|
239
|
+
case types_1.Op.GETTABLEKS:
|
|
240
|
+
case types_1.Op.SETTABLEKS:
|
|
241
|
+
line.push(" A=" + (0, types_1.iA)(insn) + " B=" + (0, types_1.iB)(insn));
|
|
242
|
+
break;
|
|
243
|
+
case types_1.Op.NAMECALL:
|
|
244
|
+
line.push(" A=" + (0, types_1.iA)(insn) + " B=" + (0, types_1.iB)(insn) + " C=" + (0, types_1.iC)(insn));
|
|
245
|
+
break;
|
|
246
|
+
case types_1.Op.CALL:
|
|
247
|
+
case types_1.Op.CALLFB:
|
|
248
|
+
line.push(" A=" + (0, types_1.iA)(insn) + " B=" + (0, types_1.iB)(insn) + " C=" + (0, types_1.iC)(insn));
|
|
249
|
+
break;
|
|
250
|
+
case types_1.Op.LOADN:
|
|
251
|
+
line.push(" D=" + (0, types_1.iD)(insn));
|
|
252
|
+
break;
|
|
253
|
+
case types_1.Op.LOADK:
|
|
254
|
+
case types_1.Op.DUPTABLE:
|
|
255
|
+
line.push(" D=" + (0, types_1.iD)(insn) + " (k" + constText(program, p, (0, types_1.iD)(insn)) + ")");
|
|
256
|
+
break;
|
|
257
|
+
case types_1.Op.RETURN:
|
|
258
|
+
line.push(" A=" + (0, types_1.iA)(insn) + " B=" + (0, types_1.iB)(insn));
|
|
259
|
+
break;
|
|
260
|
+
case types_1.Op.JUMP:
|
|
261
|
+
case types_1.Op.JUMPBACK:
|
|
262
|
+
case types_1.Op.JUMPIF:
|
|
263
|
+
case types_1.Op.JUMPIFNOT:
|
|
264
|
+
line.push(" A=" + (0, types_1.iA)(insn) + " D=" + (0, types_1.iD)(insn));
|
|
265
|
+
break;
|
|
266
|
+
default: break;
|
|
267
|
+
}
|
|
268
|
+
const tgt = (0, types_1.jumpTarget)(p.code, i);
|
|
269
|
+
if (tgt >= 0)
|
|
270
|
+
line.push(" -> " + tgt);
|
|
271
|
+
if ((0, types_1.opHasAux)(op) && i + 1 < p.code.length) {
|
|
272
|
+
const aux = p.code[i + 1];
|
|
273
|
+
line.push(" ; aux=" + (aux >>> 0).toString(16));
|
|
274
|
+
if (op === types_1.Op.NEWTABLE)
|
|
275
|
+
line.push(" (hash" + (0, types_1.iC)(insn) + ", array " + aux + ")");
|
|
276
|
+
if (op === types_1.Op.SETLIST)
|
|
277
|
+
line.push(" (base " + aux + ")");
|
|
278
|
+
if (op === types_1.Op.FORGLOOP)
|
|
279
|
+
line.push(" (vars " + (aux & 0xff) + ((aux >>> 31) ? " ipairs" : "") + ")");
|
|
280
|
+
if (op === types_1.Op.GETIMPORT)
|
|
281
|
+
line.push(" (import " + (aux >>> 30) + ": " + (0, types_1.auxA)(aux) + "," + (0, types_1.auxB)(aux) + "," + (aux & 0x3ff) + " => " + importPath(p, aux) + ")");
|
|
282
|
+
if (op === types_1.Op.JUMPIFEQ || op === types_1.Op.JUMPIFLE || op === types_1.Op.JUMPIFLT ||
|
|
283
|
+
op === types_1.Op.JUMPIFNOTEQ || op === types_1.Op.JUMPIFNOTLE || op === types_1.Op.JUMPIFNOTLT)
|
|
284
|
+
line.push(" (aux reg " + (aux & 0xff) + ")");
|
|
285
|
+
if (op === types_1.Op.GETTABLEKS || op === types_1.Op.SETTABLEKS || op === types_1.Op.NAMECALL ||
|
|
286
|
+
op === types_1.Op.GETGLOBAL || op === types_1.Op.SETGLOBAL || op === types_1.Op.LOADKX)
|
|
287
|
+
line.push(" (k" + aux + " " + constText(program, p, aux) + ")");
|
|
288
|
+
}
|
|
289
|
+
out.push(line.join("") + "\n");
|
|
290
|
+
if (op === types_1.Op.NEWCLOSURE && (0, types_1.iD)(insn) >= 0 && (0, types_1.iD)(insn) < p.children.length) {
|
|
291
|
+
const child = program.protos[p.children[(0, types_1.iD)(insn)]];
|
|
292
|
+
for (let u = 0; u < child.nups; u++) {
|
|
293
|
+
i++;
|
|
294
|
+
if (i >= p.code.length)
|
|
295
|
+
break;
|
|
296
|
+
const cap = p.code[i];
|
|
297
|
+
const kind = (0, types_1.iA)(cap) === 1 ? "REF" : ((0, types_1.iA)(cap) === 2 ? "UPVAL" : "VAL");
|
|
298
|
+
out.push(" " + (cap >>> 0).toString(16) + " " + i + " - CAPTURE " + kind + " B=" + (0, types_1.iB)(cap) + "\n");
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if ((0, types_1.opHasAux)(op))
|
|
302
|
+
i++;
|
|
303
|
+
i++;
|
|
304
|
+
}
|
|
305
|
+
out.push("\n");
|
|
306
|
+
}
|
|
307
|
+
function disassemble(program) {
|
|
308
|
+
const out = [];
|
|
309
|
+
out.push("-- luau disassembly\n");
|
|
310
|
+
out.push("-- protos: " + program.protos.length + ", strings: " + program.strings.length + "\n\n");
|
|
311
|
+
for (let pi = 0; pi < program.protos.length; pi++)
|
|
312
|
+
dumpProto(out, program, pi);
|
|
313
|
+
return out.join("");
|
|
314
|
+
}
|
|
315
|
+
function disassembleProto(program, index) {
|
|
316
|
+
if (index < 0 || index >= program.protos.length)
|
|
317
|
+
return "-- no proto " + index + "\n";
|
|
318
|
+
const out = [];
|
|
319
|
+
dumpProto(out, program, index);
|
|
320
|
+
return out.join("");
|
|
321
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Program } from "./types";
|
|
2
|
+
import { ClosureResult, UpvalBind } from "./decompile_internal";
|
|
3
|
+
export { Program, Proto, Constant, ConstKind, Op } from "./types";
|
|
4
|
+
export { base64Decode } from "./base64";
|
|
5
|
+
export { disassemble, disassembleProto } from "./disasm";
|
|
6
|
+
export { decompile, decompileProto } from "./decompiler";
|
|
7
|
+
export { ClosureResult, Expr, Sink, UpvalBind } from "./decompile_internal";
|
|
8
|
+
export interface DecompileInput {
|
|
9
|
+
/** Raw Luau bytecode bytes. */
|
|
10
|
+
bytes?: Uint8Array;
|
|
11
|
+
/** Base64-encoded Luau bytecode (tried automatically if `bytes` fails to parse). */
|
|
12
|
+
base64?: string;
|
|
13
|
+
/** Optional path/name, used only for error context. */
|
|
14
|
+
name?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface DecompileResult {
|
|
17
|
+
/** Reconstructed, readable Luau source. */
|
|
18
|
+
source: string;
|
|
19
|
+
/** Parsed program IR (useful for further analysis). */
|
|
20
|
+
program: Program;
|
|
21
|
+
/** True when the bytes were base64-decoded before parsing. */
|
|
22
|
+
decodedFromBase64: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare class DecompilerError extends Error {
|
|
25
|
+
constructor(message: string);
|
|
26
|
+
}
|
|
27
|
+
export declare const Decompiler: {
|
|
28
|
+
/**
|
|
29
|
+
* Decompile Luau/Roblox bytecode to readable Luau source.
|
|
30
|
+
* Accepts raw bytes or a base64 string (the latter is auto-tried if raw bytes fail).
|
|
31
|
+
*/
|
|
32
|
+
decompile(input: DecompileInput | Uint8Array): DecompileResult;
|
|
33
|
+
/** Produce a textual disassembly of the bytecode (all protos). */
|
|
34
|
+
disassemble(input: DecompileInput | Uint8Array): string;
|
|
35
|
+
/** Produce a textual disassembly of a single proto by index. */
|
|
36
|
+
disassembleProto(input: DecompileInput | Uint8Array, index: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* Low-level: parse bytecode into the Program IR without decompiling.
|
|
39
|
+
* Returns { program, decodedFromBase64 }.
|
|
40
|
+
*/
|
|
41
|
+
parse(input: DecompileInput | Uint8Array): {
|
|
42
|
+
program: Program;
|
|
43
|
+
decodedFromBase64: boolean;
|
|
44
|
+
};
|
|
45
|
+
/** Decompile a single proto (with upvalue bindings) to a ClosureResult. */
|
|
46
|
+
decompileProto(program: Program, protoIndex: number, upvals?: UpvalBind[]): ClosureResult;
|
|
47
|
+
};
|
|
48
|
+
export default Decompiler;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Decompiler = exports.DecompilerError = exports.Sink = exports.decompileProto = exports.decompile = exports.disassembleProto = exports.disassemble = exports.base64Decode = exports.Op = exports.ConstKind = void 0;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
const parser_1 = require("./parser");
|
|
6
|
+
const base64_1 = require("./base64");
|
|
7
|
+
const disasm_1 = require("./disasm");
|
|
8
|
+
const decompiler_1 = require("./decompiler");
|
|
9
|
+
var types_2 = require("./types");
|
|
10
|
+
Object.defineProperty(exports, "ConstKind", { enumerable: true, get: function () { return types_2.ConstKind; } });
|
|
11
|
+
Object.defineProperty(exports, "Op", { enumerable: true, get: function () { return types_2.Op; } });
|
|
12
|
+
var base64_2 = require("./base64");
|
|
13
|
+
Object.defineProperty(exports, "base64Decode", { enumerable: true, get: function () { return base64_2.base64Decode; } });
|
|
14
|
+
var disasm_2 = require("./disasm");
|
|
15
|
+
Object.defineProperty(exports, "disassemble", { enumerable: true, get: function () { return disasm_2.disassemble; } });
|
|
16
|
+
Object.defineProperty(exports, "disassembleProto", { enumerable: true, get: function () { return disasm_2.disassembleProto; } });
|
|
17
|
+
var decompiler_2 = require("./decompiler");
|
|
18
|
+
Object.defineProperty(exports, "decompile", { enumerable: true, get: function () { return decompiler_2.decompile; } });
|
|
19
|
+
Object.defineProperty(exports, "decompileProto", { enumerable: true, get: function () { return decompiler_2.decompileProto; } });
|
|
20
|
+
var decompile_internal_1 = require("./decompile_internal");
|
|
21
|
+
Object.defineProperty(exports, "Sink", { enumerable: true, get: function () { return decompile_internal_1.Sink; } });
|
|
22
|
+
class DecompilerError extends Error {
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "DecompilerError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.DecompilerError = DecompilerError;
|
|
29
|
+
function parseInput(input) {
|
|
30
|
+
const program = (0, types_1.makeProgram)();
|
|
31
|
+
let fromBase64 = false;
|
|
32
|
+
if (input.bytes && input.bytes.length > 0) {
|
|
33
|
+
const err = { msg: "" };
|
|
34
|
+
if ((0, parser_1.parseBytecode)(input.bytes, program, err)) {
|
|
35
|
+
return { program, fromBase64 };
|
|
36
|
+
}
|
|
37
|
+
if (input.base64) {
|
|
38
|
+
// fall through to base64 path on failure
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw new DecompilerError(err.msg || "failed to parse bytecode");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (input.base64) {
|
|
45
|
+
const decoded = (0, base64_1.base64Decode)(input.base64);
|
|
46
|
+
if (!decoded) {
|
|
47
|
+
throw new DecompilerError("invalid base64 input");
|
|
48
|
+
}
|
|
49
|
+
const err = { msg: "" };
|
|
50
|
+
if (!(0, parser_1.parseBytecode)(decoded, program, err)) {
|
|
51
|
+
throw new DecompilerError(err.msg || "failed to parse bytecode");
|
|
52
|
+
}
|
|
53
|
+
fromBase64 = true;
|
|
54
|
+
return { program, fromBase64 };
|
|
55
|
+
}
|
|
56
|
+
if (input.bytes && input.bytes.length > 0) {
|
|
57
|
+
const err = { msg: "" };
|
|
58
|
+
if (!(0, parser_1.parseBytecode)(input.bytes, program, err)) {
|
|
59
|
+
throw new DecompilerError(err.msg || "failed to parse bytecode");
|
|
60
|
+
}
|
|
61
|
+
return { program, fromBase64 };
|
|
62
|
+
}
|
|
63
|
+
throw new DecompilerError("no bytecode provided (expected `bytes` or `base64`)");
|
|
64
|
+
}
|
|
65
|
+
exports.Decompiler = {
|
|
66
|
+
/**
|
|
67
|
+
* Decompile Luau/Roblox bytecode to readable Luau source.
|
|
68
|
+
* Accepts raw bytes or a base64 string (the latter is auto-tried if raw bytes fail).
|
|
69
|
+
*/
|
|
70
|
+
decompile(input) {
|
|
71
|
+
let resolved;
|
|
72
|
+
if (input instanceof Uint8Array)
|
|
73
|
+
resolved = { bytes: input };
|
|
74
|
+
else
|
|
75
|
+
resolved = input;
|
|
76
|
+
const { program, fromBase64 } = parseInput(resolved);
|
|
77
|
+
const source = (0, decompiler_1.decompile)(program);
|
|
78
|
+
return { source, program, decodedFromBase64: fromBase64 };
|
|
79
|
+
},
|
|
80
|
+
/** Produce a textual disassembly of the bytecode (all protos). */
|
|
81
|
+
disassemble(input) {
|
|
82
|
+
const resolved = input instanceof Uint8Array ? { bytes: input } : input;
|
|
83
|
+
const { program } = parseInput(resolved);
|
|
84
|
+
return (0, disasm_1.disassemble)(program);
|
|
85
|
+
},
|
|
86
|
+
/** Produce a textual disassembly of a single proto by index. */
|
|
87
|
+
disassembleProto(input, index) {
|
|
88
|
+
const resolved = input instanceof Uint8Array ? { bytes: input } : input;
|
|
89
|
+
const { program } = parseInput(resolved);
|
|
90
|
+
return (0, disasm_1.disassembleProto)(program, index);
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* Low-level: parse bytecode into the Program IR without decompiling.
|
|
94
|
+
* Returns { program, decodedFromBase64 }.
|
|
95
|
+
*/
|
|
96
|
+
parse(input) {
|
|
97
|
+
const resolved = input instanceof Uint8Array ? { bytes: input } : input;
|
|
98
|
+
const { program, fromBase64 } = parseInput(resolved);
|
|
99
|
+
return { program, decodedFromBase64: fromBase64 };
|
|
100
|
+
},
|
|
101
|
+
/** Decompile a single proto (with upvalue bindings) to a ClosureResult. */
|
|
102
|
+
decompileProto(program, protoIndex, upvals = []) {
|
|
103
|
+
const proto = program.protos[protoIndex];
|
|
104
|
+
if (!proto)
|
|
105
|
+
throw new DecompilerError(`no proto at index ${protoIndex}`);
|
|
106
|
+
return (0, decompiler_1.decompileProto)(program, proto, upvals);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
exports.default = exports.Decompiler;
|
package/dist/parser.d.ts
ADDED