luau-obfuscator 1.0.1 → 1.0.2

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 [];
243
309
  }
244
- return visitor(expr) ?? expr;
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();
347
+ }
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
  }
@@ -459,11 +569,12 @@ function stringCharCall(bytes) {
459
569
  return call(member(identifier("string"), "char"), bytes.map(numberLiteral));
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);
@@ -555,12 +666,15 @@ function buildRecursiveStringExpr(chunks) {
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
@@ -2557,13 +2675,13 @@ function serializeConstValue(v) {
2557
2675
  if (typeof v === "number") return numberLiteral(v);
2558
2676
  return booleanLiteral(v);
2559
2677
  }
2560
- function serializeInstrs(proto, names) {
2678
+ function serializeInstrs(proto) {
2561
2679
  const fields = proto.code.map((instr) => positionalField(
2562
2680
  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))
2681
+ positionalField(vmNumberLiteral(instr.op)),
2682
+ positionalField(vmNumberLiteral(instr.a)),
2683
+ positionalField(vmNumberLiteral(instr.b)),
2684
+ positionalField(vmNumberLiteral(instr.c))
2567
2685
  ])
2568
2686
  ));
2569
2687
  return table(fields);
@@ -2572,17 +2690,17 @@ function serializeUpvalDescs(proto, names) {
2572
2690
  const fields = proto.upvalDescs.map((d) => positionalField(
2573
2691
  table([
2574
2692
  namedField(names.kind, stringLiteral(d.kind)),
2575
- namedField(names.index, numberLiteral(d.index))
2693
+ namedField(names.index, vmNumberLiteral(d.index))
2576
2694
  ])
2577
2695
  ));
2578
2696
  return table(fields);
2579
2697
  }
2580
2698
  function serializeProto(proto, names) {
2581
2699
  const fields = [
2582
- namedField(names.numParams, numberLiteral(proto.numParams)),
2700
+ namedField(names.numParams, vmNumberLiteral(proto.numParams)),
2583
2701
  namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
2584
- namedField(names.maxRegs, numberLiteral(proto.maxRegs)),
2585
- namedField(names.code, serializeInstrs(proto, names)),
2702
+ namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
2703
+ namedField(names.code, serializeInstrs(proto)),
2586
2704
  namedField(names.consts, serializeConsts(proto.consts)),
2587
2705
  namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
2588
2706
  namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names)))))
@@ -2599,7 +2717,7 @@ local ${N.handlers} = {}
2599
2717
 
2600
2718
  local function ${N.dispatch}(${N.frame}, pc)
2601
2719
  local instr = ${N.frame}.${N.code}[pc]
2602
- return ${N.handlers}[instr.${N.op}](${N.frame}, pc, instr)
2720
+ return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
2603
2721
  end
2604
2722
 
2605
2723
  local function ${N.rk}(${N.frame}, x)
@@ -2612,156 +2730,156 @@ end
2612
2730
 
2613
2731
  -- MOVE
2614
2732
  ${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}]
2733
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
2616
2734
  return ${N.dispatch}(${N.frame}, pc + 1)
2617
2735
  end
2618
2736
 
2619
2737
  -- LOADK
2620
2738
  ${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]
2739
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
2622
2740
  return ${N.dispatch}(${N.frame}, pc + 1)
2623
2741
  end
2624
2742
 
2625
2743
  -- LOADBOOL
2626
2744
  ${N.handlers}[${op(2 /* LOADBOOL */)}] = function(${N.frame}, pc, instr)
