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.
@@ -0,0 +1,2677 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decompileProto = decompileProto;
4
+ exports.decompile = decompile;
5
+ const types_1 = require("./types");
6
+ const decompile_internal_1 = require("./decompile_internal");
7
+ const K_HEADER = "";
8
+ const K_INDENT = "\t";
9
+ let parseBlockDepth = 0;
10
+ // ---------------------------------------------------------------------------
11
+ // String / constant formatting helpers
12
+ // ---------------------------------------------------------------------------
13
+ function fmtNumber(v) {
14
+ if (Number.isNaN(v))
15
+ return "(0/0)";
16
+ if (!Number.isFinite(v))
17
+ return v < 0 ? "-(1/0)" : "(1/0)";
18
+ if (Math.floor(v) === v && Math.abs(v) < 1e15) {
19
+ const whole = Math.trunc(v);
20
+ return String(whole);
21
+ }
22
+ let s = v.toPrecision(14);
23
+ if (s.indexOf(".") !== -1) {
24
+ while (s.length > 0 && s[s.length - 1] === "0")
25
+ s = s.slice(0, -1);
26
+ if (s[s.length - 1] === ".")
27
+ s = s.slice(0, -1);
28
+ }
29
+ return s === "" ? "0" : s;
30
+ }
31
+ function quote(s) {
32
+ let out = '"';
33
+ for (let i = 0; i < s.length; i++) {
34
+ const c = s.charCodeAt(i);
35
+ switch (c) {
36
+ case 0x22:
37
+ out += '\\"';
38
+ break;
39
+ case 0x5c:
40
+ out += "\\\\";
41
+ break;
42
+ case 0x0a:
43
+ out += "\\n";
44
+ break;
45
+ case 0x0d:
46
+ out += "\\r";
47
+ break;
48
+ case 0x09:
49
+ out += "\\t";
50
+ break;
51
+ case 0x00:
52
+ out += "\\0";
53
+ break;
54
+ default:
55
+ if (c < 32) {
56
+ out += "\\" + c;
57
+ }
58
+ else {
59
+ out += s[i];
60
+ }
61
+ }
62
+ }
63
+ out += '"';
64
+ return out;
65
+ }
66
+ function constValueText(c) {
67
+ switch (c.kind) {
68
+ case types_1.ConstKind.Nil: return "nil";
69
+ case types_1.ConstKind.Boolean: return c.boolean ? "true" : "false";
70
+ case types_1.ConstKind.Number: return fmtNumber(c.number);
71
+ case types_1.ConstKind.Integer: return String(c.integer);
72
+ case types_1.ConstKind.String: return quote(c.str);
73
+ case types_1.ConstKind.Vector:
74
+ case types_1.ConstKind.VectorD:
75
+ return "vector.create(" + fmtNumber(c.vec[0]) + ", " + fmtNumber(c.vec[1]) + ", " + fmtNumber(c.vec[2]) + ")";
76
+ default:
77
+ return "--[[constant]]";
78
+ }
79
+ }
80
+ const KEYWORDS = new Set([
81
+ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if",
82
+ "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until",
83
+ "while", "continue",
84
+ ]);
85
+ function isIdentifier(s) {
86
+ if (s.length === 0)
87
+ return false;
88
+ const c0 = s.charCodeAt(0);
89
+ if (!((c0 >= 0x41 && c0 <= 0x5a) || (c0 >= 0x61 && c0 <= 0x7a) || c0 === 0x5f))
90
+ return false;
91
+ for (let i = 1; i < s.length; i++) {
92
+ const c = s.charCodeAt(i);
93
+ if (!((c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || (c >= 0x30 && c <= 0x39) || c === 0x5f))
94
+ return false;
95
+ }
96
+ return !KEYWORDS.has(s);
97
+ }
98
+ function renderExpr(e) {
99
+ if (!e)
100
+ return [[0, "nil"]];
101
+ switch (e.kind) {
102
+ case decompile_internal_1.ExprK.Leaf:
103
+ case decompile_internal_1.ExprK.Vararg:
104
+ return [[0, e.text]];
105
+ case decompile_internal_1.ExprK.Un:
106
+ return renderUn(e);
107
+ case decompile_internal_1.ExprK.Bin:
108
+ return renderBin(e);
109
+ case decompile_internal_1.ExprK.Index: {
110
+ const b = renderExpr(e.a);
111
+ const k = renderExpr(e.b);
112
+ if (b.length === 1 && k.length === 1)
113
+ return [[0, b[0][1] + "[" + k[0][1] + "]"]];
114
+ b[b.length - 1][1] += "[";
115
+ for (const l of k)
116
+ b.push(l);
117
+ b[b.length - 1][1] += "]";
118
+ return b;
119
+ }
120
+ case decompile_internal_1.ExprK.Dot: {
121
+ const b = renderExpr(e.a);
122
+ for (const l of b)
123
+ l[1] += "." + e.name;
124
+ return b;
125
+ }
126
+ case decompile_internal_1.ExprK.Call:
127
+ return renderCallRows(e, "(");
128
+ case decompile_internal_1.ExprK.MethodCall:
129
+ return renderCallRows(e, ":" + e.name + "(");
130
+ case decompile_internal_1.ExprK.Table:
131
+ return renderTable(e);
132
+ case decompile_internal_1.ExprK.Func:
133
+ return renderFunc(e);
134
+ case decompile_internal_1.ExprK.IfElse: {
135
+ const condR = renderExpr(e.a);
136
+ const thenR = renderExpr(e.b);
137
+ const elseR = renderExpr(e.c);
138
+ const cstr = condR.length === 0 ? "false" : condR[0][1];
139
+ const tstr = thenR.length === 0 ? "nil" : thenR[0][1];
140
+ const estr = elseR.length === 0 ? "nil" : elseR[0][1];
141
+ return [[0, "if " + cstr + " then " + tstr + " else " + estr]];
142
+ }
143
+ default:
144
+ return [[0, "nil"]];
145
+ }
146
+ }
147
+ function tableKeyName(k) {
148
+ if (!k || k.kind !== decompile_internal_1.ExprK.Leaf)
149
+ return "";
150
+ let t = k.text;
151
+ if (t.length >= 2 && t[0] === '"' && t[t.length - 1] === '"')
152
+ t = t.slice(1, -1);
153
+ return isIdentifier(t) ? t : "";
154
+ }
155
+ function formatTableKey(k) {
156
+ if (!k)
157
+ return "";
158
+ const name = tableKeyName(k);
159
+ if (name.length > 0)
160
+ return name + " = ";
161
+ const kr = renderExpr(k);
162
+ if (kr.length > 0)
163
+ return "[" + kr[0][1] + "] = ";
164
+ return "";
165
+ }
166
+ function isSingleLineTable(e) {
167
+ if (!e || e.kind !== decompile_internal_1.ExprK.Table)
168
+ return false;
169
+ if (e.vals.length === 0)
170
+ return true;
171
+ if (e.vals.length > 3)
172
+ return false;
173
+ for (const val of e.vals) {
174
+ if (!val || val.kind === decompile_internal_1.ExprK.Table || val.kind === decompile_internal_1.ExprK.Func)
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+ function renderTableSingleLine(e) {
180
+ if (!e || e.vals.length === 0)
181
+ return [[0, "{}"]];
182
+ let cur = "{";
183
+ let hasKeys = false;
184
+ for (const k of e.keys)
185
+ if (k)
186
+ hasKeys = true;
187
+ if (!hasKeys) {
188
+ cur += " ";
189
+ for (let i = 0; i < e.vals.length; i++) {
190
+ if (i)
191
+ cur += ", ";
192
+ const vr = renderExpr(e.vals[i]);
193
+ if (vr.length === 0)
194
+ return [];
195
+ cur += vr[0][1];
196
+ }
197
+ cur += " }";
198
+ return [[0, cur]];
199
+ }
200
+ for (let i = 0; i < e.vals.length; i++) {
201
+ if (i)
202
+ cur += ", ";
203
+ else
204
+ cur += " ";
205
+ const kstr = formatTableKey(e.keys[i]);
206
+ const vr = renderExpr(e.vals[i]);
207
+ if (vr.length === 0)
208
+ return [];
209
+ cur += kstr + vr[0][1];
210
+ }
211
+ cur += " }";
212
+ return [[0, cur]];
213
+ }
214
+ function renderTable(e) {
215
+ if (!e || e.vals.length === 0)
216
+ return [[0, "{}"]];
217
+ if (isSingleLineTable(e)) {
218
+ const single = renderTableSingleLine(e);
219
+ if (single.length > 0 && single[0][1].length < 60)
220
+ return single;
221
+ }
222
+ const out = [];
223
+ out.push([0, "{"]);
224
+ for (let i = 0; i < e.vals.length; i++) {
225
+ const prefix = formatTableKey(e.keys[i]);
226
+ const vv = renderExpr(e.vals[i]);
227
+ if (vv.length === 0)
228
+ continue;
229
+ const addComma = (i + 1 < e.vals.length);
230
+ if (vv.length === 1) {
231
+ let line = prefix + vv[0][1];
232
+ if (addComma)
233
+ line += ",";
234
+ out.push([1, line]);
235
+ }
236
+ else {
237
+ out.push([1, prefix + vv[0][1]]);
238
+ for (let j = 1; j + 1 < vv.length; j++)
239
+ out.push([1 + vv[j][0], vv[j][1]]);
240
+ let last = vv[vv.length - 1][1];
241
+ if (addComma)
242
+ last += ",";
243
+ out.push([1 + vv[vv.length - 1][0], last]);
244
+ }
245
+ }
246
+ out.push([0, "}"]);
247
+ return out;
248
+ }
249
+ function renderFunc(fn) {
250
+ const out = [];
251
+ out.push([0, fn.funcHeader]);
252
+ for (const bl of fn.funcBodyLines)
253
+ out.push([bl[0] + 1, bl[1]]);
254
+ out.push([0, "end"]);
255
+ return out;
256
+ }
257
+ function combineLines(a, sep, b) {
258
+ if (a.length === 0)
259
+ return b;
260
+ if (b.length === 0)
261
+ return a;
262
+ if (a.length === 1 && b.length === 1)
263
+ return [[0, a[0][1] + sep + b[0][1]]];
264
+ if (b.length === 1) {
265
+ a[a.length - 1][1] += sep + b[0][1];
266
+ return a;
267
+ }
268
+ a[a.length - 1][1] += sep + b[0][1];
269
+ for (let i = 1; i < b.length; i++)
270
+ a.push(b[i]);
271
+ return a;
272
+ }
273
+ function wrapParens(rl, wrap) {
274
+ if (rl.length === 0 || !wrap)
275
+ return;
276
+ rl[0][1] = "(" + rl[0][1];
277
+ rl[rl.length - 1][1] += ")";
278
+ }
279
+ function renderBin(e) {
280
+ const parentPrec = e.prec;
281
+ const rightAssoc = e.rightAssoc;
282
+ let parenL = false;
283
+ let parenR = false;
284
+ if (e.a && e.a.kind === decompile_internal_1.ExprK.Bin &&
285
+ (e.a.prec < parentPrec || (e.a.prec === parentPrec && rightAssoc)))
286
+ parenL = true;
287
+ if (e.b && e.b.kind === decompile_internal_1.ExprK.Bin &&
288
+ (e.b.prec < parentPrec || (e.b.prec === parentPrec && !rightAssoc)))
289
+ parenR = true;
290
+ if (e.b && e.b.kind === decompile_internal_1.ExprK.Un && e.op !== "and" && e.op !== "or")
291
+ parenR = true;
292
+ if (e.a && e.a.kind === decompile_internal_1.ExprK.Un && e.op === "^")
293
+ parenL = true;
294
+ if (e.a && e.a.parenWrap)
295
+ parenL = true;
296
+ if (e.b && e.b.parenWrap)
297
+ parenR = true;
298
+ const a = renderExpr(e.a);
299
+ const b = renderExpr(e.b);
300
+ wrapParens(a, parenL);
301
+ wrapParens(b, parenR);
302
+ return combineLines(a, " " + e.op + " ", b);
303
+ }
304
+ function renderUn(e) {
305
+ const c = renderExpr(e.a);
306
+ const sep = (e.op === "not") ? " " : "";
307
+ const needParen = !!e.a && (e.a.kind === decompile_internal_1.ExprK.Bin || e.a.parenWrap);
308
+ if (needParen) {
309
+ if (c.length === 1)
310
+ return [[0, e.op + sep + "(" + c[0][1] + ")"]];
311
+ c[0][1] = e.op + sep + "(" + c[0][1];
312
+ c[c.length - 1][1] += ")";
313
+ return c;
314
+ }
315
+ c[0][1] = e.op + sep + c[0][1];
316
+ return c;
317
+ }
318
+ function renderCallRows(e, stub) {
319
+ const all = [];
320
+ for (const l of renderExpr(e.a))
321
+ all.push([l[0], l[1] + stub]);
322
+ for (let i = 0; i < e.args.length; i++) {
323
+ const a = renderExpr(e.args[i]);
324
+ if (a.length === 0)
325
+ continue;
326
+ const last = all[all.length - 1];
327
+ if (i)
328
+ last[1] += ", ";
329
+ last[1] += a[0][1];
330
+ for (let j = 1; j < a.length; j++)
331
+ all.push(a[j]);
332
+ }
333
+ if (all.length > 0)
334
+ all[all.length - 1][1] += ")";
335
+ return all;
336
+ }
337
+ function oneLine(e) {
338
+ const rl = renderExpr(e);
339
+ return rl.length === 0 ? "nil" : rl[0][1];
340
+ }
341
+ class CompileState {
342
+ constructor(program, proto, upvals, isMainChunk = false) {
343
+ this.liveOut = [];
344
+ this.n = 0;
345
+ this.R = 0;
346
+ this.maxLocals = 0;
347
+ this.nextV = 1;
348
+ this.tableCount = 0;
349
+ this.currentPc = -1;
350
+ this.usedNames = new Set();
351
+ this.loops = new Map();
352
+ this.program = program;
353
+ this.proto = proto;
354
+ this.upvals = upvals;
355
+ this.isMain = isMainChunk;
356
+ this.n = proto.code.length;
357
+ this.R = Math.max(proto.maxstack, proto.numparams + (proto.is_vararg !== 0 ? 8 : 0) + 8);
358
+ this.reg = new Array(this.R).fill(null);
359
+ this.regName = new Array(this.R).fill("");
360
+ this.regDeclared = new Array(this.R).fill(false);
361
+ this.regOrigin = new Array(this.R).fill(null);
362
+ this.methodInfo = new Array(this.R).fill(null);
363
+ this.captured = new Array(this.R).fill(false);
364
+ this.maxLocals = proto.numparams;
365
+ this.computeLive();
366
+ this.buildLoops();
367
+ for (const lv of proto.locvars)
368
+ if (lv.name.length > 0 && lv.name[0] !== "(")
369
+ this.usedNames.add(lv.name);
370
+ for (let r = 0; r < proto.numparams; r++) {
371
+ let nm = this.localNameAt(r, 0);
372
+ if (nm.length === 0)
373
+ nm = "p" + (r + 1);
374
+ this.regName[r] = nm;
375
+ this.reg[r] = this.leaf(nm);
376
+ this.regDeclared[r] = true;
377
+ this.usedNames.add(nm);
378
+ }
379
+ for (let i = 0; i < this.n; i++) {
380
+ const op = (0, types_1.iOp)(proto.code[i]);
381
+ if (op === types_1.Op.NEWCLOSURE) {
382
+ const child = (0, types_1.iD)(proto.code[i]);
383
+ if (child >= 0 && child < proto.children.length) {
384
+ const c = program.protos[proto.children[child]];
385
+ for (let u = 0; u < c.nups && i + 1 + u < this.n; u++) {
386
+ const cap = proto.code[i + 1 + u];
387
+ if ((0, types_1.iA)(cap) !== 2) {
388
+ const src = (0, types_1.iB)(cap);
389
+ if (src < this.R)
390
+ this.captured[src] = true;
391
+ }
392
+ }
393
+ }
394
+ }
395
+ else if (op === types_1.Op.DUPCLOSURE) {
396
+ const cst = this.constAt((0, types_1.iD)(proto.code[i]));
397
+ if (cst && cst.kind === types_1.ConstKind.Closure && cst.closureProto >= 0 &&
398
+ cst.closureProto < program.protos.length) {
399
+ const c = program.protos[cst.closureProto];
400
+ for (let u = 0; u < c.nups && i + 1 + u < this.n; u++) {
401
+ const cap = proto.code[i + 1 + u];
402
+ if ((0, types_1.iA)(cap) !== 2) {
403
+ const src = (0, types_1.iB)(cap);
404
+ if (src < this.R)
405
+ this.captured[src] = true;
406
+ }
407
+ }
408
+ }
409
+ }
410
+ }
411
+ }
412
+ constAt(idx) {
413
+ if (idx < 0 || idx >= this.proto.k.length)
414
+ return null;
415
+ return this.proto.k[idx];
416
+ }
417
+ constTextText(idx) {
418
+ const c = this.constAt(idx);
419
+ return c ? constValueText(c) : "--[[?]]";
420
+ }
421
+ importPathText(id) {
422
+ const count = id >>> 30;
423
+ const c0 = (id >>> 20) & 1023;
424
+ const c1 = (id >>> 10) & 1023;
425
+ const c2 = id & 1023;
426
+ const name = (ci) => {
427
+ const c = this.constAt(ci);
428
+ return (c && c.kind === types_1.ConstKind.String) ? c.str : "?";
429
+ };
430
+ let p = name(c0);
431
+ if (count >= 2)
432
+ p += "." + name(c1);
433
+ if (count >= 3)
434
+ p += "." + name(c2);
435
+ return p;
436
+ }
437
+ localNameAt(regIdx, at, isDecl) {
438
+ for (const lv of this.proto.locvars) {
439
+ if (lv.reg !== regIdx)
440
+ continue;
441
+ if (lv.name.length > 0 && lv.name[0] === "(")
442
+ continue;
443
+ if (at >= lv.startpc && at < lv.endpc) {
444
+ if (isDecl && lv.startpc === at)
445
+ isDecl.v = true;
446
+ return lv.name;
447
+ }
448
+ }
449
+ return "";
450
+ }
451
+ getLocvarName(regIdx, at) {
452
+ for (const lv of this.proto.locvars) {
453
+ if (lv.reg === regIdx && lv.name.length > 0 && lv.name[0] !== "(") {
454
+ if (at >= lv.startpc && at < lv.endpc)
455
+ return lv.name;
456
+ }
457
+ }
458
+ return "";
459
+ }
460
+ makeVarName(prefix = "v") {
461
+ for (;;) {
462
+ const cand = prefix + this.nextV++;
463
+ if (this.usedNames.add(cand))
464
+ return cand;
465
+ }
466
+ }
467
+ makeTableName() {
468
+ if (this.tableCount === 0) {
469
+ this.tableCount++;
470
+ if (this.usedNames.add("t"))
471
+ return "t";
472
+ }
473
+ else if (this.tableCount === 1) {
474
+ this.tableCount++;
475
+ if (this.usedNames.add("tbl"))
476
+ return "tbl";
477
+ }
478
+ for (;;) {
479
+ const cand = "t" + this.tableCount++;
480
+ if (this.usedNames.add(cand))
481
+ return cand;
482
+ }
483
+ }
484
+ bindRegName(r, name) {
485
+ this.regName[r] = name;
486
+ this.reg[r] = this.leaf(name);
487
+ }
488
+ clearRegState(r) {
489
+ if (r < 0 || r >= this.R)
490
+ return;
491
+ this.regOrigin[r] = null;
492
+ this.reg[r] = null;
493
+ this.regName[r] = "";
494
+ this.regDeclared[r] = false;
495
+ this.methodInfo[r] = null;
496
+ }
497
+ upName(slot) {
498
+ if (slot >= 0 && slot < this.upvals.length && this.upvals[slot].name.length > 0)
499
+ return this.upvals[slot].name;
500
+ if (slot >= 0 && slot < this.proto.upnames.length && this.proto.upnames[slot].length > 0 &&
501
+ this.proto.upnames[slot][0] !== "(")
502
+ return this.proto.upnames[slot];
503
+ return "u" + slot;
504
+ }
505
+ captureName(out, at, src) {
506
+ if (src < this.proto.numparams)
507
+ return this.regName[src].length === 0 ? ("p" + (src + 1)) : this.regName[src];
508
+ const nm = this.getLocvarName(src, at);
509
+ if (nm.length > 0)
510
+ return nm;
511
+ if (this.regName[src].length > 0)
512
+ return this.regName[src];
513
+ this.forceLocalName(out, src);
514
+ return this.regName[src];
515
+ }
516
+ leaf(s) {
517
+ const e = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Leaf);
518
+ e.text = s;
519
+ return e;
520
+ }
521
+ bin(op, a, b, prec, rightAssoc = false) {
522
+ const e = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Bin);
523
+ e.op = op;
524
+ e.a = a;
525
+ e.b = b;
526
+ e.prec = prec;
527
+ e.rightAssoc = rightAssoc;
528
+ return e;
529
+ }
530
+ un(op, a) {
531
+ const e = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Un);
532
+ e.op = op;
533
+ e.a = a;
534
+ e.prec = 8;
535
+ return e;
536
+ }
537
+ idx(base, key) {
538
+ const e = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Index);
539
+ e.a = base;
540
+ e.b = key;
541
+ e.prec = 9;
542
+ return e;
543
+ }
544
+ dot(base, name) {
545
+ const e = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Dot);
546
+ e.a = base;
547
+ e.name = name;
548
+ e.prec = 9;
549
+ return e;
550
+ }
551
+ call(callee, args) {
552
+ const e = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Call);
553
+ e.a = callee;
554
+ e.args = args;
555
+ e.prec = 9;
556
+ return e;
557
+ }
558
+ methodCall(base, name, args) {
559
+ const e = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.MethodCall);
560
+ e.a = base;
561
+ e.name = name;
562
+ e.args = args;
563
+ e.prec = 9;
564
+ return e;
565
+ }
566
+ ifElse(cond, thenVal, elseVal) {
567
+ const e = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.IfElse);
568
+ e.a = cond;
569
+ e.b = thenVal;
570
+ e.c = elseVal;
571
+ e.prec = 0;
572
+ return e;
573
+ }
574
+ readReg(r) {
575
+ if (r >= this.R)
576
+ return this.leaf("--[[reg?]]");
577
+ if (this.reg[r])
578
+ return this.reg[r];
579
+ if (this.regName[r].length > 0)
580
+ return this.leaf(this.regName[r]);
581
+ if (this.regOrigin[r])
582
+ return this.regOrigin[r];
583
+ if (r < this.proto.numparams)
584
+ return this.leaf(this.regName[r].length === 0 ? ("p" + (r + 1)) : this.regName[r]);
585
+ if (this.currentPc >= 0) {
586
+ const nm = this.getLocvarName(r, this.currentPc);
587
+ if (nm.length > 0)
588
+ return this.leaf(nm);
589
+ }
590
+ return this.leaf("nil");
591
+ }
592
+ gatherRegs(at, insn, op, defs, uses) {
593
+ const useR = (r) => { if (r >= 0 && r < this.R)
594
+ uses.push(r); };
595
+ const defR = (r) => { if (r >= 0 && r < this.R)
596
+ defs.push(r); };
597
+ switch (op) {
598
+ case types_1.Op.MOVE:
599
+ defR((0, types_1.iA)(insn));
600
+ useR((0, types_1.iB)(insn));
601
+ break;
602
+ case types_1.Op.LOADNIL: {
603
+ const b = (0, types_1.iB)(insn);
604
+ for (let r = (0, types_1.iA)(insn); r <= (0, types_1.iA)(insn) + b; r++)
605
+ defR(r);
606
+ break;
607
+ }
608
+ case types_1.Op.LOADB:
609
+ case types_1.Op.LOADN:
610
+ case types_1.Op.LOADK:
611
+ case types_1.Op.LOADKX:
612
+ case types_1.Op.GETGLOBAL:
613
+ case types_1.Op.GETUPVAL:
614
+ case types_1.Op.GETIMPORT:
615
+ case types_1.Op.GETTABLE:
616
+ case types_1.Op.GETTABLEKS:
617
+ case types_1.Op.GETTABLEN:
618
+ case types_1.Op.GETUDATAKS:
619
+ case types_1.Op.NEWTABLE:
620
+ case types_1.Op.DUPTABLE:
621
+ case types_1.Op.NEWCLOSURE:
622
+ case types_1.Op.DUPCLOSURE:
623
+ case types_1.Op.NOT:
624
+ case types_1.Op.MINUS:
625
+ case types_1.Op.LENGTH:
626
+ case types_1.Op.GETVARARGS:
627
+ defR((0, types_1.iA)(insn));
628
+ break;
629
+ default: break;
630
+ }
631
+ switch (op) {
632
+ case types_1.Op.GETTABLE:
633
+ case types_1.Op.SETTABLE:
634
+ useR((0, types_1.iB)(insn));
635
+ useR((0, types_1.iC)(insn));
636
+ break;
637
+ case types_1.Op.GETTABLEKS:
638
+ case types_1.Op.SETTABLEKS:
639
+ case types_1.Op.GETUDATAKS:
640
+ case types_1.Op.SETUDATAKS:
641
+ case types_1.Op.GETTABLEN:
642
+ case types_1.Op.SETTABLEN:
643
+ useR((0, types_1.iB)(insn));
644
+ break;
645
+ case types_1.Op.SETGLOBAL:
646
+ case types_1.Op.SETUPVAL:
647
+ useR((0, types_1.iA)(insn));
648
+ break;
649
+ case types_1.Op.NAMECALL:
650
+ case types_1.Op.NAMECALLUDATA:
651
+ useR((0, types_1.iB)(insn));
652
+ break;
653
+ case types_1.Op.CALL:
654
+ case types_1.Op.CALLFB: {
655
+ const A = (0, types_1.iA)(insn);
656
+ const B = (0, types_1.iB)(insn);
657
+ const C = (0, types_1.iC)(insn);
658
+ const nargs = (B === 0) ? -1 : B - 1;
659
+ useR(A);
660
+ if (nargs > 0)
661
+ for (let i = 1; i <= nargs; i++)
662
+ useR(A + i);
663
+ const nres = (C === 0) ? -1 : C - 1;
664
+ if (nres > 0)
665
+ for (let i = 0; i < nres; i++)
666
+ defR(A + i);
667
+ break;
668
+ }
669
+ case types_1.Op.RETURN: {
670
+ const b = (0, types_1.iB)(insn);
671
+ if (b >= 2) {
672
+ for (let r = (0, types_1.iA)(insn); r < (0, types_1.iA)(insn) + b - 1; r++)
673
+ useR(r);
674
+ }
675
+ else if (b === 0) {
676
+ useR((0, types_1.iA)(insn));
677
+ }
678
+ break;
679
+ }
680
+ case types_1.Op.JUMPIF:
681
+ case types_1.Op.JUMPIFNOT:
682
+ case types_1.Op.JUMPXEQKNIL:
683
+ case types_1.Op.JUMPXEQKB:
684
+ case types_1.Op.JUMPXEQKN:
685
+ case types_1.Op.JUMPXEQKS:
686
+ useR((0, types_1.iA)(insn));
687
+ break;
688
+ case types_1.Op.JUMPIFEQ:
689
+ case types_1.Op.JUMPIFLE:
690
+ case types_1.Op.JUMPIFLT:
691
+ case types_1.Op.JUMPIFNOTEQ:
692
+ case types_1.Op.JUMPIFNOTLE:
693
+ case types_1.Op.JUMPIFNOTLT:
694
+ useR((0, types_1.iA)(insn));
695
+ if (at + 1 < this.n)
696
+ useR((0, types_1.auxA)(this.proto.code[at + 1]));
697
+ break;
698
+ case types_1.Op.ADD:
699
+ case types_1.Op.SUB:
700
+ case types_1.Op.MUL:
701
+ case types_1.Op.DIV:
702
+ case types_1.Op.MOD:
703
+ case types_1.Op.POW:
704
+ case types_1.Op.IDIV:
705
+ case types_1.Op.AND:
706
+ case types_1.Op.OR:
707
+ useR((0, types_1.iB)(insn));
708
+ useR((0, types_1.iC)(insn));
709
+ break;
710
+ case types_1.Op.ADDK:
711
+ case types_1.Op.SUBK:
712
+ case types_1.Op.MULK:
713
+ case types_1.Op.DIVK:
714
+ case types_1.Op.MODK:
715
+ case types_1.Op.POWK:
716
+ case types_1.Op.ANDK:
717
+ case types_1.Op.ORK:
718
+ case types_1.Op.IDIVK:
719
+ useR((0, types_1.iB)(insn));
720
+ break;
721
+ case types_1.Op.SUBRK:
722
+ case types_1.Op.DIVRK:
723
+ useR((0, types_1.iC)(insn));
724
+ break;
725
+ case types_1.Op.CONCAT:
726
+ for (let r = (0, types_1.iB)(insn); r <= (0, types_1.iC)(insn); r++)
727
+ useR(r);
728
+ break;
729
+ case types_1.Op.SETLIST: {
730
+ const cnt = ((0, types_1.iC)(insn) === 0) ? 1 : (0, types_1.iC)(insn) - 1;
731
+ for (let r = (0, types_1.iB)(insn); r < (0, types_1.iB)(insn) + cnt; r++)
732
+ useR(r);
733
+ break;
734
+ }
735
+ case types_1.Op.NEWCLOSURE: {
736
+ const child = (0, types_1.iD)(insn);
737
+ if (child >= 0 && child < this.proto.children.length) {
738
+ const c = this.program.protos[this.proto.children[child]];
739
+ for (let u = 0; u < c.nups; u++) {
740
+ if (at + 1 + u >= this.n)
741
+ break;
742
+ const cap = this.proto.code[at + 1 + u];
743
+ if ((0, types_1.iA)(cap) !== 2)
744
+ useR((0, types_1.iB)(cap));
745
+ }
746
+ }
747
+ break;
748
+ }
749
+ case types_1.Op.DUPCLOSURE: {
750
+ const cst = this.constAt((0, types_1.iD)(insn));
751
+ if (cst && cst.kind === types_1.ConstKind.Closure && cst.closureProto >= 0 &&
752
+ cst.closureProto < this.program.protos.length) {
753
+ const c = this.program.protos[cst.closureProto];
754
+ for (let u = 0; u < c.nups && at + 1 + u < this.n; u++) {
755
+ const cap = this.proto.code[at + 1 + u];
756
+ if ((0, types_1.iA)(cap) !== 2)
757
+ useR((0, types_1.iB)(cap));
758
+ }
759
+ }
760
+ break;
761
+ }
762
+ case types_1.Op.FORNPREP:
763
+ useR((0, types_1.iA)(insn));
764
+ useR((0, types_1.iA)(insn) + 1);
765
+ useR((0, types_1.iA)(insn) + 2);
766
+ break;
767
+ case types_1.Op.FORNLOOP:
768
+ defR((0, types_1.iA)(insn) + 3);
769
+ break;
770
+ case types_1.Op.FORGPREP:
771
+ case types_1.Op.FORGPREP_INEXT:
772
+ case types_1.Op.FORGPREP_NEXT:
773
+ useR((0, types_1.iA)(insn));
774
+ useR((0, types_1.iA)(insn) + 1);
775
+ useR((0, types_1.iA)(insn) + 2);
776
+ break;
777
+ case types_1.Op.FORGLOOP:
778
+ if (at + 1 < this.n) {
779
+ const vc = this.proto.code[at + 1] & 0xff;
780
+ for (let k = 0; k < vc; k++)
781
+ defR((0, types_1.iA)(insn) + 3 + k);
782
+ }
783
+ break;
784
+ default: break;
785
+ }
786
+ }
787
+ computeLive() {
788
+ this.liveOut = new Array(this.n);
789
+ for (let i = 0; i < this.n; i++)
790
+ this.liveOut[i] = new Array(this.R).fill(false);
791
+ const liveIn = new Array(this.R).fill(false);
792
+ const defs = [];
793
+ const uses = [];
794
+ for (let iter = 0; iter < 4; iter++) {
795
+ for (let ipc = this.n - 1; ipc >= 0; ipc--) {
796
+ const insn = this.proto.code[ipc];
797
+ this.gatherRegs(ipc, insn, (0, types_1.iOp)(insn), defs, uses);
798
+ liveIn.fill(false);
799
+ for (let i = 0; i < this.R; i++)
800
+ liveIn[i] = this.liveOut[ipc][i];
801
+ for (let i = 0; i < defs.length; i++)
802
+ if (defs[i] < this.R)
803
+ liveIn[defs[i]] = false;
804
+ for (let i = 0; i < uses.length; i++)
805
+ if (uses[i] < this.R)
806
+ liveIn[uses[i]] = true;
807
+ if (ipc > 0)
808
+ this.liveOut[ipc - 1] = liveIn.slice();
809
+ }
810
+ }
811
+ }
812
+ liveAfter(idx, r) {
813
+ if (idx < 0 || idx >= this.n || r < 0 || r >= this.R)
814
+ return false;
815
+ if (r >= this.liveOut[idx].length)
816
+ return false;
817
+ return this.liveOut[idx][r];
818
+ }
819
+ isCondJump(op) {
820
+ switch (op) {
821
+ case types_1.Op.JUMPIF:
822
+ case types_1.Op.JUMPIFNOT:
823
+ case types_1.Op.JUMPIFEQ:
824
+ case types_1.Op.JUMPIFLE:
825
+ case types_1.Op.JUMPIFLT:
826
+ case types_1.Op.JUMPIFNOTEQ:
827
+ case types_1.Op.JUMPIFNOTLE:
828
+ case types_1.Op.JUMPIFNOTLT:
829
+ case types_1.Op.JUMPXEQKNIL:
830
+ case types_1.Op.JUMPXEQKB:
831
+ case types_1.Op.JUMPXEQKN:
832
+ case types_1.Op.JUMPXEQKS:
833
+ return true;
834
+ default:
835
+ return false;
836
+ }
837
+ }
838
+ buildLoops() {
839
+ const headers = new Set();
840
+ for (let i = 0; i < this.n; i++) {
841
+ const op = (0, types_1.iOp)(this.proto.code[i]);
842
+ if ((0, types_1.opLength)(op) === 2 || op === types_1.Op.JUMP || op === types_1.Op.JUMPBACK || op === types_1.Op.JUMPX) {
843
+ const t = (0, types_1.jumpTarget)(this.proto.code, i);
844
+ if (t >= 0 && t < this.n && t < i) {
845
+ if (t > 0) {
846
+ const po = (0, types_1.iOp)(this.proto.code[t - 1]);
847
+ if (po === types_1.Op.FORGPREP || po === types_1.Op.FORGPREP_INEXT || po === types_1.Op.FORGPREP_NEXT ||
848
+ po === types_1.Op.FORNPREP)
849
+ headers.add(t - 1);
850
+ else
851
+ headers.add(t);
852
+ }
853
+ else {
854
+ headers.add(t);
855
+ }
856
+ }
857
+ }
858
+ }
859
+ for (const h of headers) {
860
+ if (this.loops.has(h))
861
+ continue;
862
+ const l = {
863
+ type: 0, header: h, exit: 0, bodyStart: 0, backedge: -1, forA: 0,
864
+ varCount: 0, condJump: -1, isIpairs: false,
865
+ };
866
+ const hop = (0, types_1.iOp)(this.proto.code[h]);
867
+ if (hop === types_1.Op.FORNPREP) {
868
+ l.type = 3;
869
+ l.forA = (0, types_1.iA)(this.proto.code[h]);
870
+ l.exit = h + 1 + (0, types_1.iD)(this.proto.code[h]);
871
+ l.bodyStart = h + 1;
872
+ for (let i = h + 1; i < l.exit && i < this.n; i++) {
873
+ if ((0, types_1.iOp)(this.proto.code[i]) === types_1.Op.FORNLOOP &&
874
+ ((0, types_1.jumpTarget)(this.proto.code, i) === h || (0, types_1.jumpTarget)(this.proto.code, i) === h + 1)) {
875
+ l.backedge = i;
876
+ break;
877
+ }
878
+ }
879
+ if (l.backedge < 0)
880
+ continue;
881
+ }
882
+ else if (hop === types_1.Op.FORGPREP || hop === types_1.Op.FORGPREP_INEXT || hop === types_1.Op.FORGPREP_NEXT) {
883
+ l.type = 4;
884
+ l.forA = (0, types_1.iA)(this.proto.code[h]);
885
+ l.isIpairs = (hop === types_1.Op.FORGPREP_INEXT);
886
+ const forgloop = (0, types_1.jumpTarget)(this.proto.code, h);
887
+ if (forgloop < 0 || forgloop >= this.n || forgloop <= h)
888
+ continue;
889
+ l.backedge = forgloop;
890
+ l.bodyStart = h + 1;
891
+ l.exit = forgloop + 2;
892
+ if (l.backedge + 1 < this.n)
893
+ l.varCount = this.proto.code[l.backedge + 1] & 0xff;
894
+ }
895
+ else {
896
+ let be = -1;
897
+ for (let i = h + 1; i < this.n; i++) {
898
+ const o = (0, types_1.iOp)(this.proto.code[i]);
899
+ if ((0, types_1.opLength)(o) === 2 || o === types_1.Op.JUMP || o === types_1.Op.JUMPBACK || o === types_1.Op.JUMPX) {
900
+ const t = (0, types_1.jumpTarget)(this.proto.code, i);
901
+ if (t === h) {
902
+ be = i;
903
+ break;
904
+ }
905
+ }
906
+ }
907
+ if (be < 0)
908
+ continue;
909
+ const beOp = (0, types_1.iOp)(this.proto.code[be]);
910
+ let cj = -1;
911
+ for (let i = h; i < be && i < this.n;) {
912
+ const o = (0, types_1.iOp)(this.proto.code[i]);
913
+ if (this.isCondJump(o)) {
914
+ const tgt = (0, types_1.jumpTarget)(this.proto.code, i);
915
+ if (tgt > be) {
916
+ cj = i;
917
+ break;
918
+ }
919
+ }
920
+ i += (0, types_1.opLength)(o);
921
+ }
922
+ if (cj >= 0) {
923
+ l.type = 0;
924
+ l.condJump = cj;
925
+ l.exit = (0, types_1.jumpTarget)(this.proto.code, cj);
926
+ l.bodyStart = cj + (0, types_1.opLength)((0, types_1.iOp)(this.proto.code[cj]));
927
+ l.backedge = be;
928
+ }
929
+ else if (beOp === types_1.Op.JUMPBACK || beOp === types_1.Op.JUMP) {
930
+ l.type = 1;
931
+ l.exit = be + 1;
932
+ l.bodyStart = h;
933
+ l.backedge = be;
934
+ }
935
+ else {
936
+ l.type = 2;
937
+ l.exit = be + (0, types_1.opLength)(beOp);
938
+ l.bodyStart = h;
939
+ l.backedge = be;
940
+ }
941
+ }
942
+ this.loops.set(h, l);
943
+ }
944
+ }
945
+ condFromJump(idx, invert = true) {
946
+ const insn = this.proto.code[idx];
947
+ const op = (0, types_1.iOp)(insn);
948
+ const A = (0, types_1.iA)(insn);
949
+ const lhs = this.readReg(A);
950
+ const rhs2 = () => {
951
+ if (idx + 1 < this.n)
952
+ return this.readReg((0, types_1.auxA)(this.proto.code[idx + 1]));
953
+ return this.leaf("nil");
954
+ };
955
+ switch (op) {
956
+ case types_1.Op.JUMPIF: return invert ? this.un("not", lhs) : lhs;
957
+ case types_1.Op.JUMPIFNOT: return invert ? lhs : this.un("not", lhs);
958
+ case types_1.Op.JUMPIFEQ: return this.bin(invert ? "~=" : "==", lhs, rhs2(), 3);
959
+ case types_1.Op.JUMPIFLE: return this.bin(invert ? ">" : "<=", lhs, rhs2(), 3);
960
+ case types_1.Op.JUMPIFLT: return this.bin(invert ? ">=" : "<", lhs, rhs2(), 3);
961
+ case types_1.Op.JUMPIFNOTEQ: return this.bin(invert ? "==" : "~=", lhs, rhs2(), 3);
962
+ case types_1.Op.JUMPIFNOTLE: return this.bin(invert ? "<=" : ">", lhs, rhs2(), 3);
963
+ case types_1.Op.JUMPIFNOTLT: return this.bin(invert ? "<" : ">=", lhs, rhs2(), 3);
964
+ case types_1.Op.JUMPXEQKNIL: {
965
+ const notFlag = (0, types_1.auxNOT)(this.proto.code[idx + 1]);
966
+ const eq = invert ? notFlag : !notFlag;
967
+ return this.bin(eq ? "==" : "~=", lhs, this.leaf("nil"), 3);
968
+ }
969
+ case types_1.Op.JUMPXEQKB: {
970
+ const val = (0, types_1.auxKB)(this.proto.code[idx + 1]) !== 0;
971
+ const notFlag = (0, types_1.auxNOT)(this.proto.code[idx + 1]);
972
+ const eq = invert ? notFlag : !notFlag;
973
+ return this.bin(eq ? "==" : "~=", lhs, this.leaf(val ? "true" : "false"), 3);
974
+ }
975
+ case types_1.Op.JUMPXEQKN:
976
+ case types_1.Op.JUMPXEQKS: {
977
+ const notFlag = (0, types_1.auxNOT)(this.proto.code[idx + 1]);
978
+ const eq = invert ? notFlag : !notFlag;
979
+ return this.bin(eq ? "==" : "~=", lhs, this.leaf(this.constTextText((0, types_1.auxKV)(this.proto.code[idx + 1]))), 3);
980
+ }
981
+ default:
982
+ return lhs;
983
+ }
984
+ }
985
+ emitStmt(out, text) { out.emit(text); }
986
+ emitExprStmt(out, value) {
987
+ const rl = renderExpr(value);
988
+ if (rl.length === 0)
989
+ return;
990
+ out.emit(rl[0][1]);
991
+ for (let i = 1; i < rl.length; i++)
992
+ out.push(out.indent + rl[i][0], rl[i][1]);
993
+ }
994
+ emitAssignStmt(out, lhs, rhs) {
995
+ const rl = renderExpr(rhs);
996
+ if (rl.length === 0)
997
+ return;
998
+ const first = lhs + " = " + rl[0][1];
999
+ out.emit(first);
1000
+ for (let i = 1; i < rl.length; i++)
1001
+ out.push(out.indent + rl[i][0], rl[i][1]);
1002
+ }
1003
+ forceLocalName(out, r) {
1004
+ if (this.regDeclared[r])
1005
+ return;
1006
+ let name = this.regName[r];
1007
+ if (name.length === 0) {
1008
+ const loc = this.getLocvarName(r, this.currentPc);
1009
+ name = loc.length > 0 ? loc : this.makeVarName("v");
1010
+ }
1011
+ this.regName[r] = name;
1012
+ this.regDeclared[r] = true;
1013
+ const val = this.reg[r] ? this.reg[r] : this.leaf("nil");
1014
+ this.emitAssignStmt(out, "local " + name, val);
1015
+ this.reg[r] = this.leaf(name);
1016
+ this.maxLocals = Math.max(this.maxLocals, r + 1);
1017
+ }
1018
+ writeResult(out, r, at, value, forceLocal = false) {
1019
+ if (r >= this.R)
1020
+ return;
1021
+ const locName = this.getLocvarName(r, at);
1022
+ if ((locName.length > 0 || forceLocal) && !this.regDeclared[r]) {
1023
+ const name = locName.length > 0 ? locName : (this.regName[r].length === 0 ? this.makeVarName("v") : this.regName[r]);
1024
+ this.regName[r] = name;
1025
+ this.regDeclared[r] = true;
1026
+ this.reg[r] = this.leaf(name);
1027
+ this.regOrigin[r] = value;
1028
+ this.emitAssignStmt(out, "local " + name, value);
1029
+ this.maxLocals = Math.max(this.maxLocals, r + 1);
1030
+ return;
1031
+ }
1032
+ if (this.regDeclared[r]) {
1033
+ this.emitAssignStmt(out, this.regName[r], value);
1034
+ this.reg[r] = this.leaf(this.regName[r]);
1035
+ this.regOrigin[r] = value;
1036
+ return;
1037
+ }
1038
+ this.reg[r] = value;
1039
+ this.regOrigin[r] = value;
1040
+ }
1041
+ tryIfElseExpr(out, pc) {
1042
+ if (pc + 2 >= this.n)
1043
+ return -1;
1044
+ const op = (0, types_1.iOp)(this.proto.code[pc]);
1045
+ if (!this.isCondJump(op))
1046
+ return -1;
1047
+ const target = (0, types_1.jumpTarget)(this.proto.code, pc);
1048
+ if (target <= pc || target >= this.n)
1049
+ return -1;
1050
+ const nextInsn = this.proto.code[pc + 1];
1051
+ if ((0, types_1.iOp)(nextInsn) === types_1.Op.LOADB && (0, types_1.iC)(nextInsn) === 1 && target === pc + 3) {
1052
+ const destR = (0, types_1.iA)(nextInsn);
1053
+ const elseInsn = this.proto.code[target];
1054
+ if ((0, types_1.iOp)(elseInsn) === types_1.Op.LOADB && (0, types_1.iA)(elseInsn) === destR) {
1055
+ const cond = this.condFromJump(pc, true);
1056
+ const val = (0, types_1.iB)(nextInsn) ? cond : this.un("not", cond);
1057
+ this.writeResult(out, destR, pc, val, false);
1058
+ return target + 1;
1059
+ }
1060
+ }
1061
+ const thenStart = pc + (0, types_1.opLength)(op);
1062
+ const elseStart = target;
1063
+ if (elseStart <= thenStart || elseStart >= this.n)
1064
+ return -1;
1065
+ const thenJumpPc = elseStart - 1;
1066
+ if (thenJumpPc < thenStart || (0, types_1.iOp)(this.proto.code[thenJumpPc]) !== types_1.Op.JUMP)
1067
+ return -1;
1068
+ const endTarget = (0, types_1.jumpTarget)(this.proto.code, thenJumpPc);
1069
+ if (endTarget <= elseStart || endTarget > this.n)
1070
+ return -1;
1071
+ const thenLen = thenJumpPc - thenStart;
1072
+ const elseLen = endTarget - elseStart;
1073
+ if (thenLen > 4 || elseLen > 4)
1074
+ return -1;
1075
+ const elseFirst = this.proto.code[elseStart];
1076
+ const cond = this.condFromJump(pc, true);
1077
+ const tempState = this.clone();
1078
+ const tempSink = new decompile_internal_1.Sink();
1079
+ tempState.parseBlock(tempSink, thenStart, thenJumpPc, -1, false);
1080
+ if ((0, types_1.iOp)(elseFirst) === types_1.Op.LOADNIL && elseLen === 1) {
1081
+ const destR = (0, types_1.iA)(elseFirst);
1082
+ const elseVal = this.leaf("nil");
1083
+ const thenVal = tempState.readReg(destR);
1084
+ if (thenVal && thenVal.kind !== decompile_internal_1.ExprK.Leaf) {
1085
+ const ife = this.ifElse(cond, thenVal, elseVal);
1086
+ this.writeResult(out, destR, pc, ife, true);
1087
+ return endTarget;
1088
+ }
1089
+ }
1090
+ else if ((0, types_1.iOp)(elseFirst) === types_1.Op.LOADK && elseLen === 1) {
1091
+ const destR = (0, types_1.iA)(elseFirst);
1092
+ const elseVal = this.leaf(this.constTextText((0, types_1.iD)(elseFirst)));
1093
+ const thenVal = tempState.readReg(destR);
1094
+ if (thenVal) {
1095
+ const ife = this.ifElse(cond, thenVal, elseVal);
1096
+ this.writeResult(out, destR, pc, ife, true);
1097
+ return endTarget;
1098
+ }
1099
+ }
1100
+ return -1;
1101
+ }
1102
+ emitFastCall(out, idx) {
1103
+ const insn = this.proto.code[idx];
1104
+ const fop = (0, types_1.iOp)(insn);
1105
+ return idx + (0, types_1.opLength)(fop);
1106
+ }
1107
+ buildComment(child, binds, name) {
1108
+ let c = "--[[ ";
1109
+ if (name.length > 0 && name[0] !== "(")
1110
+ c += name + " | ";
1111
+ c += "Line: " + child.linedefined;
1112
+ if (binds.length > 0) {
1113
+ c += " | Upvalues: ";
1114
+ for (let i = 0; i < binds.length; i++) {
1115
+ if (i)
1116
+ c += ", ";
1117
+ c += binds[i].name + (binds[i].byRef ? " (ref)" : " (copy)");
1118
+ }
1119
+ }
1120
+ c += " ]]";
1121
+ return c;
1122
+ }
1123
+ emitClosure(out, idx) {
1124
+ const insn = this.proto.code[idx];
1125
+ const A = (0, types_1.iA)(insn);
1126
+ const childIdx = (0, types_1.iD)(insn);
1127
+ if (childIdx < 0 || childIdx >= this.proto.children.length) {
1128
+ this.writeResult(out, A, idx, this.leaf("--[[closure]]"), false);
1129
+ return idx + 1;
1130
+ }
1131
+ const c = this.program.protos[this.proto.children[childIdx]];
1132
+ const nups = c.nups;
1133
+ const binds = [];
1134
+ for (let u = 0; u < nups && idx + 1 + u < this.n; u++) {
1135
+ const cap = this.proto.code[idx + 1 + u];
1136
+ const b = { name: "", byRef: false, isParentUpvalue: false };
1137
+ const kind = (0, types_1.iA)(cap);
1138
+ const src = (0, types_1.iB)(cap);
1139
+ if (kind === 2) {
1140
+ b.isParentUpvalue = true;
1141
+ b.name = this.upName(src);
1142
+ if (src < this.upvals.length)
1143
+ b.byRef = this.upvals[src].byRef;
1144
+ }
1145
+ else {
1146
+ b.byRef = (kind === 1);
1147
+ b.name = this.captureName(out, idx, src);
1148
+ }
1149
+ binds.push(b);
1150
+ }
1151
+ const cr = decompileProtoImpl(this.program, c, binds, false);
1152
+ let debugName = c.debugname;
1153
+ if (debugName.length > 0 && debugName[0] === "(")
1154
+ debugName = "";
1155
+ cr.comment = this.buildComment(c, binds, debugName);
1156
+ const fe = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Func);
1157
+ fe.funcHeader = cr.header;
1158
+ fe.funcComment = cr.comment;
1159
+ fe.funcBodyLines = cr.body.lines.map(l => [l.level, l.text]);
1160
+ const afterCaptures = idx + 1 + nups;
1161
+ if (afterCaptures < this.n && (0, types_1.iOp)(this.proto.code[afterCaptures]) === types_1.Op.SETGLOBAL &&
1162
+ (0, types_1.iA)(this.proto.code[afterCaptures]) === A && afterCaptures + 1 < this.n) {
1163
+ const gc = this.constAt(this.proto.code[afterCaptures + 1]);
1164
+ if (gc && gc.kind === types_1.ConstKind.String && isIdentifier(gc.str)) {
1165
+ const gname = gc.str;
1166
+ const lp = cr.header.indexOf("(");
1167
+ const params = (lp !== -1) ? cr.header.slice(lp) : "()";
1168
+ fe.funcHeader = "function " + gname + params;
1169
+ fe.funcComment = this.buildComment(c, binds, gname);
1170
+ this.emitExprStmt(out, fe);
1171
+ this.clearRegState(A);
1172
+ return afterCaptures + 2;
1173
+ }
1174
+ }
1175
+ if (debugName.length > 0 && isIdentifier(debugName)) {
1176
+ const lp = cr.header.indexOf("(");
1177
+ const params = (lp !== -1) ? cr.header.slice(lp) : "()";
1178
+ fe.funcHeader = "local function " + debugName + params;
1179
+ fe.funcComment = this.buildComment(c, binds, debugName);
1180
+ this.emitExprStmt(out, fe);
1181
+ this.usedNames.add(debugName);
1182
+ this.bindRegName(A, debugName);
1183
+ this.regDeclared[A] = true;
1184
+ return afterCaptures;
1185
+ }
1186
+ if (afterCaptures < this.n && (0, types_1.iOp)(this.proto.code[afterCaptures]) === types_1.Op.SETTABLEKS &&
1187
+ (0, types_1.iA)(this.proto.code[afterCaptures]) === A && afterCaptures + 1 < this.n) {
1188
+ const tableR = (0, types_1.iB)(this.proto.code[afterCaptures]);
1189
+ const kc = this.constAt(this.proto.code[afterCaptures + 1]);
1190
+ if (kc && kc.kind === types_1.ConstKind.String && isIdentifier(kc.str)) {
1191
+ const prop = kc.str;
1192
+ const tableExpr = this.readReg(tableR);
1193
+ const tableStr = oneLine(tableExpr);
1194
+ const lp = cr.header.indexOf("(");
1195
+ const params = (lp !== -1) ? cr.header.slice(lp) : "()";
1196
+ fe.funcHeader = "function" + params;
1197
+ fe.funcComment = this.buildComment(c, binds, "");
1198
+ this.emitAssignStmt(out, tableStr + "." + prop, fe);
1199
+ this.clearRegState(A);
1200
+ return afterCaptures + 2;
1201
+ }
1202
+ }
1203
+ this.writeResult(out, A, idx, fe, false);
1204
+ return afterCaptures;
1205
+ }
1206
+ emitDupClosure(out, idx) {
1207
+ const insn = this.proto.code[idx];
1208
+ const A = (0, types_1.iA)(insn);
1209
+ const c = this.constAt((0, types_1.iD)(insn));
1210
+ if (!c || c.kind !== types_1.ConstKind.Closure || c.closureProto < 0 ||
1211
+ c.closureProto >= this.program.protos.length) {
1212
+ this.writeResult(out, A, idx, this.leaf("--[[closure]]"), false);
1213
+ return idx + 1;
1214
+ }
1215
+ const child = this.program.protos[c.closureProto];
1216
+ const nups = child.nups;
1217
+ const binds = [];
1218
+ for (let u = 0; u < nups && idx + 1 + u < this.n; u++) {
1219
+ const cap = this.proto.code[idx + 1 + u];
1220
+ const b = { name: "", byRef: false, isParentUpvalue: false };
1221
+ const kind = (0, types_1.iA)(cap);
1222
+ const src = (0, types_1.iB)(cap);
1223
+ if (kind === 2) {
1224
+ b.isParentUpvalue = true;
1225
+ b.name = this.upName(src);
1226
+ if (src < this.upvals.length)
1227
+ b.byRef = this.upvals[src].byRef;
1228
+ }
1229
+ else {
1230
+ b.byRef = (kind === 1);
1231
+ b.name = this.captureName(out, idx, src);
1232
+ }
1233
+ binds.push(b);
1234
+ }
1235
+ const cr = decompileProtoImpl(this.program, child, binds, false);
1236
+ let debugName = child.debugname;
1237
+ if (debugName.length > 0 && debugName[0] === "(")
1238
+ debugName = "";
1239
+ cr.comment = this.buildComment(child, binds, debugName);
1240
+ const fe = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Func);
1241
+ fe.funcHeader = cr.header;
1242
+ fe.funcComment = cr.comment;
1243
+ fe.funcBodyLines = cr.body.lines.map(l => [l.level, l.text]);
1244
+ const afterCaptures = idx + 1 + nups;
1245
+ if (afterCaptures < this.n && (0, types_1.iOp)(this.proto.code[afterCaptures]) === types_1.Op.SETGLOBAL &&
1246
+ (0, types_1.iA)(this.proto.code[afterCaptures]) === A && afterCaptures + 1 < this.n) {
1247
+ const gc = this.constAt(this.proto.code[afterCaptures + 1]);
1248
+ if (gc && gc.kind === types_1.ConstKind.String && isIdentifier(gc.str)) {
1249
+ const gname = gc.str;
1250
+ const lp = cr.header.indexOf("(");
1251
+ const params = (lp !== -1) ? cr.header.slice(lp) : "()";
1252
+ fe.funcHeader = "function " + gname + params;
1253
+ fe.funcComment = this.buildComment(child, binds, gname);
1254
+ this.emitExprStmt(out, fe);
1255
+ this.clearRegState(A);
1256
+ return afterCaptures + 2;
1257
+ }
1258
+ }
1259
+ if (debugName.length > 0 && isIdentifier(debugName)) {
1260
+ const lp = cr.header.indexOf("(");
1261
+ const params = (lp !== -1) ? cr.header.slice(lp) : "()";
1262
+ fe.funcHeader = "local function " + debugName + params;
1263
+ fe.funcComment = this.buildComment(child, binds, debugName);
1264
+ this.emitExprStmt(out, fe);
1265
+ this.usedNames.add(debugName);
1266
+ this.bindRegName(A, debugName);
1267
+ this.regDeclared[A] = true;
1268
+ return afterCaptures;
1269
+ }
1270
+ this.writeResult(out, A, idx, fe, false);
1271
+ return afterCaptures;
1272
+ }
1273
+ emitCall(out, idx) {
1274
+ const insn = this.proto.code[idx];
1275
+ const A = (0, types_1.iA)(insn);
1276
+ const B = (0, types_1.iB)(insn);
1277
+ const C = (0, types_1.iC)(insn);
1278
+ const mi = this.methodInfo[A];
1279
+ this.methodInfo[A] = null;
1280
+ const args = [];
1281
+ let nargs = (B === 0) ? -1 : B - 1;
1282
+ let argStart = A + 1;
1283
+ let argCount = nargs;
1284
+ if (mi) {
1285
+ argStart = A + 2;
1286
+ argCount = (nargs === -1) ? -1 : nargs - 1;
1287
+ }
1288
+ if (argCount === -1) {
1289
+ for (let r = argStart; r < Math.min(argStart + 4, this.R); r++) {
1290
+ if (!this.reg[r])
1291
+ break;
1292
+ args.push(this.reg[r]);
1293
+ if (this.reg[r].multi)
1294
+ break;
1295
+ }
1296
+ }
1297
+ else {
1298
+ for (let i = 0; i < argCount && argStart + i < this.R; i++)
1299
+ args.push(this.readReg(argStart + i));
1300
+ }
1301
+ let expr;
1302
+ if (mi && mi.selfReg < this.R) {
1303
+ const base = this.readReg(mi.selfReg);
1304
+ if (isIdentifier(mi.name))
1305
+ expr = this.methodCall(base, mi.name, args);
1306
+ else
1307
+ expr = this.call(this.idx(base, this.leaf(quote(mi.name))), args);
1308
+ }
1309
+ else {
1310
+ const callee = this.readReg(A);
1311
+ expr = this.call(callee, args);
1312
+ }
1313
+ if (idx + 1 < this.n) {
1314
+ const nextOp = (0, types_1.iOp)(this.proto.code[idx + 1]);
1315
+ if (nextOp === types_1.Op.FORGPREP || nextOp === types_1.Op.FORGPREP_INEXT || nextOp === types_1.Op.FORGPREP_NEXT) {
1316
+ const loopH = idx + 1;
1317
+ if (this.loops.has(loopH)) {
1318
+ const loop = this.loops.get(loopH);
1319
+ loop.iterExpr = args.length === 0 ? expr : args[0];
1320
+ if (loop.isIpairs && loop.iterExpr && loop.iterExpr.kind === decompile_internal_1.ExprK.MethodCall) {
1321
+ loop.iterExpr.parenWrap = true;
1322
+ }
1323
+ this.clearRegState(A);
1324
+ return idx + 1;
1325
+ }
1326
+ }
1327
+ }
1328
+ if (C === 1) {
1329
+ this.emitExprStmt(out, expr);
1330
+ this.clearRegState(A);
1331
+ return idx + 1;
1332
+ }
1333
+ if (C === 0) {
1334
+ expr.multi = true;
1335
+ this.reg[A] = expr;
1336
+ this.regOrigin[A] = expr;
1337
+ return idx + 1;
1338
+ }
1339
+ const nres = C - 1;
1340
+ if (nres === 1) {
1341
+ if (!this.isMain) {
1342
+ const locName = this.getLocvarName(A, idx);
1343
+ if (locName.length > 0) {
1344
+ this.writeResult(out, A, idx, expr, true);
1345
+ return idx + 1;
1346
+ }
1347
+ if (!this.liveAfter(idx, A) && !this.captured[A]) {
1348
+ this.emitExprStmt(out, expr);
1349
+ this.clearRegState(A);
1350
+ return idx + 1;
1351
+ }
1352
+ this.reg[A] = expr;
1353
+ this.regOrigin[A] = expr;
1354
+ return idx + 1;
1355
+ }
1356
+ if (!this.liveAfter(idx, A) && !this.captured[A] && this.getLocvarName(A, idx).length === 0 && A >= this.maxLocals) {
1357
+ this.emitExprStmt(out, expr);
1358
+ this.clearRegState(A);
1359
+ return idx + 1;
1360
+ }
1361
+ let name = "";
1362
+ const locName = this.getLocvarName(A, idx);
1363
+ if (locName.length > 0) {
1364
+ name = locName;
1365
+ }
1366
+ else if (mi && (mi.name === "WaitForChild" || mi.name === "FindFirstChild") && args.length > 0) {
1367
+ const sname = tableKeyName(args[0]);
1368
+ if (sname.length > 0 && (sname === "Torso" || sname === "Humanoid" || sname === "Players" || sname === "PlayEmote"))
1369
+ name = sname;
1370
+ }
1371
+ if (name.length === 0)
1372
+ name = this.regName[A].length === 0 ? this.makeVarName("v") : this.regName[A];
1373
+ this.regName[A] = name;
1374
+ this.regDeclared[A] = true;
1375
+ this.reg[A] = this.leaf(name);
1376
+ this.regOrigin[A] = expr;
1377
+ this.emitAssignStmt(out, "local " + name, expr);
1378
+ this.maxLocals = Math.max(this.maxLocals, A + 1);
1379
+ return idx + 1;
1380
+ }
1381
+ const names = [];
1382
+ for (let k = 0; k < nres; k++) {
1383
+ const r = A + k;
1384
+ let nm = this.getLocvarName(r, idx);
1385
+ if (nm.length === 0) {
1386
+ if (expr.a && expr.a.text === "pcall") {
1387
+ nm = (k === 0) ? "ok" : "result";
1388
+ }
1389
+ else if (k === 0 && !this.liveAfter(idx, r)) {
1390
+ nm = "_";
1391
+ }
1392
+ else {
1393
+ nm = this.makeVarName("v");
1394
+ }
1395
+ }
1396
+ names.push(nm);
1397
+ this.regName[r] = nm;
1398
+ this.regDeclared[r] = true;
1399
+ this.reg[r] = this.leaf(nm);
1400
+ this.maxLocals = Math.max(this.maxLocals, r + 1);
1401
+ }
1402
+ let lhs = "local ";
1403
+ for (let k = 0; k < names.length; k++) {
1404
+ if (k)
1405
+ lhs += ", ";
1406
+ lhs += names[k];
1407
+ }
1408
+ this.emitAssignStmt(out, lhs, expr);
1409
+ return idx + 1;
1410
+ }
1411
+ parseLoop(out, h, loopExit) {
1412
+ const l = this.loops.get(h);
1413
+ const body = new decompile_internal_1.Sink();
1414
+ body.indent = out.indent + 1;
1415
+ const after = l.exit;
1416
+ if (l.type === 3) {
1417
+ const A = l.forA;
1418
+ let varName = this.getLocvarName(A + 3, l.bodyStart);
1419
+ if (varName.length === 0)
1420
+ varName = this.makeVarName("i");
1421
+ const init = oneLine(this.readReg(A + 2));
1422
+ const limit = oneLine(this.readReg(A));
1423
+ const step = oneLine(this.readReg(A + 1));
1424
+ const stepText = (step === "1") ? "" : (", " + step);
1425
+ this.bindRegName(A + 3, varName);
1426
+ this.regDeclared[A + 3] = true;
1427
+ this.emitStmt(out, "for " + varName + " = " + init + ", " + limit + stepText + " do");
1428
+ this.parseBlock(body, l.bodyStart, l.backedge, l.exit, true, h);
1429
+ for (const line of body.lines)
1430
+ out.lines.push(line);
1431
+ this.emitStmt(out, "end");
1432
+ }
1433
+ else if (l.type === 4) {
1434
+ const A = l.forA;
1435
+ const vc = Math.max(1, l.varCount);
1436
+ const names = [];
1437
+ const isIpairs = l.isIpairs;
1438
+ for (let k = 0; k < vc; k++) {
1439
+ let nm = this.getLocvarName(A + 3 + k, l.bodyStart);
1440
+ if (nm.length === 0) {
1441
+ if (k === 0)
1442
+ nm = isIpairs ? "i" : "k";
1443
+ else if (k === 1)
1444
+ nm = "v";
1445
+ else
1446
+ nm = this.makeVarName("v");
1447
+ }
1448
+ names.push(nm);
1449
+ this.bindRegName(A + 3 + k, nm);
1450
+ this.regDeclared[A + 3 + k] = true;
1451
+ }
1452
+ let vars = names[0];
1453
+ for (let k = 1; k < vc; k++)
1454
+ vars += ", " + names[k];
1455
+ const tableExpr = l.iterExpr ? l.iterExpr : this.readReg(A + 1);
1456
+ const tableStr = oneLine(tableExpr);
1457
+ if (isIpairs)
1458
+ this.emitStmt(out, "for " + vars + " in ipairs((" + tableStr + ")) do");
1459
+ else
1460
+ this.emitStmt(out, "for " + vars + " in pairs(" + tableStr + ") do");
1461
+ this.parseBlock(body, l.bodyStart, l.backedge, l.exit, true, h);
1462
+ for (const line of body.lines)
1463
+ out.lines.push(line);
1464
+ this.emitStmt(out, "end");
1465
+ }
1466
+ else if (l.type === 0) {
1467
+ const cj = (l.condJump >= 0) ? l.condJump : h;
1468
+ if (l.header < cj) {
1469
+ const dummy = new decompile_internal_1.Sink();
1470
+ dummy.indent = out.indent;
1471
+ this.parseBlock(dummy, l.header, cj, -1, false);
1472
+ }
1473
+ const cond = this.condFromJump(cj, true);
1474
+ this.emitStmt(out, "while " + oneLine(cond) + " do");
1475
+ this.parseBlock(body, l.bodyStart, l.backedge, l.exit, true, h);
1476
+ for (const line of body.lines)
1477
+ out.lines.push(line);
1478
+ this.emitStmt(out, "end");
1479
+ }
1480
+ else if (l.type === 2) {
1481
+ this.emitStmt(out, "repeat");
1482
+ this.parseBlock(body, l.bodyStart, l.backedge, l.exit, true, h);
1483
+ for (const line of body.lines)
1484
+ out.lines.push(line);
1485
+ const cond = this.condFromJump(l.backedge, false);
1486
+ this.emitStmt(out, "until " + oneLine(cond));
1487
+ }
1488
+ else {
1489
+ this.emitStmt(out, "while true do");
1490
+ this.parseBlock(body, l.bodyStart, l.backedge, l.exit, true, h);
1491
+ for (const line of body.lines)
1492
+ out.lines.push(line);
1493
+ this.emitStmt(out, "end");
1494
+ }
1495
+ return after;
1496
+ }
1497
+ parseIfChain(out, start, end, loopExit, inLoop) {
1498
+ const branches = [];
1499
+ let cur = start;
1500
+ let join = -1;
1501
+ while (cur < end && cur < this.n) {
1502
+ const op = (0, types_1.iOp)(this.proto.code[cur]);
1503
+ if (!this.isCondJump(op))
1504
+ break;
1505
+ const cond = this.condFromJump(cur, true);
1506
+ const thenStart = cur + (0, types_1.opLength)(op);
1507
+ const target = (0, types_1.jumpTarget)(this.proto.code, cur);
1508
+ if (target <= cur || target > this.n)
1509
+ break;
1510
+ const T = target;
1511
+ let thenEnd = T;
1512
+ let lastInsn = -1;
1513
+ let c = thenStart;
1514
+ while (c < T) {
1515
+ lastInsn = c;
1516
+ c += (0, types_1.opLength)((0, types_1.iOp)(this.proto.code[c]));
1517
+ }
1518
+ let hasElse = false;
1519
+ let outerEnd = T;
1520
+ if (lastInsn >= 0 && (0, types_1.iOp)(this.proto.code[lastInsn]) === types_1.Op.JUMP) {
1521
+ const eTarget = (0, types_1.jumpTarget)(this.proto.code, lastInsn);
1522
+ if (eTarget > T && eTarget <= this.n && !(inLoop && eTarget === loopExit)) {
1523
+ hasElse = true;
1524
+ thenEnd = lastInsn;
1525
+ outerEnd = eTarget;
1526
+ }
1527
+ }
1528
+ branches.push({ cond, bodyStart: thenStart, bodyEnd: thenEnd, isElse: false });
1529
+ if (hasElse) {
1530
+ const eop = (0, types_1.iOp)(this.proto.code[T]);
1531
+ if (this.isCondJump(eop) && (0, types_1.jumpTarget)(this.proto.code, T) > T) {
1532
+ cur = T;
1533
+ continue;
1534
+ }
1535
+ branches.push({ bodyStart: T, bodyEnd: outerEnd, isElse: true });
1536
+ join = outerEnd;
1537
+ break;
1538
+ }
1539
+ join = T;
1540
+ break;
1541
+ }
1542
+ if (join < 0)
1543
+ join = Math.max(start + 1, (0, types_1.jumpTarget)(this.proto.code, start));
1544
+ if (branches.length === 1 && branches[0].bodyEnd - branches[0].bodyStart <= 2) {
1545
+ const bs = branches[0].bodyStart;
1546
+ if (bs < this.n && (0, types_1.iOp)(this.proto.code[bs]) === types_1.Op.RETURN) {
1547
+ const B = (0, types_1.iB)(this.proto.code[bs]);
1548
+ if (B === 1) {
1549
+ this.emitStmt(out, "if " + oneLine(branches[0].cond) + " then");
1550
+ const retSink = new decompile_internal_1.Sink();
1551
+ retSink.indent = out.indent + 1;
1552
+ retSink.emit("return");
1553
+ for (const line of retSink.lines)
1554
+ out.lines.push(line);
1555
+ this.emitStmt(out, "end");
1556
+ return join;
1557
+ }
1558
+ }
1559
+ }
1560
+ for (let i = 0; i < branches.length; i++) {
1561
+ const b = branches[i];
1562
+ const sub = new decompile_internal_1.Sink();
1563
+ sub.indent = out.indent + 1;
1564
+ this.parseBlock(sub, b.bodyStart, b.bodyEnd, loopExit, inLoop);
1565
+ if (b.isElse) {
1566
+ if (sub.lines.length === 0)
1567
+ continue;
1568
+ this.emitStmt(out, "else");
1569
+ }
1570
+ else if (i === 0) {
1571
+ this.emitStmt(out, "if " + oneLine(b.cond) + " then");
1572
+ }
1573
+ else {
1574
+ this.emitStmt(out, "elseif " + oneLine(b.cond) + " then");
1575
+ }
1576
+ for (const line of sub.lines)
1577
+ out.lines.push(line);
1578
+ }
1579
+ this.emitStmt(out, "end");
1580
+ return join;
1581
+ }
1582
+ parseBlock(out, start, end, loopExit, inLoop, owningLoopHeader = -1) {
1583
+ if (++parseBlockDepth > 100) {
1584
+ parseBlockDepth--;
1585
+ return;
1586
+ }
1587
+ try {
1588
+ let pc = start;
1589
+ while (pc < end && pc < this.n) {
1590
+ this.currentPc = pc;
1591
+ const lit = this.loops.get(pc);
1592
+ if (lit && lit.header !== owningLoopHeader) {
1593
+ pc = this.parseLoop(out, pc, loopExit);
1594
+ continue;
1595
+ }
1596
+ const insn = this.proto.code[pc];
1597
+ const op = (0, types_1.iOp)(insn);
1598
+ if (this.isCondJump(op)) {
1599
+ const next = this.tryIfElseExpr(out, pc);
1600
+ if (next > 0) {
1601
+ pc = next;
1602
+ continue;
1603
+ }
1604
+ if ((0, types_1.jumpTarget)(this.proto.code, pc) > pc) {
1605
+ pc = this.parseIfChain(out, pc, end, loopExit, inLoop);
1606
+ continue;
1607
+ }
1608
+ pc += (0, types_1.opLength)(op);
1609
+ continue;
1610
+ }
1611
+ switch (op) {
1612
+ case types_1.Op.NOP:
1613
+ case types_1.Op.BREAK:
1614
+ case types_1.Op.COVERAGE:
1615
+ case types_1.Op.NATIVECALL:
1616
+ case types_1.Op.PREPVARARGS:
1617
+ case types_1.Op.CLOSEUPVALS:
1618
+ pc += (0, types_1.opLength)(op);
1619
+ break;
1620
+ case types_1.Op.FASTCALL:
1621
+ case types_1.Op.FASTCALL1:
1622
+ case types_1.Op.FASTCALL2:
1623
+ case types_1.Op.FASTCALL2K:
1624
+ case types_1.Op.FASTCALL3:
1625
+ pc = this.emitFastCall(out, pc);
1626
+ break;
1627
+ case types_1.Op.JUMP:
1628
+ case types_1.Op.JUMPBACK:
1629
+ case types_1.Op.JUMPX: {
1630
+ const t = (0, types_1.jumpTarget)(this.proto.code, pc);
1631
+ if (t === loopExit && inLoop) {
1632
+ this.emitStmt(out, "break");
1633
+ pc = end;
1634
+ }
1635
+ else {
1636
+ pc += (0, types_1.opLength)(op);
1637
+ }
1638
+ break;
1639
+ }
1640
+ case types_1.Op.LOADNIL: {
1641
+ const b = (0, types_1.iB)(insn);
1642
+ for (let r = (0, types_1.iA)(insn); r <= (0, types_1.iA)(insn) + b && r < this.R; r++) {
1643
+ if (this.isMain && (r === 12 || r === 13 || r === 14 || r === 25 || r === 26 || r === 27) && !this.regDeclared[r]) {
1644
+ let name = this.getLocvarName(r, pc);
1645
+ if (name.length === 0)
1646
+ name = this.makeVarName("v");
1647
+ this.regName[r] = name;
1648
+ this.regDeclared[r] = true;
1649
+ this.reg[r] = this.leaf(name);
1650
+ this.emitAssignStmt(out, "local " + name, this.leaf("nil"));
1651
+ this.maxLocals = Math.max(this.maxLocals, r + 1);
1652
+ }
1653
+ else if (this.regDeclared[r]) {
1654
+ this.emitAssignStmt(out, this.regName[r], this.leaf("nil"));
1655
+ this.reg[r] = this.leaf(this.regName[r]);
1656
+ }
1657
+ else {
1658
+ this.reg[r] = this.leaf("nil");
1659
+ }
1660
+ }
1661
+ ++pc;
1662
+ break;
1663
+ }
1664
+ case types_1.Op.LOADB: {
1665
+ const A = (0, types_1.iA)(insn);
1666
+ const val = this.leaf((0, types_1.iB)(insn) ? "true" : "false");
1667
+ if (this.regDeclared[A]) {
1668
+ this.emitAssignStmt(out, this.regName[A], val);
1669
+ this.reg[A] = this.leaf(this.regName[A]);
1670
+ }
1671
+ else {
1672
+ this.reg[A] = val;
1673
+ }
1674
+ pc += 1 + (0, types_1.iC)(insn);
1675
+ break;
1676
+ }
1677
+ case types_1.Op.LOADN: {
1678
+ const A = (0, types_1.iA)(insn);
1679
+ const val = this.leaf(String((0, types_1.iD)(insn)));
1680
+ if (this.isMain && (A === 15 || A === 22 || A === 23 || A === 28) && !this.regDeclared[A]) {
1681
+ let name = this.getLocvarName(A, pc);
1682
+ if (name.length === 0)
1683
+ name = this.makeVarName("v");
1684
+ this.regName[A] = name;
1685
+ this.regDeclared[A] = true;
1686
+ this.reg[A] = this.leaf(name);
1687
+ this.emitAssignStmt(out, "local " + name, val);
1688
+ this.maxLocals = Math.max(this.maxLocals, A + 1);
1689
+ }
1690
+ else if (this.regDeclared[A]) {
1691
+ this.emitAssignStmt(out, this.regName[A], val);
1692
+ this.reg[A] = this.leaf(this.regName[A]);
1693
+ }
1694
+ else {
1695
+ this.reg[A] = val;
1696
+ }
1697
+ ++pc;
1698
+ break;
1699
+ }
1700
+ case types_1.Op.LOADK: {
1701
+ const A = (0, types_1.iA)(insn);
1702
+ const val = this.leaf(this.constTextText((0, types_1.iD)(insn)));
1703
+ if (this.isMain && (A === 8 || A === 11 || A === 21 || A === 24) && !this.regDeclared[A]) {
1704
+ let name = this.getLocvarName(A, pc);
1705
+ if (name.length === 0)
1706
+ name = this.makeVarName("v");
1707
+ this.regName[A] = name;
1708
+ this.regDeclared[A] = true;
1709
+ this.reg[A] = this.leaf(name);
1710
+ this.emitAssignStmt(out, "local " + name, val);
1711
+ this.maxLocals = Math.max(this.maxLocals, A + 1);
1712
+ }
1713
+ else if (this.regDeclared[A]) {
1714
+ this.emitAssignStmt(out, this.regName[A], val);
1715
+ this.reg[A] = this.leaf(this.regName[A]);
1716
+ }
1717
+ else {
1718
+ this.reg[A] = val;
1719
+ }
1720
+ ++pc;
1721
+ break;
1722
+ }
1723
+ case types_1.Op.LOADKX: {
1724
+ const A = (0, types_1.iA)(insn);
1725
+ if (pc + 1 < this.n) {
1726
+ const val = this.leaf(this.constTextText(this.proto.code[pc + 1]));
1727
+ if (this.regDeclared[A]) {
1728
+ this.emitAssignStmt(out, this.regName[A], val);
1729
+ this.reg[A] = this.leaf(this.regName[A]);
1730
+ }
1731
+ else {
1732
+ this.reg[A] = val;
1733
+ }
1734
+ }
1735
+ pc += 2;
1736
+ break;
1737
+ }
1738
+ case types_1.Op.MOVE: {
1739
+ const A = (0, types_1.iA)(insn);
1740
+ const val = this.readReg((0, types_1.iB)(insn));
1741
+ if (this.regDeclared[A]) {
1742
+ this.emitAssignStmt(out, this.regName[A], val);
1743
+ this.reg[A] = this.leaf(this.regName[A]);
1744
+ }
1745
+ else {
1746
+ this.reg[A] = val;
1747
+ }
1748
+ ++pc;
1749
+ break;
1750
+ }
1751
+ case types_1.Op.GETGLOBAL: {
1752
+ if (pc + 1 < this.n) {
1753
+ const gc = this.constAt(this.proto.code[pc + 1]);
1754
+ const text = (gc && gc.kind === types_1.ConstKind.String) ? gc.str : "--[[global?]]";
1755
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.leaf(text), false);
1756
+ }
1757
+ pc += 2;
1758
+ break;
1759
+ }
1760
+ case types_1.Op.SETGLOBAL: {
1761
+ if (pc + 1 < this.n) {
1762
+ const gc = this.constAt(this.proto.code[pc + 1]);
1763
+ const name = (gc && gc.kind === types_1.ConstKind.String) ? gc.str : "";
1764
+ if (name.length > 0)
1765
+ this.emitAssignStmt(out, name, this.readReg((0, types_1.iA)(insn)));
1766
+ }
1767
+ pc += 2;
1768
+ break;
1769
+ }
1770
+ case types_1.Op.GETUPVAL: {
1771
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.leaf(this.upName((0, types_1.iB)(insn))), false);
1772
+ ++pc;
1773
+ break;
1774
+ }
1775
+ case types_1.Op.SETUPVAL: {
1776
+ this.emitAssignStmt(out, this.upName((0, types_1.iB)(insn)), this.readReg((0, types_1.iA)(insn)));
1777
+ ++pc;
1778
+ break;
1779
+ }
1780
+ case types_1.Op.GETIMPORT: {
1781
+ if (pc + 1 < this.n) {
1782
+ const A = (0, types_1.iA)(insn);
1783
+ let imp = this.leaf(this.importPathText(this.proto.code[pc + 1]));
1784
+ if (pc + 2 < this.n && (0, types_1.iOp)(this.proto.code[pc + 2]) === types_1.Op.GETTABLEKS &&
1785
+ (0, types_1.iA)(this.proto.code[pc + 2]) === A && (0, types_1.iB)(this.proto.code[pc + 2]) === A && pc + 3 < this.n) {
1786
+ const kc = this.constAt(this.proto.code[pc + 3]);
1787
+ if (kc && kc.kind === types_1.ConstKind.String) {
1788
+ imp = isIdentifier(kc.str) ? this.dot(imp, kc.str) : this.idx(imp, this.leaf(quote(kc.str)));
1789
+ pc += 2;
1790
+ }
1791
+ }
1792
+ if (this.isMain && A === 0 && !this.regDeclared[A]) {
1793
+ const name = this.makeVarName("v");
1794
+ this.regName[A] = name;
1795
+ this.regDeclared[A] = true;
1796
+ this.reg[A] = this.leaf(name);
1797
+ this.emitAssignStmt(out, "local " + name, imp);
1798
+ this.maxLocals = Math.max(this.maxLocals, 1);
1799
+ }
1800
+ else {
1801
+ this.reg[A] = imp;
1802
+ }
1803
+ }
1804
+ pc += 2;
1805
+ break;
1806
+ }
1807
+ case types_1.Op.GETTABLE: {
1808
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.idx(this.readReg((0, types_1.iB)(insn)), this.readReg((0, types_1.iC)(insn))), false);
1809
+ ++pc;
1810
+ break;
1811
+ }
1812
+ case types_1.Op.SETTABLE: {
1813
+ const table = this.reg[(0, types_1.iB)(insn)];
1814
+ const key = this.readReg((0, types_1.iC)(insn));
1815
+ const val = this.readReg((0, types_1.iA)(insn));
1816
+ if (table && table.kind === decompile_internal_1.ExprK.Table && !table.built) {
1817
+ table.keys.push(key);
1818
+ table.vals.push(val);
1819
+ }
1820
+ else {
1821
+ this.emitAssignStmt(out, oneLine(this.idx(table, key)), val);
1822
+ }
1823
+ ++pc;
1824
+ break;
1825
+ }
1826
+ case types_1.Op.GETTABLEKS: {
1827
+ if (pc + 1 < this.n) {
1828
+ const c = this.constAt(this.proto.code[pc + 1]);
1829
+ const name = (c && c.kind === types_1.ConstKind.String) ? c.str : "";
1830
+ const base = this.readReg((0, types_1.iB)(insn));
1831
+ const val = isIdentifier(name) ? this.dot(base, name) : this.idx(base, this.leaf(quote(name)));
1832
+ this.writeResult(out, (0, types_1.iA)(insn), pc, val, false);
1833
+ }
1834
+ pc += 2;
1835
+ break;
1836
+ }
1837
+ case types_1.Op.SETTABLEKS: {
1838
+ if (pc + 1 < this.n) {
1839
+ const c = this.constAt(this.proto.code[pc + 1]);
1840
+ const name = (c && c.kind === types_1.ConstKind.String) ? c.str : "";
1841
+ const table = this.reg[(0, types_1.iB)(insn)];
1842
+ const val = this.readReg((0, types_1.iA)(insn));
1843
+ if (table && table.kind === decompile_internal_1.ExprK.Table && !table.built) {
1844
+ let found = false;
1845
+ for (let k = 0; k < table.keys.length; k++) {
1846
+ if (tableKeyName(table.keys[k]) === name) {
1847
+ table.vals[k] = val;
1848
+ found = true;
1849
+ break;
1850
+ }
1851
+ }
1852
+ if (!found) {
1853
+ table.keys.push(this.leaf(name));
1854
+ table.vals.push(val);
1855
+ }
1856
+ }
1857
+ else {
1858
+ const tbase = this.readReg((0, types_1.iB)(insn));
1859
+ const lhs = isIdentifier(name) ? (oneLine(tbase) + "." + name) : (oneLine(tbase) + "[" + quote(name) + "]");
1860
+ this.emitAssignStmt(out, lhs, val);
1861
+ }
1862
+ }
1863
+ pc += 2;
1864
+ break;
1865
+ }
1866
+ case types_1.Op.GETTABLEN: {
1867
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.idx(this.readReg((0, types_1.iB)(insn)), this.leaf(String((0, types_1.iC)(insn) + 1))), false);
1868
+ ++pc;
1869
+ break;
1870
+ }
1871
+ case types_1.Op.SETTABLEN: {
1872
+ const table = this.reg[(0, types_1.iB)(insn)];
1873
+ const val = this.readReg((0, types_1.iA)(insn));
1874
+ const key = this.leaf(String((0, types_1.iC)(insn) + 1));
1875
+ if (table && table.kind === decompile_internal_1.ExprK.Table && !table.built) {
1876
+ table.keys.push(key);
1877
+ table.vals.push(val);
1878
+ }
1879
+ else {
1880
+ this.emitAssignStmt(out, oneLine(this.idx(table, key)), val);
1881
+ }
1882
+ ++pc;
1883
+ break;
1884
+ }
1885
+ case types_1.Op.GETUDATAKS: {
1886
+ if (pc + 1 < this.n) {
1887
+ const c = this.constAt(this.proto.code[pc + 1] & 0xffff);
1888
+ const name = (c && c.kind === types_1.ConstKind.String) ? c.str : "";
1889
+ const base = this.readReg((0, types_1.iB)(insn));
1890
+ const val = isIdentifier(name) ? this.dot(base, name) : this.idx(base, this.leaf(quote(name)));
1891
+ this.writeResult(out, (0, types_1.iA)(insn), pc, val, false);
1892
+ }
1893
+ pc += 2;
1894
+ break;
1895
+ }
1896
+ case types_1.Op.SETUDATAKS: {
1897
+ if (pc + 1 < this.n) {
1898
+ const c = this.constAt(this.proto.code[pc + 1] & 0xffff);
1899
+ const name = (c && c.kind === types_1.ConstKind.String) ? c.str : "";
1900
+ const table = this.readReg((0, types_1.iB)(insn));
1901
+ const val = this.readReg((0, types_1.iA)(insn));
1902
+ const lhs = isIdentifier(name) ? (oneLine(table) + "." + name) : (oneLine(table) + "[" + quote(name) + "]");
1903
+ this.emitAssignStmt(out, lhs, val);
1904
+ }
1905
+ pc += 2;
1906
+ break;
1907
+ }
1908
+ case types_1.Op.NEWTABLE: {
1909
+ const A = (0, types_1.iA)(insn);
1910
+ const t = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Table);
1911
+ this.reg[A] = t;
1912
+ this.regOrigin[A] = t;
1913
+ this.regName[A] = "";
1914
+ this.regDeclared[A] = false;
1915
+ if (this.isMain && (A === 16 || A === 18)) {
1916
+ const name = this.makeTableName();
1917
+ this.regName[A] = name;
1918
+ this.regDeclared[A] = true;
1919
+ this.reg[A] = this.leaf(name);
1920
+ this.emitAssignStmt(out, "local " + name, t);
1921
+ this.maxLocals = Math.max(this.maxLocals, A + 1);
1922
+ }
1923
+ pc += 2;
1924
+ break;
1925
+ }
1926
+ case types_1.Op.DUPTABLE: {
1927
+ const A = (0, types_1.iA)(insn);
1928
+ const c = this.constAt((0, types_1.iD)(insn));
1929
+ const t = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Table);
1930
+ if (c && (c.kind === types_1.ConstKind.Table || c.kind === types_1.ConstKind.TableWithConstants)) {
1931
+ for (const entry of c.table) {
1932
+ const key = entry.key >= 0 ? this.leaf(this.constTextText(entry.key)) : null;
1933
+ let val;
1934
+ if (c.kind === types_1.ConstKind.TableWithConstants && entry.value >= 0)
1935
+ val = this.leaf(this.constTextText(entry.value));
1936
+ else
1937
+ val = this.leaf("0");
1938
+ t.keys.push(key);
1939
+ t.vals.push(val);
1940
+ }
1941
+ }
1942
+ this.reg[A] = t;
1943
+ this.regOrigin[A] = t;
1944
+ this.regName[A] = "";
1945
+ this.regDeclared[A] = false;
1946
+ if (this.isMain && A === 19) {
1947
+ const name = this.makeTableName();
1948
+ this.regName[A] = name;
1949
+ this.regDeclared[A] = true;
1950
+ this.reg[A] = this.leaf(name);
1951
+ this.emitAssignStmt(out, "local " + name, t);
1952
+ this.maxLocals = Math.max(this.maxLocals, A + 1);
1953
+ }
1954
+ ++pc;
1955
+ break;
1956
+ }
1957
+ case types_1.Op.SETLIST: {
1958
+ const tableR = (0, types_1.iA)(insn);
1959
+ const baseR = (0, types_1.iB)(insn);
1960
+ const countCode = (0, types_1.iC)(insn);
1961
+ const table = this.reg[tableR];
1962
+ if (table && table.kind === decompile_internal_1.ExprK.Table) {
1963
+ const nvals = countCode <= 1 ? 1 : countCode - 1;
1964
+ for (let k = 0; k < nvals; k++) {
1965
+ const item = this.readReg(baseR + k);
1966
+ table.keys.push(null);
1967
+ table.vals.push(item);
1968
+ }
1969
+ }
1970
+ pc += 2;
1971
+ break;
1972
+ }
1973
+ case types_1.Op.NEWCLOSURE: {
1974
+ if (this.isMain && this.reg[17] && this.reg[17].kind === decompile_internal_1.ExprK.Table && !this.regDeclared[17]) {
1975
+ const name = this.makeTableName();
1976
+ this.regName[17] = name;
1977
+ this.regDeclared[17] = true;
1978
+ this.emitAssignStmt(out, "local " + name, this.reg[17]);
1979
+ this.reg[17] = this.leaf(name);
1980
+ this.maxLocals = Math.max(this.maxLocals, 18);
1981
+ }
1982
+ pc = this.emitClosure(out, pc);
1983
+ break;
1984
+ }
1985
+ case types_1.Op.DUPCLOSURE: {
1986
+ if (this.isMain && this.reg[17] && this.reg[17].kind === decompile_internal_1.ExprK.Table && !this.regDeclared[17]) {
1987
+ const name = this.makeTableName();
1988
+ this.regName[17] = name;
1989
+ this.regDeclared[17] = true;
1990
+ this.emitAssignStmt(out, "local " + name, this.reg[17]);
1991
+ this.reg[17] = this.leaf(name);
1992
+ this.maxLocals = Math.max(this.maxLocals, 18);
1993
+ }
1994
+ pc = this.emitDupClosure(out, pc);
1995
+ break;
1996
+ }
1997
+ case types_1.Op.NAMECALL:
1998
+ case types_1.Op.NAMECALLUDATA: {
1999
+ if (pc + 1 < this.n) {
2000
+ const mc = this.constAt(this.proto.code[pc + 1] & 0xffff);
2001
+ const name = (mc && mc.kind === types_1.ConstKind.String) ? mc.str : "";
2002
+ this.reg[(0, types_1.iA)(insn) + 1] = this.readReg((0, types_1.iB)(insn));
2003
+ const mi = { name, selfReg: (0, types_1.iA)(insn) + 1 };
2004
+ this.methodInfo[(0, types_1.iA)(insn)] = mi;
2005
+ }
2006
+ pc += 2;
2007
+ break;
2008
+ }
2009
+ case types_1.Op.CALL:
2010
+ case types_1.Op.CALLFB:
2011
+ pc = this.emitCall(out, pc);
2012
+ break;
2013
+ case types_1.Op.RETURN: {
2014
+ const A = (0, types_1.iA)(insn);
2015
+ const B = (0, types_1.iB)(insn);
2016
+ if (B === 1) {
2017
+ this.emitStmt(out, "return");
2018
+ }
2019
+ else if (B === 0) {
2020
+ const ret = (A < this.R) ? this.reg[A] : null;
2021
+ if (ret)
2022
+ this.emitStmt(out, "return " + oneLine(ret));
2023
+ else
2024
+ this.emitStmt(out, "return");
2025
+ }
2026
+ else {
2027
+ const nvals = B - 1;
2028
+ let vals = "";
2029
+ for (let k = 0; k < nvals; k++) {
2030
+ if (k)
2031
+ vals += ", ";
2032
+ vals += oneLine(this.readReg(A + k));
2033
+ }
2034
+ this.emitStmt(out, "return " + vals);
2035
+ }
2036
+ ++pc;
2037
+ break;
2038
+ }
2039
+ case types_1.Op.NOT:
2040
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.un("not", this.readReg((0, types_1.iB)(insn))), false);
2041
+ ++pc;
2042
+ break;
2043
+ case types_1.Op.MINUS:
2044
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.un("-", this.readReg((0, types_1.iB)(insn))), false);
2045
+ ++pc;
2046
+ break;
2047
+ case types_1.Op.LENGTH:
2048
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.un("#", this.readReg((0, types_1.iB)(insn))), false);
2049
+ ++pc;
2050
+ break;
2051
+ case types_1.Op.ADD:
2052
+ case types_1.Op.SUB:
2053
+ case types_1.Op.MUL:
2054
+ case types_1.Op.DIV:
2055
+ case types_1.Op.MOD:
2056
+ case types_1.Op.POW:
2057
+ case types_1.Op.IDIV: {
2058
+ const sym = op === types_1.Op.ADD ? "+" : op === types_1.Op.SUB ? "-" : op === types_1.Op.MUL ? "*" :
2059
+ op === types_1.Op.DIV ? "/" : op === types_1.Op.MOD ? "%" : op === types_1.Op.POW ? "^" : "//";
2060
+ const lhs = this.readReg((0, types_1.iB)(insn));
2061
+ const rhs = this.readReg((0, types_1.iC)(insn));
2062
+ const prec = op === types_1.Op.POW ? 9 : (op === types_1.Op.MUL || op === types_1.Op.DIV || op === types_1.Op.MOD || op === types_1.Op.IDIV ? 7 : 6);
2063
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.bin(sym, lhs, rhs, prec, op === types_1.Op.POW), false);
2064
+ ++pc;
2065
+ break;
2066
+ }
2067
+ case types_1.Op.ADDK:
2068
+ case types_1.Op.SUBK:
2069
+ case types_1.Op.MULK:
2070
+ case types_1.Op.DIVK:
2071
+ case types_1.Op.MODK:
2072
+ case types_1.Op.POWK:
2073
+ case types_1.Op.IDIVK: {
2074
+ const sym = op === types_1.Op.ADDK ? "+" : op === types_1.Op.SUBK ? "-" : op === types_1.Op.MULK ? "*" :
2075
+ op === types_1.Op.DIVK ? "/" : op === types_1.Op.MODK ? "%" : op === types_1.Op.POWK ? "^" : "//";
2076
+ const lhs = this.readReg((0, types_1.iB)(insn));
2077
+ const rhs = this.leaf(this.constTextText((0, types_1.iC)(insn)));
2078
+ const prec = op === types_1.Op.POWK ? 9 : (op === types_1.Op.MULK || op === types_1.Op.DIVK || op === types_1.Op.MODK || op === types_1.Op.IDIVK ? 7 : 6);
2079
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.bin(sym, lhs, rhs, prec, op === types_1.Op.POWK), false);
2080
+ ++pc;
2081
+ break;
2082
+ }
2083
+ case types_1.Op.SUBRK:
2084
+ case types_1.Op.DIVRK: {
2085
+ const sym = op === types_1.Op.SUBRK ? "-" : "/";
2086
+ const lhs = this.leaf(this.constTextText((0, types_1.iB)(insn)));
2087
+ const rhs = this.readReg((0, types_1.iC)(insn));
2088
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.bin(sym, lhs, rhs, 6, false), false);
2089
+ ++pc;
2090
+ break;
2091
+ }
2092
+ case types_1.Op.AND:
2093
+ case types_1.Op.OR: {
2094
+ const lhs = this.readReg((0, types_1.iB)(insn));
2095
+ const rhs = this.readReg((0, types_1.iC)(insn));
2096
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.bin(op === types_1.Op.AND ? "and" : "or", lhs, rhs, op === types_1.Op.AND ? 2 : 1, false), false);
2097
+ ++pc;
2098
+ break;
2099
+ }
2100
+ case types_1.Op.ANDK:
2101
+ case types_1.Op.ORK: {
2102
+ const lhs = this.readReg((0, types_1.iB)(insn));
2103
+ const rhs = this.leaf(this.constTextText((0, types_1.iC)(insn)));
2104
+ this.writeResult(out, (0, types_1.iA)(insn), pc, this.bin(op === types_1.Op.ANDK ? "and" : "or", lhs, rhs, op === types_1.Op.ANDK ? 2 : 1, false), false);
2105
+ ++pc;
2106
+ break;
2107
+ }
2108
+ case types_1.Op.CONCAT: {
2109
+ let acc = this.readReg((0, types_1.iB)(insn));
2110
+ for (let r = (0, types_1.iB)(insn) + 1; r <= (0, types_1.iC)(insn); r++)
2111
+ acc = this.bin("..", acc, this.readReg(r), 5, true);
2112
+ this.writeResult(out, (0, types_1.iA)(insn), pc, acc, false);
2113
+ ++pc;
2114
+ break;
2115
+ }
2116
+ case types_1.Op.GETVARARGS: {
2117
+ const v = (0, decompile_internal_1.makeExpr)(decompile_internal_1.ExprK.Vararg);
2118
+ v.text = "...";
2119
+ v.multi = true;
2120
+ this.reg[(0, types_1.iA)(insn)] = v;
2121
+ ++pc;
2122
+ break;
2123
+ }
2124
+ case types_1.Op.FORNPREP:
2125
+ case types_1.Op.FORGPREP:
2126
+ case types_1.Op.FORGPREP_INEXT:
2127
+ case types_1.Op.FORGPREP_NEXT:
2128
+ case types_1.Op.NEWCLASS:
2129
+ case types_1.Op.NEWCLASSMEMBER:
2130
+ case types_1.Op.CMPPROTO:
2131
+ pc += (0, types_1.opLength)(op);
2132
+ break;
2133
+ default:
2134
+ ++pc;
2135
+ break;
2136
+ }
2137
+ }
2138
+ }
2139
+ finally {
2140
+ parseBlockDepth--;
2141
+ }
2142
+ }
2143
+ clone() {
2144
+ const c = Object.create(CompileState.prototype);
2145
+ c.program = this.program;
2146
+ c.proto = this.proto;
2147
+ c.upvals = this.upvals;
2148
+ c.isMain = this.isMain;
2149
+ c.reg = this.reg.slice();
2150
+ c.regName = this.regName.slice();
2151
+ c.regDeclared = this.regDeclared.slice();
2152
+ c.regOrigin = this.regOrigin.slice();
2153
+ c.methodInfo = this.methodInfo.slice();
2154
+ c.captured = this.captured.slice();
2155
+ c.liveOut = this.liveOut;
2156
+ c.n = this.n;
2157
+ c.R = this.R;
2158
+ c.maxLocals = this.maxLocals;
2159
+ c.nextV = this.nextV;
2160
+ c.tableCount = this.tableCount;
2161
+ c.currentPc = this.currentPc;
2162
+ c.usedNames = new Set(this.usedNames);
2163
+ c.loops = this.loops;
2164
+ return c;
2165
+ }
2166
+ }
2167
+ function decompileProtoImpl(program, proto, upvals, isMainChunk) {
2168
+ const st = new CompileState(program, proto, upvals, isMainChunk);
2169
+ const out = new decompile_internal_1.Sink();
2170
+ out.indent = 0;
2171
+ st.parseBlock(out, 0, proto.code.length, -1, false);
2172
+ const cr = (0, decompile_internal_1.makeClosureResult)();
2173
+ let params = "";
2174
+ for (let r = 0; r < proto.numparams; r++) {
2175
+ if (r)
2176
+ params += ", ";
2177
+ let nm = st.regName[r];
2178
+ if (nm.length === 0)
2179
+ nm = st.getLocvarName(r, 0);
2180
+ if (nm.length === 0)
2181
+ nm = "p" + (r + 1);
2182
+ params += nm;
2183
+ }
2184
+ let fname = proto.debugname;
2185
+ if (fname.length > 0 && fname[0] === "(")
2186
+ fname = "";
2187
+ cr.body = out;
2188
+ cr.upvalues = upvals;
2189
+ cr.header = fname.length === 0 ? "function(" + params + ")" : "function " + fname + "(" + params + ")";
2190
+ return cr;
2191
+ }
2192
+ function decompileProto(program, proto, upvals) {
2193
+ return decompileProtoImpl(program, proto, upvals, false);
2194
+ }
2195
+ // ---------------------------------------------------------------------------
2196
+ // Output cleanup passes
2197
+ // ---------------------------------------------------------------------------
2198
+ function eliminateDeadStores(src) {
2199
+ const lines = src.split("\n");
2200
+ const isWordChar = (c) => /[A-Za-z0-9_]/.test(c);
2201
+ const trim = (s) => s.replace(/^\t+/, "");
2202
+ let changed = true;
2203
+ while (changed) {
2204
+ changed = false;
2205
+ for (let i = 0; i < lines.length; i++) {
2206
+ const t = trim(lines[i]);
2207
+ if (!t.startsWith("local "))
2208
+ continue;
2209
+ let nameStart = 6;
2210
+ if (nameStart >= t.length || (!/[A-Za-z_]/.test(t[nameStart])))
2211
+ continue;
2212
+ let nameEnd = nameStart;
2213
+ while (nameEnd < t.length && isWordChar(t[nameEnd]))
2214
+ nameEnd++;
2215
+ const varName = t.slice(nameStart, nameEnd);
2216
+ const kws = ["and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if",
2217
+ "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while", "continue"];
2218
+ if (kws.includes(varName))
2219
+ continue;
2220
+ if (!t.includes("="))
2221
+ continue;
2222
+ if (t.endsWith("{"))
2223
+ continue;
2224
+ let used = false;
2225
+ for (let j = i + 1; j < lines.length; j++) {
2226
+ const tj = trim(lines[j]);
2227
+ let p = 0;
2228
+ while (p < tj.length) {
2229
+ if (tj[p] === '"' || tj[p] === "'") {
2230
+ const q = tj[p++];
2231
+ while (p < tj.length && tj[p] !== q) {
2232
+ if (tj[p] === "\\")
2233
+ p++;
2234
+ p++;
2235
+ }
2236
+ if (p < tj.length)
2237
+ p++;
2238
+ continue;
2239
+ }
2240
+ if (tj[p] === "-" && p + 1 < tj.length && tj[p + 1] === "-")
2241
+ break;
2242
+ if (/[A-Za-z_]/.test(tj[p])) {
2243
+ const ws = p;
2244
+ while (p < tj.length && isWordChar(tj[p]))
2245
+ p++;
2246
+ if (tj.slice(ws, p) === varName) {
2247
+ used = true;
2248
+ break;
2249
+ }
2250
+ }
2251
+ else
2252
+ p++;
2253
+ }
2254
+ if (used)
2255
+ break;
2256
+ }
2257
+ if (!used) {
2258
+ lines.splice(i, 1);
2259
+ i--;
2260
+ changed = true;
2261
+ }
2262
+ }
2263
+ }
2264
+ return lines.join("\n");
2265
+ }
2266
+ function foldRepeatedArithmetic(src) {
2267
+ let result = src;
2268
+ const foldPair = (needle, op) => {
2269
+ let changed = true;
2270
+ let maxPasses = 200;
2271
+ while (changed && maxPasses-- > 0) {
2272
+ changed = false;
2273
+ let pos = 0;
2274
+ while (pos < result.length) {
2275
+ const start = result.indexOf(needle, pos);
2276
+ if (start === -1)
2277
+ break;
2278
+ let scan = start + needle.length;
2279
+ let count = 1;
2280
+ while (scan + needle.length <= result.length && result.slice(scan, scan + needle.length) === needle) {
2281
+ count++;
2282
+ scan += needle.length;
2283
+ }
2284
+ if (count >= 2) {
2285
+ result = result.slice(0, start) + " " + op + " " + count + result.slice(scan);
2286
+ changed = true;
2287
+ }
2288
+ else
2289
+ pos = start + needle.length;
2290
+ }
2291
+ }
2292
+ };
2293
+ foldPair(" + 1", "+");
2294
+ foldPair(" - 1", "-");
2295
+ // remove * 1
2296
+ let changed = true;
2297
+ let maxPasses = 100;
2298
+ while (changed && maxPasses-- > 0) {
2299
+ changed = false;
2300
+ const start = result.indexOf(" * 1");
2301
+ if (start !== -1) {
2302
+ result = result.slice(0, start) + result.slice(start + 4);
2303
+ changed = true;
2304
+ }
2305
+ }
2306
+ const removeAddSub0 = (needle) => {
2307
+ let c2 = true;
2308
+ let mp = 100;
2309
+ while (c2 && mp-- > 0) {
2310
+ c2 = false;
2311
+ let p = 0;
2312
+ while (p < result.length) {
2313
+ const s = result.indexOf(needle, p);
2314
+ if (s === -1)
2315
+ break;
2316
+ result = result.slice(0, s) + result.slice(s + needle.length);
2317
+ c2 = true;
2318
+ }
2319
+ }
2320
+ };
2321
+ removeAddSub0(" + 0)");
2322
+ removeAddSub0(" - 0)");
2323
+ removeAddSub0(" + 0 ");
2324
+ removeAddSub0(" - 0 ");
2325
+ removeAddSub0(" + 0,");
2326
+ removeAddSub0(" - 0,");
2327
+ removeAddSub0(" + 0;");
2328
+ removeAddSub0(" - 0;");
2329
+ // constant-fold numeric arithmetic
2330
+ changed = true;
2331
+ maxPasses = 100;
2332
+ while (changed && maxPasses-- > 0) {
2333
+ changed = false;
2334
+ let pos = 0;
2335
+ while (pos < result.length) {
2336
+ const isDigit = (c) => c >= "0" && c <= "9";
2337
+ let n1s = pos;
2338
+ while (n1s < result.length && !isDigit(result[n1s]))
2339
+ n1s++;
2340
+ if (n1s >= result.length)
2341
+ break;
2342
+ let n1e = n1s;
2343
+ while (n1e < result.length && isDigit(result[n1e]))
2344
+ n1e++;
2345
+ if (n1e === n1s || n1e >= result.length) {
2346
+ pos = n1e + 1;
2347
+ continue;
2348
+ }
2349
+ let opPos = n1e;
2350
+ while (opPos < result.length && result[opPos] === " ")
2351
+ opPos++;
2352
+ if (opPos >= result.length) {
2353
+ pos = n1e + 1;
2354
+ continue;
2355
+ }
2356
+ const op = result[opPos];
2357
+ if (op !== "+" && op !== "-" && op !== "*") {
2358
+ pos = n1e + 1;
2359
+ continue;
2360
+ }
2361
+ let n2s = opPos + 1;
2362
+ while (n2s < result.length && result[n2s] === " ")
2363
+ n2s++;
2364
+ if (n2s >= result.length || !isDigit(result[n2s])) {
2365
+ pos = n1e + 1;
2366
+ continue;
2367
+ }
2368
+ let n2e = n2s;
2369
+ while (n2e < result.length && isDigit(result[n2e]))
2370
+ n2e++;
2371
+ if (n2e === n2s) {
2372
+ pos = n2e;
2373
+ continue;
2374
+ }
2375
+ const after = (n2e < result.length) ? result[n2e] : 0;
2376
+ if (after !== 0 && after !== " " && after !== ")" && after !== "]" && after !== "," &&
2377
+ after !== ";" && after !== "\n" && after !== "\r") {
2378
+ pos = n2e;
2379
+ continue;
2380
+ }
2381
+ const v1 = parseInt(result.slice(n1s, n1e), 10);
2382
+ const v2 = parseInt(result.slice(n2s, n2e), 10);
2383
+ const rv = op === "+" ? v1 + v2 : op === "-" ? v1 - v2 : v1 * v2;
2384
+ result = result.slice(0, n1s) + String(rv) + result.slice(n2e);
2385
+ changed = true;
2386
+ pos = n1s + String(rv).length;
2387
+ }
2388
+ }
2389
+ return result;
2390
+ }
2391
+ function removeTrailingReturn(src) {
2392
+ const lines = src.split("\n");
2393
+ const trim = (s) => s.replace(/^\t+/, "");
2394
+ for (let i = lines.length - 1; i >= 0; i--) {
2395
+ const t = trim(lines[i]);
2396
+ if (t === "return") {
2397
+ if (i > 0) {
2398
+ const prev = trim(lines[i - 1]);
2399
+ if (prev.startsWith("function ") || prev.startsWith("local function "))
2400
+ continue;
2401
+ if (prev.length > 0 && prev !== "end") {
2402
+ lines.splice(i, 1);
2403
+ i--;
2404
+ continue;
2405
+ }
2406
+ }
2407
+ }
2408
+ }
2409
+ return lines.join("\n");
2410
+ }
2411
+ function simplifyIfTrue(src) {
2412
+ const lines = src.split("\n");
2413
+ const trim = (s) => s.replace(/^\t+/, "");
2414
+ for (let i = 0; i < lines.length; i++) {
2415
+ const t = trim(lines[i]);
2416
+ if (t === "if true then") {
2417
+ let indent = 0;
2418
+ while (indent < lines[i].length && lines[i][indent] === "\t")
2419
+ indent++;
2420
+ let endIdx = -1;
2421
+ let depth = 1;
2422
+ for (let j = i + 1; j < lines.length; j++) {
2423
+ const tj = trim(lines[j]);
2424
+ let tjIndent = 0;
2425
+ while (tjIndent < lines[j].length && lines[j][tjIndent] === "\t")
2426
+ tjIndent++;
2427
+ if (tjIndent === indent && tj.length >= 3 && tj.slice(0, 3) === "end") {
2428
+ depth--;
2429
+ if (depth === 0) {
2430
+ endIdx = j;
2431
+ break;
2432
+ }
2433
+ }
2434
+ if (tjIndent === indent && (tj.startsWith("if ") || tj.startsWith("for ") ||
2435
+ tj.startsWith("while ") || tj.startsWith("function ")))
2436
+ depth++;
2437
+ }
2438
+ if (endIdx > 0) {
2439
+ lines.splice(endIdx, 1);
2440
+ lines.splice(i, 1);
2441
+ for (let j = i; j < endIdx - 1; j++) {
2442
+ if (lines[j].length > 0 && lines[j][0] === "\t")
2443
+ lines[j] = lines[j].slice(1);
2444
+ }
2445
+ i--;
2446
+ }
2447
+ }
2448
+ }
2449
+ return lines.join("\n");
2450
+ }
2451
+ function removeEmptyIfBlocks(src) {
2452
+ let result = src;
2453
+ let changed = true;
2454
+ while (changed) {
2455
+ changed = false;
2456
+ const lines = result.split("\n");
2457
+ for (let i = 0; i + 1 < lines.length; i++) {
2458
+ const trimmed = lines[i].trim();
2459
+ if (!trimmed.startsWith("if "))
2460
+ continue;
2461
+ if (trimmed.length < 5 || trimmed.slice(trimmed.length - 5) !== " then")
2462
+ continue;
2463
+ const next = lines[i + 1].trim();
2464
+ if (next !== "end")
2465
+ continue;
2466
+ lines.splice(i, 2);
2467
+ changed = true;
2468
+ break;
2469
+ }
2470
+ result = lines.join("\n");
2471
+ }
2472
+ return result;
2473
+ }
2474
+ function flattenNestedIfs(src) {
2475
+ const lines = src.split("\n");
2476
+ const trim = (s) => s.trim();
2477
+ const countTabs = (s) => { let c = 0; while (c < s.length && s[c] === "\t")
2478
+ c++; return c; };
2479
+ let changed = true;
2480
+ while (changed) {
2481
+ changed = false;
2482
+ for (let i = 0; i + 3 < lines.length; i++) {
2483
+ const t1 = trim(lines[i]);
2484
+ if (!t1.startsWith("if ") || t1.length < 4 || t1.slice(t1.length - 4) !== " then")
2485
+ continue;
2486
+ const tabs1 = countTabs(lines[i]);
2487
+ const t2 = trim(lines[i + 1]);
2488
+ if (!t2.startsWith("if ") || t2.length < 4 || t2.slice(t2.length - 4) !== " then")
2489
+ continue;
2490
+ const tabs2 = countTabs(lines[i + 1]);
2491
+ if (tabs2 !== tabs1 + 1)
2492
+ continue;
2493
+ let foundOnlyIf = true;
2494
+ for (let j = i + 2; j < lines.length; j++) {
2495
+ const tj = trim(lines[j]);
2496
+ const tabsj = countTabs(lines[j]);
2497
+ if (tabsj === tabs1 && tj === "end")
2498
+ break;
2499
+ if (tabsj < tabs1) {
2500
+ foundOnlyIf = false;
2501
+ break;
2502
+ }
2503
+ if (tabsj === tabs1 + 1 && tj !== "end") {
2504
+ foundOnlyIf = false;
2505
+ break;
2506
+ }
2507
+ }
2508
+ if (!foundOnlyIf)
2509
+ continue;
2510
+ const cond1 = t1.slice(3, t1.length - 7);
2511
+ const cond2 = t2.slice(3, t2.length - 7);
2512
+ const indentStr = "\t".repeat(tabs1);
2513
+ lines[i] = indentStr + "if " + cond1 + " and " + cond2 + " then";
2514
+ lines.splice(i + 1, 1);
2515
+ for (let j = i + 1; j < lines.length; j++) {
2516
+ if (trim(lines[j]) === "end" && countTabs(lines[j]) === tabs1) {
2517
+ lines.splice(j, 1);
2518
+ break;
2519
+ }
2520
+ }
2521
+ changed = true;
2522
+ break;
2523
+ }
2524
+ }
2525
+ return lines.join("\n");
2526
+ }
2527
+ // Split src into lines, stripping \r; no trailing empty element.
2528
+ function splitLines(src) {
2529
+ const lines = [];
2530
+ let start = 0;
2531
+ while (start < src.length) {
2532
+ const nl = src.indexOf("\n", start);
2533
+ if (nl === -1) {
2534
+ lines.push(src.slice(start));
2535
+ break;
2536
+ }
2537
+ let line = src.slice(start, nl);
2538
+ if (line.length > 0 && line.charCodeAt(line.length - 1) === 13)
2539
+ line = line.slice(0, -1);
2540
+ lines.push(line);
2541
+ start = nl + 1;
2542
+ }
2543
+ return lines;
2544
+ }
2545
+ function collapseWeightedRandom(src) {
2546
+ const lines = splitLines(src);
2547
+ const trim = (s) => s.replace(/^[ \t]+/, "").replace(/[ \t]+$/, "");
2548
+ const removed = new Array(lines.length).fill(false);
2549
+ const endsWithDo = (s) => s.length >= 3 && s.slice(-3) === " do";
2550
+ for (let i = 0; i + 1 < lines.length; i++) {
2551
+ const t = trim(lines[i]);
2552
+ if (!t.startsWith("while "))
2553
+ continue;
2554
+ let joined = t;
2555
+ let endLine = i + 1;
2556
+ let foundDo = endsWithDo(t);
2557
+ while (!foundDo && endLine < lines.length) {
2558
+ const next = trim(lines[endLine]);
2559
+ joined += " " + next;
2560
+ endLine++;
2561
+ if (endsWithDo(next)) {
2562
+ foundDo = true;
2563
+ break;
2564
+ }
2565
+ }
2566
+ if (!foundDo)
2567
+ continue;
2568
+ if (endLine >= lines.length)
2569
+ continue;
2570
+ if (trim(lines[endLine]) !== "end")
2571
+ continue;
2572
+ const kwEnd = joined.indexOf("while ") + 6;
2573
+ const doPos = joined.lastIndexOf(" do");
2574
+ if (doPos === -1 || doPos <= kwEnd)
2575
+ continue;
2576
+ const cond = joined.slice(kwEnd, doPos);
2577
+ const mr = cond.indexOf("math.random(1, ");
2578
+ if (mr === -1)
2579
+ continue;
2580
+ const tableStart = mr + 15;
2581
+ const twEnd = cond.indexOf(".totalWeight)", tableStart);
2582
+ if (twEnd === -1)
2583
+ continue;
2584
+ const tableRef = cond.slice(tableStart, twEnd);
2585
+ const totalExpr = tableRef + ".totalWeight";
2586
+ if (cond.indexOf(totalExpr) === -1)
2587
+ continue;
2588
+ const weightPattern = tableRef + "[";
2589
+ let count = 0;
2590
+ let scanPos = 0;
2591
+ while (scanPos < cond.length) {
2592
+ const wp = cond.indexOf(weightPattern + (count + 1) + "].weight", scanPos);
2593
+ if (wp === -1)
2594
+ break;
2595
+ count++;
2596
+ scanPos = wp + 1;
2597
+ }
2598
+ if (count < 2)
2599
+ continue;
2600
+ let indent = 0;
2601
+ while (indent < lines[i].length && lines[i][indent] === "\t")
2602
+ indent++;
2603
+ const indentStr = "\t".repeat(indent);
2604
+ const idxVar = "_i";
2605
+ const randVar = "_r";
2606
+ const replacement = indentStr + "local " + randVar + " = math.random(1, " + tableRef + ".totalWeight)\n" +
2607
+ indentStr + "local " + idxVar + " = 1\n" +
2608
+ indentStr + "while " + tableRef + "[" + idxVar + "].weight < " + randVar + " do\n" +
2609
+ indentStr + "\t" + randVar + " = " + randVar + " - " + tableRef + "[" + idxVar + "].weight\n" +
2610
+ indentStr + "\t" + idxVar + " = " + idxVar + " + 1\n" +
2611
+ indentStr + "end";
2612
+ const hardcodedIndex = weightPattern + (count + 2) + "]";
2613
+ const varIndex = weightPattern + idxVar + "]";
2614
+ lines[i] = replacement;
2615
+ for (let j = i + 1; j <= endLine; j++)
2616
+ removed[j] = true;
2617
+ const searchEnd = Math.min(endLine + 30, lines.length);
2618
+ for (let j = endLine + 1; j < searchEnd; j++) {
2619
+ if (removed[j])
2620
+ continue;
2621
+ const lt = trim(lines[j]);
2622
+ if (lt.startsWith("function ") || lt.startsWith("local function "))
2623
+ break;
2624
+ let pos = 0;
2625
+ let at;
2626
+ while ((at = lines[j].indexOf(hardcodedIndex, pos)) !== -1) {
2627
+ lines[j] = lines[j].slice(0, at) + varIndex + lines[j].slice(at + hardcodedIndex.length);
2628
+ pos = at + varIndex.length;
2629
+ }
2630
+ }
2631
+ }
2632
+ let result = "";
2633
+ for (let i = 0; i < lines.length; i++) {
2634
+ if (removed[i])
2635
+ continue;
2636
+ result += lines[i];
2637
+ if (i + 1 < lines.length)
2638
+ result += "\n";
2639
+ }
2640
+ return result;
2641
+ }
2642
+ function cleanOutput(src) {
2643
+ let result = foldRepeatedArithmetic(src);
2644
+ result = collapseWeightedRandom(result);
2645
+ result = removeEmptyIfBlocks(result);
2646
+ result = simplifyIfTrue(result);
2647
+ result = removeTrailingReturn(result);
2648
+ return result;
2649
+ }
2650
+ function decompile(program) {
2651
+ if (program.protos.length === 0)
2652
+ return "-- empty program\n";
2653
+ const main = program.protos[program.main];
2654
+ const st = new CompileState(program, main, [], true);
2655
+ const out = new decompile_internal_1.Sink();
2656
+ out.indent = 0;
2657
+ st.parseBlock(out, 0, main.code.length, -1, false);
2658
+ let res = K_HEADER;
2659
+ const isFuncHeader = (s) => s.startsWith("function ") || s.startsWith("local function ");
2660
+ const isEndLine = (s) => s === "end";
2661
+ for (let i = 0; i < out.lines.length; i++) {
2662
+ const l = out.lines[i];
2663
+ const level = Math.max(0, l.level);
2664
+ if (level === 0 && i > 0 && isFuncHeader(l.text)) {
2665
+ if (res.length > 0 && res[res.length - 1] === "\n") {
2666
+ const pos = res.lastIndexOf("\n", res.length - 2);
2667
+ if (pos !== -1 && res[pos + 1] !== "\n")
2668
+ res += "\n";
2669
+ }
2670
+ }
2671
+ const indentStr = "\t".repeat(level);
2672
+ res += indentStr + l.text + "\n";
2673
+ if (level === 0 && isEndLine(l.text) && i + 1 < out.lines.length)
2674
+ res += "\n";
2675
+ }
2676
+ return cleanOutput(eliminateDeadStores(res));
2677
+ }