luau-obfuscator 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -181,67 +181,171 @@ function visitArray(arr, visitor) {
181
181
  arr[i] = mapExpression(arr[i], visitor);
182
182
  }
183
183
  }
184
- function mapExpression(expr, visitor) {
184
+ function getExpressionChildren(expr) {
185
185
  switch (expr.type) {
186
- case "InterpolatedStringExpression":
186
+ case "InterpolatedStringExpression": {
187
+ const children = [];
187
188
  for (const part of expr.parts) {
188
189
  if (part.kind === "expression") {
189
- part.expression = mapExpression(part.expression, visitor);
190
+ children.push({
191
+ get: () => part.expression,
192
+ set: (v) => {
193
+ part.expression = v;
194
+ }
195
+ });
190
196
  }
191
197
  }
192
- break;
198
+ return children;
199
+ }
193
200
  case "FunctionExpression":
194
- walkFunctionBody(expr.func, visitor);
195
- break;
196
- case "TableExpression":
201
+ return [];
202
+ case "TableExpression": {
203
+ const children = [];
197
204
  for (const field of expr.fields) {
198
205
  if (field.type === "TableFieldPositional") {
199
- field.value = mapExpression(field.value, visitor);
206
+ children.push({ get: () => field.value, set: (v) => {
207
+ field.value = v;
208
+ } });
200
209
  } else if (field.type === "TableFieldNamed") {
201
- field.value = mapExpression(field.value, visitor);
210
+ children.push({ get: () => field.value, set: (v) => {
211
+ field.value = v;
212
+ } });
202
213
  } else if (field.type === "TableFieldComputed") {
203
- field.key = mapExpression(field.key, visitor);
204
- field.value = mapExpression(field.value, visitor);
214
+ children.push({ get: () => field.key, set: (v) => {
215
+ field.key = v;
216
+ } });
217
+ children.push({ get: () => field.value, set: (v) => {
218
+ field.value = v;
219
+ } });
205
220
  }
206
221
  }
207
- break;
222
+ return children;
223
+ }
208
224
  case "BinaryExpression":
209
- expr.left = mapExpression(expr.left, visitor);
210
- expr.right = mapExpression(expr.right, visitor);
211
- break;
225
+ return [
226
+ { get: () => expr.left, set: (v) => {
227
+ expr.left = v;
228
+ } },
229
+ { get: () => expr.right, set: (v) => {
230
+ expr.right = v;
231
+ } }
232
+ ];
212
233
  case "UnaryExpression":
213
- expr.argument = mapExpression(expr.argument, visitor);
214
- break;
234
+ return [{ get: () => expr.argument, set: (v) => {
235
+ expr.argument = v;
236
+ } }];
215
237
  case "MemberExpression":
216
- expr.object = mapExpression(expr.object, visitor);
217
- break;
238
+ return [{ get: () => expr.object, set: (v) => {
239
+ expr.object = v;
240
+ } }];
218
241
  case "IndexExpression":
219
- expr.object = mapExpression(expr.object, visitor);
220
- expr.index = mapExpression(expr.index, visitor);
221
- break;
222
- case "CallExpression":
223
- expr.callee = mapExpression(expr.callee, visitor);
224
- visitArray(expr.arguments, visitor);
225
- break;
226
- case "MethodCallExpression":
227
- expr.object = mapExpression(expr.object, visitor);
228
- visitArray(expr.arguments, visitor);
229
- break;
242
+ return [
243
+ { get: () => expr.object, set: (v) => {
244
+ expr.object = v;
245
+ } },
246
+ { get: () => expr.index, set: (v) => {
247
+ expr.index = v;
248
+ } }
249
+ ];
250
+ case "CallExpression": {
251
+ const children = [
252
+ { get: () => expr.callee, set: (v) => {
253
+ expr.callee = v;
254
+ } }
255
+ ];
256
+ for (let i = 0; i < expr.arguments.length; i++) {
257
+ const idx = i;
258
+ children.push({
259
+ get: () => expr.arguments[idx],
260
+ set: (v) => {
261
+ expr.arguments[idx] = v;
262
+ }
263
+ });
264
+ }
265
+ return children;
266
+ }
267
+ case "MethodCallExpression": {
268
+ const children = [
269
+ { get: () => expr.object, set: (v) => {
270
+ expr.object = v;
271
+ } }
272
+ ];
273
+ for (let i = 0; i < expr.arguments.length; i++) {
274
+ const idx = i;
275
+ children.push({
276
+ get: () => expr.arguments[idx],
277
+ set: (v) => {
278
+ expr.arguments[idx] = v;
279
+ }
280
+ });
281
+ }
282
+ return children;
283
+ }
230
284
  case "ParenthesizedExpression":
231
- expr.expression = mapExpression(expr.expression, visitor);
232
- break;
285
+ return [{ get: () => expr.expression, set: (v) => {
286
+ expr.expression = v;
287
+ } }];
233
288
  case "TypeAssertionExpression":
234
- expr.expression = mapExpression(expr.expression, visitor);
235
- break;
236
- case "IfElseExpression":
289
+ return [{ get: () => expr.expression, set: (v) => {
290
+ expr.expression = v;
291
+ } }];
292
+ case "IfElseExpression": {
293
+ const children = [];
237
294
  for (const clause of expr.clauses) {
238
- clause.condition = mapExpression(clause.condition, visitor);
239
- clause.body = mapExpression(clause.body, visitor);
295
+ children.push({ get: () => clause.condition, set: (v) => {
296
+ clause.condition = v;
297
+ } });
298
+ children.push({ get: () => clause.body, set: (v) => {
299
+ clause.body = v;
300
+ } });
240
301
  }
241
- expr.alternate = mapExpression(expr.alternate, visitor);
242
- break;
302
+ children.push({ get: () => expr.alternate, set: (v) => {
303
+ expr.alternate = v;
304
+ } });
305
+ return children;
306
+ }
307
+ default:
308
+ return [];
309
+ }
310
+ }
311
+ function mapExpression(root, visitor) {
312
+ if (root.type === "FunctionExpression") {
313
+ walkFunctionBody(root.func, visitor);
314
+ return visitor(root) ?? root;
315
+ }
316
+ let result = root;
317
+ const stack = [{
318
+ expr: root,
319
+ children: getExpressionChildren(root),
320
+ index: 0,
321
+ setInParent: (v) => {
322
+ result = v;
323
+ }
324
+ }];
325
+ while (stack.length > 0) {
326
+ const frame = stack[stack.length - 1];
327
+ if (frame.index < frame.children.length) {
328
+ const child = frame.children[frame.index];
329
+ frame.index++;
330
+ const childExpr = child.get();
331
+ if (childExpr.type === "FunctionExpression") {
332
+ walkFunctionBody(childExpr.func, visitor);
333
+ child.set(visitor(childExpr) ?? childExpr);
334
+ continue;
335
+ }
336
+ stack.push({
337
+ expr: childExpr,
338
+ children: getExpressionChildren(childExpr),
339
+ index: 0,
340
+ setInParent: child.set
341
+ });
342
+ continue;
343
+ }
344
+ const finished = visitor(frame.expr) ?? frame.expr;
345
+ frame.setInParent?.(finished);
346
+ stack.pop();
243
347
  }
244
- return visitor(expr) ?? expr;
348
+ return result;
245
349
  }
246
350
  function walkFunctionBody(func, visitor) {
247
351
  walkBlock(func.body, visitor);
@@ -327,6 +431,12 @@ function stringLiteral(value) {
327
431
  function numberLiteral(value) {
328
432
  return { type: "NumberLiteral", value, raw: String(value), line: POS, column: POS };
329
433
  }
434
+ var vmStructuralNumbers = /* @__PURE__ */ new WeakSet();
435
+ function vmNumberLiteral(value) {
436
+ const node = numberLiteral(value);
437
+ vmStructuralNumbers.add(node);
438
+ return node;
439
+ }
330
440
  function binary(operator, left, right) {
331
441
  return { type: "BinaryExpression", operator, left, right, line: POS, column: POS };
332
442
  }
@@ -456,14 +566,15 @@ function splitByteChunks(bytes, min, max) {
456
566
  return chunks.length > 0 ? chunks : [bytes];
457
567
  }
458
568
  function stringCharCall(bytes) {
459
- return call(member(identifier("string"), "char"), bytes.map(numberLiteral));
569
+ return call(member(identifier("string"), "char"), bytes.map(vmNumberLiteral));
460
570
  }
461
571
  function buildConcatChain(chunks) {
462
- let expr = stringCharCall(chunks[0]);
463
- for (let i = 1; i < chunks.length; i++) {
464
- expr = binary("..", expr, stringCharCall(chunks[i]));
572
+ function build(lo, hi) {
573
+ if (hi - lo === 1) return stringCharCall(chunks[lo]);
574
+ const mid = lo + Math.floor((hi - lo) / 2);
575
+ return binary("..", build(lo, mid), build(mid, hi));
465
576
  }
466
- return expr;
577
+ return build(0, chunks.length);
467
578
  }
468
579
  function shuffledIndices(n) {
469
580
  const arr = Array.from({ length: n }, (_, i) => i);
@@ -486,7 +597,7 @@ function buildShuffledTables(chunks) {
486
597
  for (let slot = 0; slot < n; slot++) {
487
598
  order[storageOrder[slot]] = slot + 1;
488
599
  }
489
- const orderFields = order.map((slot) => positionalField(numberLiteral(slot)));
600
+ const orderFields = order.map((slot) => positionalField(vmNumberLiteral(slot)));
490
601
  return {
491
602
  storeVar,
492
603
  orderVar,
@@ -505,7 +616,7 @@ function buildForLoopStringExpr(chunks) {
505
616
  localStatement(accVar, stringLiteral("")),
506
617
  numericForStatement(
507
618
  iVar,
508
- numberLiteral(1),
619
+ vmNumberLiteral(1),
509
620
  unary("#", identifier(orderVar)),
510
621
  block([
511
622
  assignmentStatement(
@@ -537,7 +648,7 @@ function buildRecursiveStringExpr(chunks) {
537
648
  ]),
538
649
  returnStatement([
539
650
  call(identifier(recName), [
540
- binary("+", identifier(iParam), numberLiteral(1)),
651
+ binary("+", identifier(iParam), vmNumberLiteral(1)),
541
652
  binary(
542
653
  "..",
543
654
  identifier(accParam),
@@ -549,18 +660,21 @@ function buildRecursiveStringExpr(chunks) {
549
660
  return iife([
550
661
  ...stmts,
551
662
  localFunctionStatement(recName, functionBody([functionParam(iParam), functionParam(accParam)], body)),
552
- returnStatement([call(identifier(recName), [numberLiteral(1), stringLiteral("")])])
663
+ returnStatement([call(identifier(recName), [vmNumberLiteral(1), stringLiteral("")])])
553
664
  ]);
554
665
  }
555
666
  function pickControlFlowStrategy() {
556
667
  return Math.random() < 0.5 ? "forLoop" : "recursive";
557
668
  }
669
+ var MAX_CHUNKS_PER_STRING = 2e4;
558
670
  function runStringsToExpressions(program, options) {
559
671
  const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT;
560
672
  transformExpressions(program, (expr) => {
561
673
  if (expr.type !== "StringLiteral") return;
562
674
  if (expr.value.length === 0) return;
563
675
  const bytes = toUtf8Bytes(expr.value);
676
+ const estimatedChunks = Math.ceil(bytes.length / Math.max(1, options.min));
677
+ if (estimatedChunks > MAX_CHUNKS_PER_STRING) return;
564
678
  const chunks = splitByteChunks(bytes, options.min, options.max);
565
679
  if (chunks.length > 1 && Math.random() < controlFlowProbability) {
566
680
  return pickControlFlowStrategy() === "forLoop" ? buildForLoopStringExpr(chunks) : buildRecursiveStringExpr(chunks);
@@ -733,6 +847,7 @@ function runNumbersToExpressions(program, options) {
733
847
  const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT2;
734
848
  transformExpressions(program, (expr) => {
735
849
  if (expr.type !== "NumberLiteral") return;
850
+ if (vmStructuralNumbers.has(expr)) return;
736
851
  if (Number.isInteger(expr.value) && Math.random() < controlFlowProbability) {
737
852
  return buildControlFlowNumberExpr(expr.value, options.min, options.max);
738
853
  }
@@ -925,7 +1040,7 @@ function runConstantArray(program, options) {
925
1040
  seen.add(k);
926
1041
  order.push({ kind: "string", value: expr.value });
927
1042
  }
928
- } else if (expr.type === "NumberLiteral") {
1043
+ } else if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
929
1044
  const k = keyOf("number", expr.value);
930
1045
  if (!seen.has(k)) {
931
1046
  seen.add(k);
@@ -945,11 +1060,11 @@ function runConstantArray(program, options) {
945
1060
  transformExpressions(program, (expr) => {
946
1061
  if (expr.type === "StringLiteral") {
947
1062
  const idx = indexOf.get(keyOf("string", expr.value));
948
- return index(identifier(arrayName), numberLiteral(idx));
1063
+ return index(identifier(arrayName), vmNumberLiteral(idx));
949
1064
  }
950
- if (expr.type === "NumberLiteral") {
1065
+ if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
951
1066
  const idx = indexOf.get(keyOf("number", expr.value));
952
- return index(identifier(arrayName), numberLiteral(idx));
1067
+ return index(identifier(arrayName), vmNumberLiteral(idx));
953
1068
  }
954
1069
  return void 0;
955
1070
  });
@@ -990,15 +1105,15 @@ function obfuscatedNumber(value) {
990
1105
  const variant = randomInt4(0, 2);
991
1106
  if (variant === 0) {
992
1107
  const a2 = randomInt4(0, value);
993
- return binary("+", numberLiteral(a2), numberLiteral(value - a2));
1108
+ return binary("+", vmNumberLiteral(a2), vmNumberLiteral(value - a2));
994
1109
  }
995
1110
  if (variant === 1) {
996
1111
  const a2 = randomInt4(0, 16777215);
997
- return binary("-", numberLiteral(value + a2), numberLiteral(a2));
1112
+ return binary("-", vmNumberLiteral(value + a2), vmNumberLiteral(a2));
998
1113
  }
999
1114
  const a = randomInt4(0, 16777215);
1000
1115
  const b = (value ^ a) >>> 0;
1001
- return call(member(identifier("bit32"), "bxor"), [numberLiteral(a), numberLiteral(b)]);
1116
+ return call(member(identifier("bit32"), "bxor"), [vmNumberLiteral(a), vmNumberLiteral(b)]);
1002
1117
  }
1003
1118
  function buildDecoderStatement(name) {
1004
1119
  const dataParam = randomName3();
@@ -1009,17 +1124,17 @@ function buildDecoderStatement(name) {
1009
1124
  const nKeysVar = randomName3();
1010
1125
  const idxExpr = binary(
1011
1126
  "+",
1012
- binary("%", binary("-", identifier(iVar), numberLiteral(1)), identifier(nKeysVar)),
1013
- numberLiteral(1)
1127
+ binary("%", binary("-", identifier(iVar), vmNumberLiteral(1)), identifier(nKeysVar)),
1128
+ vmNumberLiteral(1)
1014
1129
  );
1015
1130
  const keyByte = index(identifier(keysParam), idxExpr);
1016
1131
  const mixed = call(member(identifier("bit32"), "band"), [
1017
- call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar), numberLiteral(8))]),
1018
- numberLiteral(255)
1132
+ call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar), vmNumberLiteral(8))]),
1133
+ vmNumberLiteral(255)
1019
1134
  ]);
1020
1135
  const posByte = call(member(identifier("bit32"), "band"), [
1021
1136
  binary("*", identifier(iVar), identifier(multParam)),
1022
- numberLiteral(255)
1137
+ vmNumberLiteral(255)
1023
1138
  ]);
1024
1139
  const keystream = call(member(identifier("bit32"), "bxor"), [mixed, posByte]);
1025
1140
  const finalByte = call(member(identifier("bit32"), "bxor"), [
@@ -1031,7 +1146,7 @@ function buildDecoderStatement(name) {
1031
1146
  localStatement(outVar, table([])),
1032
1147
  numericForStatement(
1033
1148
  iVar,
1034
- numberLiteral(1),
1149
+ vmNumberLiteral(1),
1035
1150
  unary("#", identifier(dataParam)),
1036
1151
  block([
1037
1152
  assignmentStatement(
@@ -1059,7 +1174,7 @@ function runEncryptStrings(program, _options) {
1059
1174
  const mult = randomInt4(1, 255);
1060
1175
  const plainBytes = toUtf8Bytes2(expr.value);
1061
1176
  const cipherBytes = plainBytes.map((b, idx) => b ^ keystreamByte(keys, mult, idx + 1));
1062
- const dataTable = table(cipherBytes.map((b) => positionalField(numberLiteral(b))));
1177
+ const dataTable = table(cipherBytes.map((b) => positionalField(vmNumberLiteral(b))));
1063
1178
  const keysTable = table(keys.map((k) => positionalField(obfuscatedNumber(k))));
1064
1179
  const multExpr = obfuscatedNumber(mult);
1065
1180
  return call(identifier(decoderName), [dataTable, keysTable, multExpr]);
@@ -1126,6 +1241,7 @@ function runEncryptNumbers(program, _options) {
1126
1241
  let used = false;
1127
1242
  transformExpressions(program, (expr) => {
1128
1243
  if (expr.type !== "NumberLiteral") return;
1244
+ if (vmStructuralNumbers.has(expr)) return;
1129
1245
  if (!isEncryptable(expr.value)) return;
1130
1246
  used = true;
1131
1247
  const keyA = randomInt5(1, 16777215);
@@ -1376,16 +1492,18 @@ function createProto(id) {
1376
1492
  maxRegs: 0,
1377
1493
  code: [],
1378
1494
  consts: [],
1495
+ constIndex: /* @__PURE__ */ new Map(),
1379
1496
  upvalDescs: [],
1380
1497
  protos: []
1381
1498
  };
1382
1499
  }
1383
1500
  function internConst(proto, value) {
1384
- for (let i = 0; i < proto.consts.length; i++) {
1385
- if (proto.consts[i] === value) return i;
1386
- }
1501
+ const existing = proto.constIndex.get(value);
1502
+ if (existing !== void 0) return existing;
1387
1503
  proto.consts.push(value);
1388
- return proto.consts.length - 1;
1504
+ const idx = proto.consts.length - 1;
1505
+ proto.constIndex.set(value, idx);
1506
+ return idx;
1389
1507
  }
1390
1508
 
1391
1509
  // src/passes/vmify/registers.ts
@@ -1637,6 +1755,23 @@ var VmCompiler = class {
1637
1755
  captured = /* @__PURE__ */ new Set();
1638
1756
  protoIdCounter = 0;
1639
1757
  declToBinding = /* @__PURE__ */ new Map();
1758
+ /**
1759
+ * 실제로 프로그램이 참조하는 모든 전역 이름(사전 등록된 builtinGlobals +
1760
+ * 스코프 분석이 자동으로 찾아낸, 로컬/파라미터/업밸류로 안 풀리는 나머지 식별자
1761
+ * 전부)을 돌려준다. Vmify.ts가 이 목록으로 브릿지 테이블(globals)을 만든다.
1762
+ *
1763
+ * 예전엔 호출자가 넘긴 builtinGlobals 목록만 브릿지에 넣었는데, 그 목록에
1764
+ * 없는 전역(예: Roblox API나 실행기 전용 전역을 다 못 채운 경우)을 참조하면
1765
+ * 컴파일은 성공하지만 GETGLOBAL이 조용히 nil을 반환해서 "attempt to call a
1766
+ * nil value"로 터진다 — 게다가 원인이 VM 디스패치 쪽 스택에서만 보여서
1767
+ * "opcode 매핑이 깨졌나?" 하고 엉뚱한 곳을 의심하게 만든다. analyzeScopes는
1768
+ * 어차피 로컬/업밸류로 못 푸는 식별자를 전부 global 바인딩으로 잡아두므로,
1769
+ * builtinGlobals에 없던 이름도 여기 globalsByName에는 다 들어있다 —
1770
+ * 하드코딩된 허용목록 대신 이걸 그대로 쓰면 커버리지 문제 자체가 사라진다.
1771
+ */
1772
+ getUsedGlobalNames() {
1773
+ return Array.from(this.analysis.globalsByName.keys());
1774
+ }
1640
1775
  computeCaptured() {
1641
1776
  for (const binding of this.analysis.bindings.values()) {
1642
1777
  if (binding.kind === "global") continue;
@@ -2557,13 +2692,13 @@ function serializeConstValue(v) {
2557
2692
  if (typeof v === "number") return numberLiteral(v);
2558
2693
  return booleanLiteral(v);
2559
2694
  }
2560
- function serializeInstrs(proto, names) {
2695
+ function serializeInstrs(proto) {
2561
2696
  const fields = proto.code.map((instr) => positionalField(
2562
2697
  table([
2563
- namedField(names.op, numberLiteral(instr.op)),
2564
- namedField(names.a, numberLiteral(instr.a)),
2565
- namedField(names.b, numberLiteral(instr.b)),
2566
- namedField(names.c, numberLiteral(instr.c))
2698
+ positionalField(vmNumberLiteral(instr.op)),
2699
+ positionalField(vmNumberLiteral(instr.a)),
2700
+ positionalField(vmNumberLiteral(instr.b)),
2701
+ positionalField(vmNumberLiteral(instr.c))
2567
2702
  ])
2568
2703
  ));
2569
2704
  return table(fields);
@@ -2572,17 +2707,17 @@ function serializeUpvalDescs(proto, names) {
2572
2707
  const fields = proto.upvalDescs.map((d) => positionalField(
2573
2708
  table([
2574
2709
  namedField(names.kind, stringLiteral(d.kind)),
2575
- namedField(names.index, numberLiteral(d.index))
2710
+ namedField(names.index, vmNumberLiteral(d.index))
2576
2711
  ])
2577
2712
  ));
2578
2713
  return table(fields);
2579
2714
  }
2580
2715
  function serializeProto(proto, names) {
2581
2716
  const fields = [
2582
- namedField(names.numParams, numberLiteral(proto.numParams)),
2717
+ namedField(names.numParams, vmNumberLiteral(proto.numParams)),
2583
2718
  namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
2584
- namedField(names.maxRegs, numberLiteral(proto.maxRegs)),
2585
- namedField(names.code, serializeInstrs(proto, names)),
2719
+ namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
2720
+ namedField(names.code, serializeInstrs(proto)),
2586
2721
  namedField(names.consts, serializeConsts(proto.consts)),
2587
2722
  namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
2588
2723
  namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names)))))
@@ -2599,7 +2734,7 @@ local ${N.handlers} = {}
2599
2734
 
2600
2735
  local function ${N.dispatch}(${N.frame}, pc)
2601
2736
  local instr = ${N.frame}.${N.code}[pc]
2602
- return ${N.handlers}[instr.${N.op}](${N.frame}, pc, instr)
2737
+ return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
2603
2738
  end
2604
2739
 
2605
2740
  local function ${N.rk}(${N.frame}, x)
@@ -2612,156 +2747,156 @@ end
2612
2747
 
2613
2748
  -- MOVE
2614
2749
  ${N.handlers}[${op(0 /* MOVE */)}] = function(${N.frame}, pc, instr)
2615
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.R}[instr.${N.b}]
2750
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
2616
2751
  return ${N.dispatch}(${N.frame}, pc + 1)
2617
2752
  end
2618
2753
 
2619
2754
  -- LOADK
2620
2755
  ${N.handlers}[${op(1 /* LOADK */)}] = function(${N.frame}, pc, instr)
2621
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.K}[instr.${N.b} + 1]
2756
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
2622
2757
  return ${N.dispatch}(${N.frame}, pc + 1)
2623
2758
  end
2624
2759
 
2625
2760
  -- LOADBOOL
2626
2761
  ${N.handlers}[${op(2 /* LOADBOOL */)}] = function(${N.frame}, pc, instr)
2627
- ${N.frame}.${N.R}[instr.${N.a}] = (instr.${N.b} ~= 0)
2762
+ ${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
2628
2763
  return ${N.dispatch}(${N.frame}, pc + 1)
2629
2764
  end
2630
2765
 
2631
2766
  -- LOADNIL
2632
2767
  ${N.handlers}[${op(3 /* LOADNIL */)}] = function(${N.frame}, pc, instr)
2633
- for i = instr.${N.a}, instr.${N.b} do ${N.frame}.${N.R}[i] = nil end
2768
+ for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
2634
2769
  return ${N.dispatch}(${N.frame}, pc + 1)
2635
2770
  end
2636
2771
 
2637
2772
  -- GETUPVAL
2638
2773
  ${N.handlers}[${op(4 /* GETUPVAL */)}] = function(${N.frame}, pc, instr)
2639
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.upvals}[instr.${N.b} + 1]
2774
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
2640
2775
  return ${N.dispatch}(${N.frame}, pc + 1)
2641
2776
  end
2642
2777
 
2643
2778
  -- SETUPVAL
2644
2779
  ${N.handlers}[${op(5 /* SETUPVAL */)}] = function(${N.frame}, pc, instr)
2645
- ${N.frame}.${N.upvals}[instr.${N.b} + 1] = ${N.frame}.${N.R}[instr.${N.a}]
2780
+ ${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
2646
2781
  return ${N.dispatch}(${N.frame}, pc + 1)
2647
2782
  end
2648
2783
 
2649
2784
  -- GETGLOBAL (b\uB294 RK\uAC00 \uC544\uB2C8\uB77C \uC0C1\uC218\uD480 \uC21C\uC218 \uC778\uB371\uC2A4)
2650
2785
  ${N.handlers}[${op(6 /* GETGLOBAL */)}] = function(${N.frame}, pc, instr)
2651
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.globals}[${N.frame}.${N.K}[instr.${N.b} + 1]]
2786
+ ${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
2652
2787
  return ${N.dispatch}(${N.frame}, pc + 1)
2653
2788
  end
2654
2789
 
2655
2790
  -- SETGLOBAL
2656
2791
  ${N.handlers}[${op(7 /* SETGLOBAL */)}] = function(${N.frame}, pc, instr)
2657
- ${N.globals}[${N.frame}.${N.K}[instr.${N.b} + 1]] = ${N.frame}.${N.R}[instr.${N.a}]
2792
+ ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
2658
2793
  return ${N.dispatch}(${N.frame}, pc + 1)
2659
2794
  end
2660
2795
 
2661
2796
  -- GETTABLE
2662
2797
  ${N.handlers}[${op(8 /* GETTABLE */)}] = function(${N.frame}, pc, instr)
2663
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.R}[instr.${N.b}][${N.rk}(${N.frame}, instr.${N.c})]
2798
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
2664
2799
  return ${N.dispatch}(${N.frame}, pc + 1)
2665
2800
  end
2666
2801
 
2667
2802
  -- SETTABLE
2668
2803
  ${N.handlers}[${op(9 /* SETTABLE */)}] = function(${N.frame}, pc, instr)
2669
- ${N.frame}.${N.R}[instr.${N.a}][${N.rk}(${N.frame}, instr.${N.b})] = ${N.rk}(${N.frame}, instr.${N.c})
2804
+ ${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
2670
2805
  return ${N.dispatch}(${N.frame}, pc + 1)
2671
2806
  end
2672
2807
 
2673
2808
  -- NEWTABLE
2674
2809
  ${N.handlers}[${op(10 /* NEWTABLE */)}] = function(${N.frame}, pc, instr)
2675
- ${N.frame}.${N.R}[instr.${N.a}] = {}
2810
+ ${N.frame}.${N.R}[instr[2]] = {}
2676
2811
  return ${N.dispatch}(${N.frame}, pc + 1)
2677
2812
  end
2678
2813
 
2679
2814
  -- SELF (R[a] = R[b][key]; R[a+1] = R[b])
2680
2815
  ${N.handlers}[${op(11 /* SELF */)}] = function(${N.frame}, pc, instr)
2681
- local obj = ${N.frame}.${N.R}[instr.${N.b}]
2682
- ${N.frame}.${N.R}[instr.${N.a}] = obj[${N.rk}(${N.frame}, instr.${N.c})]
2683
- ${N.frame}.${N.R}[instr.${N.a} + 1] = obj
2816
+ local obj = ${N.frame}.${N.R}[instr[3]]
2817
+ ${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
2818
+ ${N.frame}.${N.R}[instr[2] + 1] = obj
2684
2819
  return ${N.dispatch}(${N.frame}, pc + 1)
2685
2820
  end
2686
2821
 
2687
2822
  -- ADD SUB MUL DIV MOD POW
2688
2823
  ${N.handlers}[${op(12 /* ADD */)}] = function(${N.frame}, pc, instr)
2689
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) + ${N.rk}(${N.frame}, instr.${N.c})
2824
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
2690
2825
  return ${N.dispatch}(${N.frame}, pc + 1)
2691
2826
  end
2692
2827
  ${N.handlers}[${op(13 /* SUB */)}] = function(${N.frame}, pc, instr)
2693
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) - ${N.rk}(${N.frame}, instr.${N.c})
2828
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
2694
2829
  return ${N.dispatch}(${N.frame}, pc + 1)
2695
2830
  end
2696
2831
  ${N.handlers}[${op(14 /* MUL */)}] = function(${N.frame}, pc, instr)
2697
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) * ${N.rk}(${N.frame}, instr.${N.c})
2832
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
2698
2833
  return ${N.dispatch}(${N.frame}, pc + 1)
2699
2834
  end
2700
2835
  ${N.handlers}[${op(15 /* DIV */)}] = function(${N.frame}, pc, instr)
2701
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) / ${N.rk}(${N.frame}, instr.${N.c})
2836
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
2702
2837
  return ${N.dispatch}(${N.frame}, pc + 1)
2703
2838
  end
2704
2839
  ${N.handlers}[${op(16 /* MOD */)}] = function(${N.frame}, pc, instr)
2705
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) % ${N.rk}(${N.frame}, instr.${N.c})
2840
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
2706
2841
  return ${N.dispatch}(${N.frame}, pc + 1)
2707
2842
  end
2708
2843
  ${N.handlers}[${op(17 /* POW */)}] = function(${N.frame}, pc, instr)
2709
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) ^ ${N.rk}(${N.frame}, instr.${N.c})
2844
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
2710
2845
  return ${N.dispatch}(${N.frame}, pc + 1)
2711
2846
  end
2712
2847
 
2713
2848
  -- IDIV (Luau \uBC14\uB2E5 \uB098\uB217\uC148)
2714
2849
  ${N.handlers}[${op(34 /* IDIV */)}] = function(${N.frame}, pc, instr)
2715
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) // ${N.rk}(${N.frame}, instr.${N.c})
2850
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
2716
2851
  return ${N.dispatch}(${N.frame}, pc + 1)
2717
2852
  end
2718
2853
 
2719
2854
  -- CONCAT
2720
2855
  ${N.handlers}[${op(18 /* CONCAT */)}] = function(${N.frame}, pc, instr)
2721
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) .. ${N.rk}(${N.frame}, instr.${N.c})
2856
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
2722
2857
  return ${N.dispatch}(${N.frame}, pc + 1)
2723
2858
  end
2724
2859
 
2725
2860
  -- UNM
2726
2861
  ${N.handlers}[${op(19 /* UNM */)}] = function(${N.frame}, pc, instr)
2727
- ${N.frame}.${N.R}[instr.${N.a}] = -${N.frame}.${N.R}[instr.${N.b}]
2862
+ ${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
2728
2863
  return ${N.dispatch}(${N.frame}, pc + 1)
2729
2864
  end
2730
2865
 
2731
2866
  -- NOT
2732
2867
  ${N.handlers}[${op(20 /* NOT */)}] = function(${N.frame}, pc, instr)
2733
- ${N.frame}.${N.R}[instr.${N.a}] = not ${N.frame}.${N.R}[instr.${N.b}]
2868
+ ${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
2734
2869
  return ${N.dispatch}(${N.frame}, pc + 1)
2735
2870
  end
2736
2871
 
2737
2872
  -- LEN
2738
2873
  ${N.handlers}[${op(21 /* LEN */)}] = function(${N.frame}, pc, instr)
2739
- ${N.frame}.${N.R}[instr.${N.a}] = #${N.frame}.${N.R}[instr.${N.b}]
2874
+ ${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
2740
2875
  return ${N.dispatch}(${N.frame}, pc + 1)
2741
2876
  end
2742
2877
 
2743
2878
  -- JMP (pc += a)
2744
2879
  ${N.handlers}[${op(22 /* JMP */)}] = function(${N.frame}, pc, instr)
2745
- return ${N.dispatch}(${N.frame}, pc + instr.${N.a})
2880
+ return ${N.dispatch}(${N.frame}, pc + instr[2])
2746
2881
  end
2747
2882
 
2748
2883
  -- EQ LT LE (\uAC12 \uC0DD\uC131\uD615 \uBE44\uAD50)
2749
2884
  ${N.handlers}[${op(23 /* EQ */)}] = function(${N.frame}, pc, instr)
2750
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) == ${N.rk}(${N.frame}, instr.${N.c})
2885
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
2751
2886
  return ${N.dispatch}(${N.frame}, pc + 1)
2752
2887
  end
2753
2888
  ${N.handlers}[${op(24 /* LT */)}] = function(${N.frame}, pc, instr)
2754
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) < ${N.rk}(${N.frame}, instr.${N.c})
2889
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
2755
2890
  return ${N.dispatch}(${N.frame}, pc + 1)
2756
2891
  end
2757
2892
  ${N.handlers}[${op(25 /* LE */)}] = function(${N.frame}, pc, instr)
2758
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) <= ${N.rk}(${N.frame}, instr.${N.c})
2893
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
2759
2894
  return ${N.dispatch}(${N.frame}, pc + 1)
2760
2895
  end
2761
2896
 
2762
2897
  -- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 \u2014 \uBCF4\uD1B5 \uB2E4\uC74C \uBA85\uB839\uC740 JMP)
2763
2898
  ${N.handlers}[${op(26 /* TEST */)}] = function(${N.frame}, pc, instr)
2764
- if (not ${N.frame}.${N.R}[instr.${N.a}]) ~= (instr.${N.c} ~= 0) then
2899
+ if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
2765
2900
  return ${N.dispatch}(${N.frame}, pc + 2)
2766
2901
  end
2767
2902
  return ${N.dispatch}(${N.frame}, pc + 1)
@@ -2769,7 +2904,7 @@ end
2769
2904
 
2770
2905
  -- CALL
2771
2906
  ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
2772
- local a, b, c = instr.${N.a}, instr.${N.b}, instr.${N.c}
2907
+ local a, b, c = instr[2], instr[3], instr[4]
2773
2908
  local fn = ${N.frame}.${N.R}[a]
2774
2909
  local nargs
2775
2910
  if b == 0 then
@@ -2794,7 +2929,7 @@ end
2794
2929
 
2795
2930
  -- RETURN
2796
2931
  ${N.handlers}[${op(28 /* RETURN */)}] = function(${N.frame}, pc, instr)
2797
- local a, b = instr.${N.a}, instr.${N.b}
2932
+ local a, b = instr[2], instr[3]
2798
2933
  if b == 0 then
2799
2934
  return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
2800
2935
  end
@@ -2803,14 +2938,14 @@ end
2803
2938
 
2804
2939
  -- FORPREP
2805
2940
  ${N.handlers}[${op(29 /* FORPREP */)}] = function(${N.frame}, pc, instr)
2806
- local a = instr.${N.a}
2941
+ local a = instr[2]
2807
2942
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
2808
- return ${N.dispatch}(${N.frame}, pc + instr.${N.b})
2943
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2809
2944
  end
2810
2945
 
2811
2946
  -- FORLOOP
2812
2947
  ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2813
- local a = instr.${N.a}
2948
+ local a = instr[2]
2814
2949
  local step = ${N.frame}.${N.R}[a + 2]
2815
2950
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] + step
2816
2951
  local ok
@@ -2821,26 +2956,26 @@ ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2821
2956
  end
2822
2957
  if ok then
2823
2958
  ${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
2824
- return ${N.dispatch}(${N.frame}, pc + instr.${N.b})
2959
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2825
2960
  end
2826
2961
  return ${N.dispatch}(${N.frame}, pc + 1)
2827
2962
  end
2828
2963
 
2829
2964
  -- CLOSURE
2830
2965
  ${N.handlers}[${op(31 /* CLOSURE */)}] = function(${N.frame}, pc, instr)
2831
- local childProto = ${N.frame}.${N.protos}[instr.${N.b} + 1]
2966
+ local childProto = ${N.frame}.${N.protos}[instr[3] + 1]
2832
2967
  local n = #childProto.${N.upvalDescs}
2833
2968
  local capturedUpvals = table.create(n)
2834
2969
  for i = 1, n do
2835
2970
  local desc = childProto.${N.upvalDescs}[i]
2836
2971
  local captureInstr = ${N.frame}.${N.code}[pc + i]
2837
2972
  if desc.${N.kind} == "local" then
2838
- capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr.${N.b}]
2973
+ capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr[3]]
2839
2974
  else
2840
- capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr.${N.b} + 1]
2975
+ capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr[3] + 1]
2841
2976
  end
2842
2977
  end
2843
- ${N.frame}.${N.R}[instr.${N.a}] = function(...)
2978
+ ${N.frame}.${N.R}[instr[2]] = function(...)
2844
2979
  return ${N.execute}(childProto, capturedUpvals, ...)
2845
2980
  end
2846
2981
  return ${N.dispatch}(${N.frame}, pc + 1 + n)
@@ -2848,22 +2983,22 @@ end
2848
2983
 
2849
2984
  -- VARARG
2850
2985
  ${N.handlers}[${op(32 /* VARARG */)}] = function(${N.frame}, pc, instr)
2851
- local count = instr.${N.b} == 0 and ${N.frame}.${N.nVarargs} or (instr.${N.b} - 1)
2986
+ local count = instr[3] == 0 and ${N.frame}.${N.nVarargs} or (instr[3] - 1)
2852
2987
  for i = 1, count do
2853
- ${N.frame}.${N.R}[instr.${N.a} + i - 1] = ${N.frame}.${N.varargs}[i]
2988
+ ${N.frame}.${N.R}[instr[2] + i - 1] = ${N.frame}.${N.varargs}[i]
2854
2989
  end
2855
- if instr.${N.b} == 0 then
2856
- ${N.frame}.${N.multiTop} = instr.${N.a} + count
2990
+ if instr[3] == 0 then
2991
+ ${N.frame}.${N.multiTop} = instr[2] + count
2857
2992
  end
2858
2993
  return ${N.dispatch}(${N.frame}, pc + 1)
2859
2994
  end
2860
2995
 
2861
2996
  -- SETLIST
2862
2997
  ${N.handlers}[${op(33 /* SETLIST */)}] = function(${N.frame}, pc, instr)
2863
- local tbl = ${N.frame}.${N.R}[instr.${N.a}]
2864
- local valuesBase = instr.${N.b}
2998
+ local tbl = ${N.frame}.${N.R}[instr[2]]
2999
+ local valuesBase = instr[3]
2865
3000
  local top = ${N.frame}.${N.multiTop} or (valuesBase + 1)
2866
- local startIdx = instr.${N.c}
3001
+ local startIdx = instr[4]
2867
3002
  local n = top - valuesBase
2868
3003
  for i = 0, n - 1 do
2869
3004
  tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
@@ -2937,10 +3072,6 @@ function generateVmNames(random = Math.random) {
2937
3072
  varargs: id(),
2938
3073
  nVarargs: id(),
2939
3074
  multiTop: id(),
2940
- op: id(),
2941
- a: id(),
2942
- b: id(),
2943
- c: id(),
2944
3075
  numParams: id(),
2945
3076
  hasVarargs: id(),
2946
3077
  maxRegs: id(),
@@ -2952,19 +3083,27 @@ function generateVmNames(random = Math.random) {
2952
3083
  }
2953
3084
 
2954
3085
  // src/passes/Vmify.ts
3086
+ function markRuntimeNumbersAsStructural(body) {
3087
+ transformExpressions({ body }, (expr) => {
3088
+ if (expr.type === "NumberLiteral") vmStructuralNumbers.add(expr);
3089
+ return void 0;
3090
+ });
3091
+ }
2955
3092
  function runVmify(program, options) {
2956
- const globalNames = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
3093
+ const builtinGlobals = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
2957
3094
  const random = options.random ?? Math.random;
2958
3095
  const opcodeMap = createOpcodeMap(random);
2959
3096
  const names = generateVmNames(random);
2960
- const compiler = new VmCompiler(program, globalNames, opcodeMap);
3097
+ const compiler = new VmCompiler(program, builtinGlobals, opcodeMap);
2961
3098
  const topProto = compiler.compile();
3099
+ const usedGlobalNames = compiler.getUsedGlobalNames();
2962
3100
  const globalsTable = table(
2963
- globalNames.map((name) => namedField(name, identifier(name)))
3101
+ usedGlobalNames.map((name) => namedField(name, identifier(name)))
2964
3102
  );
2965
3103
  const runtimeSource = buildVmRuntimeSource(names, opcodeMap);
2966
3104
  const runtimeProgram = luauparser2.parse(runtimeSource);
2967
3105
  const runtimeStatements = runtimeProgram.body.statements;
3106
+ markRuntimeNumbersAsStructural(runtimeProgram.body);
2968
3107
  const protoLiteral = serializeProto(topProto, names);
2969
3108
  const newBody = [
2970
3109
  localStatement(names.globals, globalsTable),
@@ -2979,10 +3118,10 @@ function runVmify(program, options) {
2979
3118
  var PASS_ORDER = [
2980
3119
  "Vmify",
2981
3120
  "GlobalMapping",
2982
- "StringsToExpressions",
2983
- "NumbersToExpressions",
2984
3121
  "RenameVariables",
2985
3122
  "ConstantArray",
3123
+ "StringsToExpressions",
3124
+ "NumbersToExpressions",
2986
3125
  "EncryptStrings",
2987
3126
  "EncryptNumbers",
2988
3127
  "InsertJunk",