2627
- ${N.frame}.${N.R}[instr.${N.a}] = (instr.${N.b} ~= 0)
2745
+ ${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
2628
2746
  return ${N.dispatch}(${N.frame}, pc + 1)
2629
2747
  end
2630
2748
 
2631
2749
  -- LOADNIL
2632
2750
  ${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
2751
+ for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
2634
2752
  return ${N.dispatch}(${N.frame}, pc + 1)
2635
2753
  end
2636
2754
 
2637
2755
  -- GETUPVAL
2638
2756
  ${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]
2757
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
2640
2758
  return ${N.dispatch}(${N.frame}, pc + 1)
2641
2759
  end
2642
2760
 
2643
2761
  -- SETUPVAL
2644
2762
  ${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}]
2763
+ ${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
2646
2764
  return ${N.dispatch}(${N.frame}, pc + 1)
2647
2765
  end
2648
2766
 
2649
2767
  -- GETGLOBAL (b\uB294 RK\uAC00 \uC544\uB2C8\uB77C \uC0C1\uC218\uD480 \uC21C\uC218 \uC778\uB371\uC2A4)
2650
2768
  ${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]]
2769
+ ${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
2652
2770
  return ${N.dispatch}(${N.frame}, pc + 1)
2653
2771
  end
2654
2772
 
2655
2773
  -- SETGLOBAL
2656
2774
  ${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}]
2775
+ ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
2658
2776
  return ${N.dispatch}(${N.frame}, pc + 1)
2659
2777
  end
2660
2778
 
2661
2779
  -- GETTABLE
2662
2780
  ${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})]
2781
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
2664
2782
  return ${N.dispatch}(${N.frame}, pc + 1)
2665
2783
  end
2666
2784
 
2667
2785
  -- SETTABLE
2668
2786
  ${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})
2787
+ ${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
2670
2788
  return ${N.dispatch}(${N.frame}, pc + 1)
2671
2789
  end
2672
2790
 
2673
2791
  -- NEWTABLE
2674
2792
  ${N.handlers}[${op(10 /* NEWTABLE */)}] = function(${N.frame}, pc, instr)
2675
- ${N.frame}.${N.R}[instr.${N.a}] = {}
2793
+ ${N.frame}.${N.R}[instr[2]] = {}
2676
2794
  return ${N.dispatch}(${N.frame}, pc + 1)
2677
2795
  end
2678
2796
 
2679
2797
  -- SELF (R[a] = R[b][key]; R[a+1] = R[b])
2680
2798
  ${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
2799
+ local obj = ${N.frame}.${N.R}[instr[3]]
2800
+ ${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
2801
+ ${N.frame}.${N.R}[instr[2] + 1] = obj
2684
2802
  return ${N.dispatch}(${N.frame}, pc + 1)
2685
2803
  end
2686
2804
 
2687
2805
  -- ADD SUB MUL DIV MOD POW
2688
2806
  ${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})
2807
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
2690
2808
  return ${N.dispatch}(${N.frame}, pc + 1)
2691
2809
  end
2692
2810
  ${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})
2811
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
2694
2812
  return ${N.dispatch}(${N.frame}, pc + 1)
2695
2813
  end
2696
2814
  ${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})
2815
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
2698
2816
  return ${N.dispatch}(${N.frame}, pc + 1)
2699
2817
  end
2700
2818
  ${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})
2819
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
2702
2820
  return ${N.dispatch}(${N.frame}, pc + 1)
2703
2821
  end
2704
2822
  ${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})
2823
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
2706
2824
  return ${N.dispatch}(${N.frame}, pc + 1)
2707
2825
  end
2708
2826
  ${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})
2827
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
2710
2828
  return ${N.dispatch}(${N.frame}, pc + 1)
2711
2829
  end
2712
2830
 
2713
2831
  -- IDIV (Luau \uBC14\uB2E5 \uB098\uB217\uC148)
2714
2832
  ${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})
2833
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
2716
2834
  return ${N.dispatch}(${N.frame}, pc + 1)
2717
2835
  end
2718
2836
 
2719
2837
  -- CONCAT
2720
2838
  ${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})
2839
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
2722
2840
  return ${N.dispatch}(${N.frame}, pc + 1)
2723
2841
  end
2724
2842
 
2725
2843
  -- UNM
2726
2844
  ${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}]
2845
+ ${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
2728
2846
  return ${N.dispatch}(${N.frame}, pc + 1)
2729
2847
  end
2730
2848
 
2731
2849
  -- NOT
2732
2850
  ${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}]
2851
+ ${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
2734
2852
  return ${N.dispatch}(${N.frame}, pc + 1)
2735
2853
  end
2736
2854
 
2737
2855
  -- LEN
2738
2856
  ${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}]
2857
+ ${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
2740
2858
  return ${N.dispatch}(${N.frame}, pc + 1)
2741
2859
  end
2742
2860
 
2743
2861
  -- JMP (pc += a)
2744
2862
  ${N.handlers}[${op(22 /* JMP */)}] = function(${N.frame}, pc, instr)
2745
- return ${N.dispatch}(${N.frame}, pc + instr.${N.a})
2863
+ return ${N.dispatch}(${N.frame}, pc + instr[2])
2746
2864
  end
2747
2865
 
2748
2866
  -- EQ LT LE (\uAC12 \uC0DD\uC131\uD615 \uBE44\uAD50)
2749
2867
  ${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})
2868
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
2751
2869
  return ${N.dispatch}(${N.frame}, pc + 1)
2752
2870
  end
2753
2871
  ${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})
2872
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
2755
2873
  return ${N.dispatch}(${N.frame}, pc + 1)
2756
2874
  end
2757
2875
  ${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})
2876
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
2759
2877
  return ${N.dispatch}(${N.frame}, pc + 1)
2760
2878
  end
2761
2879
 
2762
2880
  -- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 \u2014 \uBCF4\uD1B5 \uB2E4\uC74C \uBA85\uB839\uC740 JMP)
2763
2881
  ${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
2882
+ if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
2765
2883
  return ${N.dispatch}(${N.frame}, pc + 2)
2766
2884
  end
2767
2885
  return ${N.dispatch}(${N.frame}, pc + 1)
@@ -2769,7 +2887,7 @@ end
2769
2887
 
2770
2888
  -- CALL
2771
2889
  ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
2772
- local a, b, c = instr.${N.a}, instr.${N.b}, instr.${N.c}
2890
+ local a, b, c = instr[2], instr[3], instr[4]
2773
2891
  local fn = ${N.frame}.${N.R}[a]
2774
2892
  local nargs
2775
2893
  if b == 0 then
@@ -2794,7 +2912,7 @@ end
2794
2912
 
2795
2913
  -- RETURN
2796
2914
  ${N.handlers}[${op(28 /* RETURN */)}] = function(${N.frame}, pc, instr)
2797
- local a, b = instr.${N.a}, instr.${N.b}
2915
+ local a, b = instr[2], instr[3]
2798
2916
  if b == 0 then
2799
2917
  return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
2800
2918
  end
@@ -2803,14 +2921,14 @@ end
2803
2921
 
2804
2922
  -- FORPREP
2805
2923
  ${N.handlers}[${op(29 /* FORPREP */)}] = function(${N.frame}, pc, instr)
2806
- local a = instr.${N.a}
2924
+ local a = instr[2]
2807
2925
  ${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})
2926
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2809
2927
  end
2810
2928
 
2811
2929
  -- FORLOOP
2812
2930
  ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2813
- local a = instr.${N.a}
2931
+ local a = instr[2]
2814
2932
  local step = ${N.frame}.${N.R}[a + 2]
2815
2933
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] + step
2816
2934
  local ok
@@ -2821,26 +2939,26 @@ ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2821
2939
  end
2822
2940
  if ok then
2823
2941
  ${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
2824
- return ${N.dispatch}(${N.frame}, pc + instr.${N.b})
2942
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2825
2943
  end
2826
2944
  return ${N.dispatch}(${N.frame}, pc + 1)
2827
2945
  end
2828
2946
 
2829
2947
  -- CLOSURE
2830
2948
  ${N.handlers}[${op(31 /* CLOSURE */)}] = function(${N.frame}, pc, instr)
2831
- local childProto = ${N.frame}.${N.protos}[instr.${N.b} + 1]
2949
+ local childProto = ${N.frame}.${N.protos}[instr[3] + 1]
2832
2950
  local n = #childProto.${N.upvalDescs}
2833
2951
  local capturedUpvals = table.create(n)
2834
2952
  for i = 1, n do
2835
2953
  local desc = childProto.${N.upvalDescs}[i]
2836
2954
  local captureInstr = ${N.frame}.${N.code}[pc + i]
2837
2955
  if desc.${N.kind} == "local" then
2838
- capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr.${N.b}]
2956
+ capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr[3]]
2839
2957
  else
2840
- capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr.${N.b} + 1]
2958
+ capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr[3] + 1]
2841
2959
  end
2842
2960
  end
2843
- ${N.frame}.${N.R}[instr.${N.a}] = function(...)
2961
+ ${N.frame}.${N.R}[instr[2]] = function(...)
2844
2962
  return ${N.execute}(childProto, capturedUpvals, ...)
2845
2963
  end
2846
2964
  return ${N.dispatch}(${N.frame}, pc + 1 + n)
@@ -2848,22 +2966,22 @@ end
2848
2966
 
2849
2967
  -- VARARG
2850
2968
  ${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)
2969
+ local count = instr[3] == 0 and ${N.frame}.${N.nVarargs} or (instr[3] - 1)
2852
2970
  for i = 1, count do
2853
- ${N.frame}.${N.R}[instr.${N.a} + i - 1] = ${N.frame}.${N.varargs}[i]
2971
+ ${N.frame}.${N.R}[instr[2] + i - 1] = ${N.frame}.${N.varargs}[i]
2854
2972
  end
2855
- if instr.${N.b} == 0 then
2856
- ${N.frame}.${N.multiTop} = instr.${N.a} + count
2973
+ if instr[3] == 0 then
2974
+ ${N.frame}.${N.multiTop} = instr[2] + count
2857
2975
  end
2858
2976
  return ${N.dispatch}(${N.frame}, pc + 1)
2859
2977
  end
2860
2978
 
2861
2979
  -- SETLIST
2862
2980
  ${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}
2981
+ local tbl = ${N.frame}.${N.R}[instr[2]]
2982
+ local valuesBase = instr[3]
2865
2983
  local top = ${N.frame}.${N.multiTop} or (valuesBase + 1)
2866
- local startIdx = instr.${N.c}
2984
+ local startIdx = instr[4]
2867
2985
  local n = top - valuesBase
2868
2986
  for i = 0, n - 1 do
2869
2987
  tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
@@ -2937,10 +3055,6 @@ function generateVmNames(random = Math.random) {
2937
3055
  varargs: id(),
2938
3056
  nVarargs: id(),
2939
3057
  multiTop: id(),
2940
- op: id(),
2941
- a: id(),
2942
- b: id(),
2943
- c: id(),
2944
3058
  numParams: id(),
2945
3059
  hasVarargs: id(),
2946
3060
  maxRegs: id(),
@@ -2952,6 +3066,12 @@ function generateVmNames(random = Math.random) {
2952
3066
  }
2953
3067
 
2954
3068
  // src/passes/Vmify.ts
3069
+ function markRuntimeNumbersAsStructural(body) {
3070
+ transformExpressions({ body }, (expr) => {
3071
+ if (expr.type === "NumberLiteral") vmStructuralNumbers.add(expr);
3072
+ return void 0;
3073
+ });
3074
+ }
2955
3075
  function runVmify(program, options) {
2956
3076
  const globalNames = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
2957
3077
  const random = options.random ?? Math.random;
@@ -2965,6 +3085,7 @@ function runVmify(program, options) {
2965
3085
  const runtimeSource = buildVmRuntimeSource(names, opcodeMap);
2966
3086
  const runtimeProgram = luauparser2.parse(runtimeSource);
2967
3087
  const runtimeStatements = runtimeProgram.body.statements;
3088
+ markRuntimeNumbersAsStructural(runtimeProgram.body);
2968
3089
  const protoLiteral = serializeProto(topProto, names);
2969
3090
  const newBody = [
2970
3091
  localStatement(names.globals, globalsTable),
@@ -2979,10 +3100,10 @@ function runVmify(program, options) {
2979
3100
  var PASS_ORDER = [
2980
3101
  "Vmify",
2981
3102
  "GlobalMapping",
2982
- "StringsToExpressions",
2983
- "NumbersToExpressions",
2984
3103
  "RenameVariables",
2985
3104
  "ConstantArray",
3105
+ "StringsToExpressions",
3106
+ "NumbersToExpressions",
2986
3107
  "EncryptStrings",
2987
3108
  "EncryptNumbers",
2988
3109
  "InsertJunk",