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.cjs CHANGED
@@ -209,67 +209,171 @@ function visitArray(arr, visitor) {
209
209
  arr[i] = mapExpression(arr[i], visitor);
210
210
  }
211
211
  }
212
- function mapExpression(expr, visitor) {
212
+ function getExpressionChildren(expr) {
213
213
  switch (expr.type) {
214
- case "InterpolatedStringExpression":
214
+ case "InterpolatedStringExpression": {
215
+ const children = [];
215
216
  for (const part of expr.parts) {
216
217
  if (part.kind === "expression") {
217
- part.expression = mapExpression(part.expression, visitor);
218
+ children.push({
219
+ get: () => part.expression,
220
+ set: (v) => {
221
+ part.expression = v;
222
+ }
223
+ });
218
224
  }
219
225
  }
220
- break;
226
+ return children;
227
+ }
221
228
  case "FunctionExpression":
222
- walkFunctionBody(expr.func, visitor);
223
- break;
224
- case "TableExpression":
229
+ return [];
230
+ case "TableExpression": {
231
+ const children = [];
225
232
  for (const field of expr.fields) {
226
233
  if (field.type === "TableFieldPositional") {
227
- field.value = mapExpression(field.value, visitor);
234
+ children.push({ get: () => field.value, set: (v) => {
235
+ field.value = v;
236
+ } });
228
237
  } else if (field.type === "TableFieldNamed") {
229
- field.value = mapExpression(field.value, visitor);
238
+ children.push({ get: () => field.value, set: (v) => {
239
+ field.value = v;
240
+ } });
230
241
  } else if (field.type === "TableFieldComputed") {
231
- field.key = mapExpression(field.key, visitor);
232
- field.value = mapExpression(field.value, visitor);
242
+ children.push({ get: () => field.key, set: (v) => {
243
+ field.key = v;
244
+ } });
245
+ children.push({ get: () => field.value, set: (v) => {
246
+ field.value = v;
247
+ } });
233
248
  }
234
249
  }
235
- break;
250
+ return children;
251
+ }
236
252
  case "BinaryExpression":
237
- expr.left = mapExpression(expr.left, visitor);
238
- expr.right = mapExpression(expr.right, visitor);
239
- break;
253
+ return [
254
+ { get: () => expr.left, set: (v) => {
255
+ expr.left = v;
256
+ } },
257
+ { get: () => expr.right, set: (v) => {
258
+ expr.right = v;
259
+ } }
260
+ ];
240
261
  case "UnaryExpression":
241
- expr.argument = mapExpression(expr.argument, visitor);
242
- break;
262
+ return [{ get: () => expr.argument, set: (v) => {
263
+ expr.argument = v;
264
+ } }];
243
265
  case "MemberExpression":
244
- expr.object = mapExpression(expr.object, visitor);
245
- break;
266
+ return [{ get: () => expr.object, set: (v) => {
267
+ expr.object = v;
268
+ } }];
246
269
  case "IndexExpression":
247
- expr.object = mapExpression(expr.object, visitor);
248
- expr.index = mapExpression(expr.index, visitor);
249
- break;
250
- case "CallExpression":
251
- expr.callee = mapExpression(expr.callee, visitor);
252
- visitArray(expr.arguments, visitor);
253
- break;
254
- case "MethodCallExpression":
255
- expr.object = mapExpression(expr.object, visitor);
256
- visitArray(expr.arguments, visitor);
257
- break;
270
+ return [
271
+ { get: () => expr.object, set: (v) => {
272
+ expr.object = v;
273
+ } },
274
+ { get: () => expr.index, set: (v) => {
275
+ expr.index = v;
276
+ } }
277
+ ];
278
+ case "CallExpression": {
279
+ const children = [
280
+ { get: () => expr.callee, set: (v) => {
281
+ expr.callee = v;
282
+ } }
283
+ ];
284
+ for (let i = 0; i < expr.arguments.length; i++) {
285
+ const idx = i;
286
+ children.push({
287
+ get: () => expr.arguments[idx],
288
+ set: (v) => {
289
+ expr.arguments[idx] = v;
290
+ }
291
+ });
292
+ }
293
+ return children;
294
+ }
295
+ case "MethodCallExpression": {
296
+ const children = [
297
+ { get: () => expr.object, set: (v) => {
298
+ expr.object = v;
299
+ } }
300
+ ];
301
+ for (let i = 0; i < expr.arguments.length; i++) {
302
+ const idx = i;
303
+ children.push({
304
+ get: () => expr.arguments[idx],
305
+ set: (v) => {
306
+ expr.arguments[idx] = v;
307
+ }
308
+ });
309
+ }
310
+ return children;
311
+ }
258
312
  case "ParenthesizedExpression":
259
- expr.expression = mapExpression(expr.expression, visitor);
260
- break;
313
+ return [{ get: () => expr.expression, set: (v) => {
314
+ expr.expression = v;
315
+ } }];
261
316
  case "TypeAssertionExpression":
262
- expr.expression = mapExpression(expr.expression, visitor);
263
- break;
264
- case "IfElseExpression":
317
+ return [{ get: () => expr.expression, set: (v) => {
318
+ expr.expression = v;
319
+ } }];
320
+ case "IfElseExpression": {
321
+ const children = [];
265
322
  for (const clause of expr.clauses) {
266
- clause.condition = mapExpression(clause.condition, visitor);
267
- clause.body = mapExpression(clause.body, visitor);
323
+ children.push({ get: () => clause.condition, set: (v) => {
324
+ clause.condition = v;
325
+ } });
326
+ children.push({ get: () => clause.body, set: (v) => {
327
+ clause.body = v;
328
+ } });
268
329
  }
269
- expr.alternate = mapExpression(expr.alternate, visitor);
270
- break;
330
+ children.push({ get: () => expr.alternate, set: (v) => {
331
+ expr.alternate = v;
332
+ } });
333
+ return children;
334
+ }
335
+ default:
336
+ return [];
271
337
  }
272
- return visitor(expr) ?? expr;
338
+ }
339
+ function mapExpression(root, visitor) {
340
+ if (root.type === "FunctionExpression") {
341
+ walkFunctionBody(root.func, visitor);
342
+ return visitor(root) ?? root;
343
+ }
344
+ let result = root;
345
+ const stack = [{
346
+ expr: root,
347
+ children: getExpressionChildren(root),
348
+ index: 0,
349
+ setInParent: (v) => {
350
+ result = v;
351
+ }
352
+ }];
353
+ while (stack.length > 0) {
354
+ const frame = stack[stack.length - 1];
355
+ if (frame.index < frame.children.length) {
356
+ const child = frame.children[frame.index];
357
+ frame.index++;
358
+ const childExpr = child.get();
359
+ if (childExpr.type === "FunctionExpression") {
360
+ walkFunctionBody(childExpr.func, visitor);
361
+ child.set(visitor(childExpr) ?? childExpr);
362
+ continue;
363
+ }
364
+ stack.push({
365
+ expr: childExpr,
366
+ children: getExpressionChildren(childExpr),
367
+ index: 0,
368
+ setInParent: child.set
369
+ });
370
+ continue;
371
+ }
372
+ const finished = visitor(frame.expr) ?? frame.expr;
373
+ frame.setInParent?.(finished);
374
+ stack.pop();
375
+ }
376
+ return result;
273
377
  }
274
378
  function walkFunctionBody(func, visitor) {
275
379
  walkBlock(func.body, visitor);
@@ -355,6 +459,12 @@ function stringLiteral(value) {
355
459
  function numberLiteral(value) {
356
460
  return { type: "NumberLiteral", value, raw: String(value), line: POS, column: POS };
357
461
  }
462
+ var vmStructuralNumbers = /* @__PURE__ */ new WeakSet();
463
+ function vmNumberLiteral(value) {
464
+ const node = numberLiteral(value);
465
+ vmStructuralNumbers.add(node);
466
+ return node;
467
+ }
358
468
  function binary(operator, left, right) {
359
469
  return { type: "BinaryExpression", operator, left, right, line: POS, column: POS };
360
470
  }
@@ -487,11 +597,12 @@ function stringCharCall(bytes) {
487
597
  return call(member(identifier("string"), "char"), bytes.map(numberLiteral));
488
598
  }
489
599
  function buildConcatChain(chunks) {
490
- let expr = stringCharCall(chunks[0]);
491
- for (let i = 1; i < chunks.length; i++) {
492
- expr = binary("..", expr, stringCharCall(chunks[i]));
600
+ function build(lo, hi) {
601
+ if (hi - lo === 1) return stringCharCall(chunks[lo]);
602
+ const mid = lo + Math.floor((hi - lo) / 2);
603
+ return binary("..", build(lo, mid), build(mid, hi));
493
604
  }
494
- return expr;
605
+ return build(0, chunks.length);
495
606
  }
496
607
  function shuffledIndices(n) {
497
608
  const arr = Array.from({ length: n }, (_, i) => i);
@@ -583,12 +694,15 @@ function buildRecursiveStringExpr(chunks) {
583
694
  function pickControlFlowStrategy() {
584
695
  return Math.random() < 0.5 ? "forLoop" : "recursive";
585
696
  }
697
+ var MAX_CHUNKS_PER_STRING = 2e4;
586
698
  function runStringsToExpressions(program, options) {
587
699
  const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT;
588
700
  transformExpressions(program, (expr) => {
589
701
  if (expr.type !== "StringLiteral") return;
590
702
  if (expr.value.length === 0) return;
591
703
  const bytes = toUtf8Bytes(expr.value);
704
+ const estimatedChunks = Math.ceil(bytes.length / Math.max(1, options.min));
705
+ if (estimatedChunks > MAX_CHUNKS_PER_STRING) return;
592
706
  const chunks = splitByteChunks(bytes, options.min, options.max);
593
707
  if (chunks.length > 1 && Math.random() < controlFlowProbability) {
594
708
  return pickControlFlowStrategy() === "forLoop" ? buildForLoopStringExpr(chunks) : buildRecursiveStringExpr(chunks);
@@ -761,6 +875,7 @@ function runNumbersToExpressions(program, options) {
761
875
  const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT2;
762
876
  transformExpressions(program, (expr) => {
763
877
  if (expr.type !== "NumberLiteral") return;
878
+ if (vmStructuralNumbers.has(expr)) return;
764
879
  if (Number.isInteger(expr.value) && Math.random() < controlFlowProbability) {
765
880
  return buildControlFlowNumberExpr(expr.value, options.min, options.max);
766
881
  }
@@ -953,7 +1068,7 @@ function runConstantArray(program, options) {
953
1068
  seen.add(k);
954
1069
  order.push({ kind: "string", value: expr.value });
955
1070
  }
956
- } else if (expr.type === "NumberLiteral") {
1071
+ } else if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
957
1072
  const k = keyOf("number", expr.value);
958
1073
  if (!seen.has(k)) {
959
1074
  seen.add(k);
@@ -973,11 +1088,11 @@ function runConstantArray(program, options) {
973
1088
  transformExpressions(program, (expr) => {
974
1089
  if (expr.type === "StringLiteral") {
975
1090
  const idx = indexOf.get(keyOf("string", expr.value));
976
- return index(identifier(arrayName), numberLiteral(idx));
1091
+ return index(identifier(arrayName), vmNumberLiteral(idx));
977
1092
  }
978
- if (expr.type === "NumberLiteral") {
1093
+ if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
979
1094
  const idx = indexOf.get(keyOf("number", expr.value));
980
- return index(identifier(arrayName), numberLiteral(idx));
1095
+ return index(identifier(arrayName), vmNumberLiteral(idx));
981
1096
  }
982
1097
  return void 0;
983
1098
  });
@@ -1018,15 +1133,15 @@ function obfuscatedNumber(value) {
1018
1133
  const variant = randomInt4(0, 2);
1019
1134
  if (variant === 0) {
1020
1135
  const a2 = randomInt4(0, value);
1021
- return binary("+", numberLiteral(a2), numberLiteral(value - a2));
1136
+ return binary("+", vmNumberLiteral(a2), vmNumberLiteral(value - a2));
1022
1137
  }
1023
1138
  if (variant === 1) {
1024
1139
  const a2 = randomInt4(0, 16777215);
1025
- return binary("-", numberLiteral(value + a2), numberLiteral(a2));
1140
+ return binary("-", vmNumberLiteral(value + a2), vmNumberLiteral(a2));
1026
1141
  }
1027
1142
  const a = randomInt4(0, 16777215);
1028
1143
  const b = (value ^ a) >>> 0;
1029
- return call(member(identifier("bit32"), "bxor"), [numberLiteral(a), numberLiteral(b)]);
1144
+ return call(member(identifier("bit32"), "bxor"), [vmNumberLiteral(a), vmNumberLiteral(b)]);
1030
1145
  }
1031
1146
  function buildDecoderStatement(name) {
1032
1147
  const dataParam = randomName3();
@@ -1037,17 +1152,17 @@ function buildDecoderStatement(name) {
1037
1152
  const nKeysVar = randomName3();
1038
1153
  const idxExpr = binary(
1039
1154
  "+",
1040
- binary("%", binary("-", identifier(iVar), numberLiteral(1)), identifier(nKeysVar)),
1041
- numberLiteral(1)
1155
+ binary("%", binary("-", identifier(iVar), vmNumberLiteral(1)), identifier(nKeysVar)),
1156
+ vmNumberLiteral(1)
1042
1157
  );
1043
1158
  const keyByte = index(identifier(keysParam), idxExpr);
1044
1159
  const mixed = call(member(identifier("bit32"), "band"), [
1045
- call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar), numberLiteral(8))]),
1046
- numberLiteral(255)
1160
+ call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar), vmNumberLiteral(8))]),
1161
+ vmNumberLiteral(255)
1047
1162
  ]);
1048
1163
  const posByte = call(member(identifier("bit32"), "band"), [
1049
1164
  binary("*", identifier(iVar), identifier(multParam)),
1050
- numberLiteral(255)
1165
+ vmNumberLiteral(255)
1051
1166
  ]);
1052
1167
  const keystream = call(member(identifier("bit32"), "bxor"), [mixed, posByte]);
1053
1168
  const finalByte = call(member(identifier("bit32"), "bxor"), [
@@ -1059,7 +1174,7 @@ function buildDecoderStatement(name) {
1059
1174
  localStatement(outVar, table([])),
1060
1175
  numericForStatement(
1061
1176
  iVar,
1062
- numberLiteral(1),
1177
+ vmNumberLiteral(1),
1063
1178
  unary("#", identifier(dataParam)),
1064
1179
  block([
1065
1180
  assignmentStatement(
@@ -1087,7 +1202,7 @@ function runEncryptStrings(program, _options) {
1087
1202
  const mult = randomInt4(1, 255);
1088
1203
  const plainBytes = toUtf8Bytes2(expr.value);
1089
1204
  const cipherBytes = plainBytes.map((b, idx) => b ^ keystreamByte(keys, mult, idx + 1));
1090
- const dataTable = table(cipherBytes.map((b) => positionalField(numberLiteral(b))));
1205
+ const dataTable = table(cipherBytes.map((b) => positionalField(vmNumberLiteral(b))));
1091
1206
  const keysTable = table(keys.map((k) => positionalField(obfuscatedNumber(k))));
1092
1207
  const multExpr = obfuscatedNumber(mult);
1093
1208
  return call(identifier(decoderName), [dataTable, keysTable, multExpr]);
@@ -1154,6 +1269,7 @@ function runEncryptNumbers(program, _options) {
1154
1269
  let used = false;
1155
1270
  transformExpressions(program, (expr) => {
1156
1271
  if (expr.type !== "NumberLiteral") return;
1272
+ if (vmStructuralNumbers.has(expr)) return;
1157
1273
  if (!isEncryptable(expr.value)) return;
1158
1274
  used = true;
1159
1275
  const keyA = randomInt5(1, 16777215);
@@ -1404,16 +1520,18 @@ function createProto(id) {
1404
1520
  maxRegs: 0,
1405
1521
  code: [],
1406
1522
  consts: [],
1523
+ constIndex: /* @__PURE__ */ new Map(),
1407
1524
  upvalDescs: [],
1408
1525
  protos: []
1409
1526
  };
1410
1527
  }
1411
1528
  function internConst(proto, value) {
1412
- for (let i = 0; i < proto.consts.length; i++) {
1413
- if (proto.consts[i] === value) return i;
1414
- }
1529
+ const existing = proto.constIndex.get(value);
1530
+ if (existing !== void 0) return existing;
1415
1531
  proto.consts.push(value);
1416
- return proto.consts.length - 1;
1532
+ const idx = proto.consts.length - 1;
1533
+ proto.constIndex.set(value, idx);
1534
+ return idx;
1417
1535
  }
1418
1536
 
1419
1537
  // src/passes/vmify/registers.ts
@@ -2585,13 +2703,13 @@ function serializeConstValue(v) {
2585
2703
  if (typeof v === "number") return numberLiteral(v);
2586
2704
  return booleanLiteral(v);
2587
2705
  }
2588
- function serializeInstrs(proto, names) {
2706
+ function serializeInstrs(proto) {
2589
2707
  const fields = proto.code.map((instr) => positionalField(
2590
2708
  table([
2591
- namedField(names.op, numberLiteral(instr.op)),
2592
- namedField(names.a, numberLiteral(instr.a)),
2593
- namedField(names.b, numberLiteral(instr.b)),
2594
- namedField(names.c, numberLiteral(instr.c))
2709
+ positionalField(vmNumberLiteral(instr.op)),
2710
+ positionalField(vmNumberLiteral(instr.a)),
2711
+ positionalField(vmNumberLiteral(instr.b)),
2712
+ positionalField(vmNumberLiteral(instr.c))
2595
2713
  ])
2596
2714
  ));
2597
2715
  return table(fields);
@@ -2600,17 +2718,17 @@ function serializeUpvalDescs(proto, names) {
2600
2718
  const fields = proto.upvalDescs.map((d) => positionalField(
2601
2719
  table([
2602
2720
  namedField(names.kind, stringLiteral(d.kind)),
2603
- namedField(names.index, numberLiteral(d.index))
2721
+ namedField(names.index, vmNumberLiteral(d.index))
2604
2722
  ])
2605
2723
  ));
2606
2724
  return table(fields);
2607
2725
  }
2608
2726
  function serializeProto(proto, names) {
2609
2727
  const fields = [
2610
- namedField(names.numParams, numberLiteral(proto.numParams)),
2728
+ namedField(names.numParams, vmNumberLiteral(proto.numParams)),
2611
2729
  namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
2612
- namedField(names.maxRegs, numberLiteral(proto.maxRegs)),
2613
- namedField(names.code, serializeInstrs(proto, names)),
2730
+ namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
2731
+ namedField(names.code, serializeInstrs(proto)),
2614
2732
  namedField(names.consts, serializeConsts(proto.consts)),
2615
2733
  namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
2616
2734
  namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names)))))
@@ -2627,7 +2745,7 @@ local ${N.handlers} = {}
2627
2745
 
2628
2746
  local function ${N.dispatch}(${N.frame}, pc)
2629
2747
  local instr = ${N.frame}.${N.code}[pc]
2630
- return ${N.handlers}[instr.${N.op}](${N.frame}, pc, instr)
2748
+ return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
2631
2749
  end
2632
2750
 
2633
2751
  local function ${N.rk}(${N.frame}, x)
@@ -2640,156 +2758,156 @@ end
2640
2758
 
2641
2759
  -- MOVE
2642
2760
  ${N.handlers}[${op(0 /* MOVE */)}] = function(${N.frame}, pc, instr)
2643
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.R}[instr.${N.b}]
2761
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
2644
2762
  return ${N.dispatch}(${N.frame}, pc + 1)
2645
2763
  end
2646
2764
 
2647
2765
  -- LOADK
2648
2766
  ${N.handlers}[${op(1 /* LOADK */)}] = function(${N.frame}, pc, instr)
2649
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.K}[instr.${N.b} + 1]
2767
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
2650
2768
  return ${N.dispatch}(${N.frame}, pc + 1)
2651
2769
  end
2652
2770
 
2653
2771
  -- LOADBOOL
2654
2772
  ${N.handlers}[${op(2 /* LOADBOOL */)}] = function(${N.frame}, pc, instr)
2655
- ${N.frame}.${N.R}[instr.${N.a}] = (instr.${N.b} ~= 0)
2773
+ ${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
2656
2774
  return ${N.dispatch}(${N.frame}, pc + 1)
2657
2775
  end
2658
2776
 
2659
2777
  -- LOADNIL
2660
2778
  ${N.handlers}[${op(3 /* LOADNIL */)}] = function(${N.frame}, pc, instr)
2661
- for i = instr.${N.a}, instr.${N.b} do ${N.frame}.${N.R}[i] = nil end
2779
+ for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
2662
2780
  return ${N.dispatch}(${N.frame}, pc + 1)
2663
2781
  end
2664
2782
 
2665
2783
  -- GETUPVAL
2666
2784
  ${N.handlers}[${op(4 /* GETUPVAL */)}] = function(${N.frame}, pc, instr)
2667
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.upvals}[instr.${N.b} + 1]
2785
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
2668
2786
  return ${N.dispatch}(${N.frame}, pc + 1)
2669
2787
  end
2670
2788
 
2671
2789
  -- SETUPVAL
2672
2790
  ${N.handlers}[${op(5 /* SETUPVAL */)}] = function(${N.frame}, pc, instr)
2673
- ${N.frame}.${N.upvals}[instr.${N.b} + 1] = ${N.frame}.${N.R}[instr.${N.a}]
2791
+ ${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
2674
2792
  return ${N.dispatch}(${N.frame}, pc + 1)
2675
2793
  end
2676
2794
 
2677
2795
  -- GETGLOBAL (b\uB294 RK\uAC00 \uC544\uB2C8\uB77C \uC0C1\uC218\uD480 \uC21C\uC218 \uC778\uB371\uC2A4)
2678
2796
  ${N.handlers}[${op(6 /* GETGLOBAL */)}] = function(${N.frame}, pc, instr)
2679
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.globals}[${N.frame}.${N.K}[instr.${N.b} + 1]]
2797
+ ${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
2680
2798
  return ${N.dispatch}(${N.frame}, pc + 1)
2681
2799
  end
2682
2800
 
2683
2801
  -- SETGLOBAL
2684
2802
  ${N.handlers}[${op(7 /* SETGLOBAL */)}] = function(${N.frame}, pc, instr)
2685
- ${N.globals}[${N.frame}.${N.K}[instr.${N.b} + 1]] = ${N.frame}.${N.R}[instr.${N.a}]
2803
+ ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
2686
2804
  return ${N.dispatch}(${N.frame}, pc + 1)
2687
2805
  end
2688
2806
 
2689
2807
  -- GETTABLE
2690
2808
  ${N.handlers}[${op(8 /* GETTABLE */)}] = function(${N.frame}, pc, instr)
2691
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.frame}.${N.R}[instr.${N.b}][${N.rk}(${N.frame}, instr.${N.c})]
2809
+ ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
2692
2810
  return ${N.dispatch}(${N.frame}, pc + 1)
2693
2811
  end
2694
2812
 
2695
2813
  -- SETTABLE
2696
2814
  ${N.handlers}[${op(9 /* SETTABLE */)}] = 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
 
2701
2819
  -- NEWTABLE
2702
2820
  ${N.handlers}[${op(10 /* NEWTABLE */)}] = function(${N.frame}, pc, instr)
2703
- ${N.frame}.${N.R}[instr.${N.a}] = {}
2821
+ ${N.frame}.${N.R}[instr[2]] = {}
2704
2822
  return ${N.dispatch}(${N.frame}, pc + 1)
2705
2823
  end
2706
2824
 
2707
2825
  -- SELF (R[a] = R[b][key]; R[a+1] = R[b])
2708
2826
  ${N.handlers}[${op(11 /* SELF */)}] = function(${N.frame}, pc, instr)
2709
- local obj = ${N.frame}.${N.R}[instr.${N.b}]
2710
- ${N.frame}.${N.R}[instr.${N.a}] = obj[${N.rk}(${N.frame}, instr.${N.c})]
2711
- ${N.frame}.${N.R}[instr.${N.a} + 1] = obj
2827
+ local obj = ${N.frame}.${N.R}[instr[3]]
2828
+ ${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
2829
+ ${N.frame}.${N.R}[instr[2] + 1] = obj
2712
2830
  return ${N.dispatch}(${N.frame}, pc + 1)
2713
2831
  end
2714
2832
 
2715
2833
  -- ADD SUB MUL DIV MOD POW
2716
2834
  ${N.handlers}[${op(12 /* ADD */)}] = function(${N.frame}, pc, instr)
2717
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) + ${N.rk}(${N.frame}, instr.${N.c})
2835
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
2718
2836
  return ${N.dispatch}(${N.frame}, pc + 1)
2719
2837
  end
2720
2838
  ${N.handlers}[${op(13 /* SUB */)}] = 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
  ${N.handlers}[${op(14 /* MUL */)}] = function(${N.frame}, pc, instr)
2725
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) * ${N.rk}(${N.frame}, instr.${N.c})
2843
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
2726
2844
  return ${N.dispatch}(${N.frame}, pc + 1)
2727
2845
  end
2728
2846
  ${N.handlers}[${op(15 /* DIV */)}] = function(${N.frame}, pc, instr)
2729
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) / ${N.rk}(${N.frame}, instr.${N.c})
2847
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
2730
2848
  return ${N.dispatch}(${N.frame}, pc + 1)
2731
2849
  end
2732
2850
  ${N.handlers}[${op(16 /* MOD */)}] = function(${N.frame}, pc, instr)
2733
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) % ${N.rk}(${N.frame}, instr.${N.c})
2851
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
2734
2852
  return ${N.dispatch}(${N.frame}, pc + 1)
2735
2853
  end
2736
2854
  ${N.handlers}[${op(17 /* POW */)}] = function(${N.frame}, pc, instr)
2737
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) ^ ${N.rk}(${N.frame}, instr.${N.c})
2855
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
2738
2856
  return ${N.dispatch}(${N.frame}, pc + 1)
2739
2857
  end
2740
2858
 
2741
2859
  -- IDIV (Luau \uBC14\uB2E5 \uB098\uB217\uC148)
2742
2860
  ${N.handlers}[${op(34 /* IDIV */)}] = function(${N.frame}, pc, instr)
2743
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) // ${N.rk}(${N.frame}, instr.${N.c})
2861
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
2744
2862
  return ${N.dispatch}(${N.frame}, pc + 1)
2745
2863
  end
2746
2864
 
2747
2865
  -- CONCAT
2748
2866
  ${N.handlers}[${op(18 /* CONCAT */)}] = function(${N.frame}, pc, instr)
2749
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) .. ${N.rk}(${N.frame}, instr.${N.c})
2867
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
2750
2868
  return ${N.dispatch}(${N.frame}, pc + 1)
2751
2869
  end
2752
2870
 
2753
2871
  -- UNM
2754
2872
  ${N.handlers}[${op(19 /* UNM */)}] = function(${N.frame}, pc, instr)
2755
- ${N.frame}.${N.R}[instr.${N.a}] = -${N.frame}.${N.R}[instr.${N.b}]
2873
+ ${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
2756
2874
  return ${N.dispatch}(${N.frame}, pc + 1)
2757
2875
  end
2758
2876
 
2759
2877
  -- NOT
2760
2878
  ${N.handlers}[${op(20 /* NOT */)}] = function(${N.frame}, pc, instr)
2761
- ${N.frame}.${N.R}[instr.${N.a}] = not ${N.frame}.${N.R}[instr.${N.b}]
2879
+ ${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
2762
2880
  return ${N.dispatch}(${N.frame}, pc + 1)
2763
2881
  end
2764
2882
 
2765
2883
  -- LEN
2766
2884
  ${N.handlers}[${op(21 /* LEN */)}] = function(${N.frame}, pc, instr)
2767
- ${N.frame}.${N.R}[instr.${N.a}] = #${N.frame}.${N.R}[instr.${N.b}]
2885
+ ${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
2768
2886
  return ${N.dispatch}(${N.frame}, pc + 1)
2769
2887
  end
2770
2888
 
2771
2889
  -- JMP (pc += a)
2772
2890
  ${N.handlers}[${op(22 /* JMP */)}] = function(${N.frame}, pc, instr)
2773
- return ${N.dispatch}(${N.frame}, pc + instr.${N.a})
2891
+ return ${N.dispatch}(${N.frame}, pc + instr[2])
2774
2892
  end
2775
2893
 
2776
2894
  -- EQ LT LE (\uAC12 \uC0DD\uC131\uD615 \uBE44\uAD50)
2777
2895
  ${N.handlers}[${op(23 /* EQ */)}] = function(${N.frame}, pc, instr)
2778
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) == ${N.rk}(${N.frame}, instr.${N.c})
2896
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
2779
2897
  return ${N.dispatch}(${N.frame}, pc + 1)
2780
2898
  end
2781
2899
  ${N.handlers}[${op(24 /* LT */)}] = function(${N.frame}, pc, instr)
2782
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) < ${N.rk}(${N.frame}, instr.${N.c})
2900
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
2783
2901
  return ${N.dispatch}(${N.frame}, pc + 1)
2784
2902
  end
2785
2903
  ${N.handlers}[${op(25 /* LE */)}] = function(${N.frame}, pc, instr)
2786
- ${N.frame}.${N.R}[instr.${N.a}] = ${N.rk}(${N.frame}, instr.${N.b}) <= ${N.rk}(${N.frame}, instr.${N.c})
2904
+ ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
2787
2905
  return ${N.dispatch}(${N.frame}, pc + 1)
2788
2906
  end
2789
2907
 
2790
2908
  -- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 \u2014 \uBCF4\uD1B5 \uB2E4\uC74C \uBA85\uB839\uC740 JMP)
2791
2909
  ${N.handlers}[${op(26 /* TEST */)}] = function(${N.frame}, pc, instr)
2792
- if (not ${N.frame}.${N.R}[instr.${N.a}]) ~= (instr.${N.c} ~= 0) then
2910
+ if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
2793
2911
  return ${N.dispatch}(${N.frame}, pc + 2)
2794
2912
  end
2795
2913
  return ${N.dispatch}(${N.frame}, pc + 1)
@@ -2797,7 +2915,7 @@ end
2797
2915
 
2798
2916
  -- CALL
2799
2917
  ${N.handlers}[${op(27 /* CALL */)}] = function(${N.frame}, pc, instr)
2800
- local a, b, c = instr.${N.a}, instr.${N.b}, instr.${N.c}
2918
+ local a, b, c = instr[2], instr[3], instr[4]
2801
2919
  local fn = ${N.frame}.${N.R}[a]
2802
2920
  local nargs
2803
2921
  if b == 0 then
@@ -2822,7 +2940,7 @@ end
2822
2940
 
2823
2941
  -- RETURN
2824
2942
  ${N.handlers}[${op(28 /* RETURN */)}] = function(${N.frame}, pc, instr)
2825
- local a, b = instr.${N.a}, instr.${N.b}
2943
+ local a, b = instr[2], instr[3]
2826
2944
  if b == 0 then
2827
2945
  return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
2828
2946
  end
@@ -2831,14 +2949,14 @@ end
2831
2949
 
2832
2950
  -- FORPREP
2833
2951
  ${N.handlers}[${op(29 /* FORPREP */)}] = function(${N.frame}, pc, instr)
2834
- local a = instr.${N.a}
2952
+ local a = instr[2]
2835
2953
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
2836
- return ${N.dispatch}(${N.frame}, pc + instr.${N.b})
2954
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2837
2955
  end
2838
2956
 
2839
2957
  -- FORLOOP
2840
2958
  ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2841
- local a = instr.${N.a}
2959
+ local a = instr[2]
2842
2960
  local step = ${N.frame}.${N.R}[a + 2]
2843
2961
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] + step
2844
2962
  local ok
@@ -2849,26 +2967,26 @@ ${N.handlers}[${op(30 /* FORLOOP */)}] = function(${N.frame}, pc, instr)
2849
2967
  end
2850
2968
  if ok then
2851
2969
  ${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
2852
- return ${N.dispatch}(${N.frame}, pc + instr.${N.b})
2970
+ return ${N.dispatch}(${N.frame}, pc + instr[3])
2853
2971
  end
2854
2972
  return ${N.dispatch}(${N.frame}, pc + 1)
2855
2973
  end
2856
2974
 
2857
2975
  -- CLOSURE
2858
2976
  ${N.handlers}[${op(31 /* CLOSURE */)}] = function(${N.frame}, pc, instr)
2859
- local childProto = ${N.frame}.${N.protos}[instr.${N.b} + 1]
2977
+ local childProto = ${N.frame}.${N.protos}[instr[3] + 1]
2860
2978
  local n = #childProto.${N.upvalDescs}
2861
2979
  local capturedUpvals = table.create(n)
2862
2980
  for i = 1, n do
2863
2981
  local desc = childProto.${N.upvalDescs}[i]
2864
2982
  local captureInstr = ${N.frame}.${N.code}[pc + i]
2865
2983
  if desc.${N.kind} == "local" then
2866
- capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr.${N.b}]
2984
+ capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr[3]]
2867
2985
  else
2868
- capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr.${N.b} + 1]
2986
+ capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr[3] + 1]
2869
2987
  end
2870
2988
  end
2871
- ${N.frame}.${N.R}[instr.${N.a}] = function(...)
2989
+ ${N.frame}.${N.R}[instr[2]] = function(...)
2872
2990
  return ${N.execute}(childProto, capturedUpvals, ...)
2873
2991
  end
2874
2992
  return ${N.dispatch}(${N.frame}, pc + 1 + n)
@@ -2876,22 +2994,22 @@ end
2876
2994
 
2877
2995
  -- VARARG
2878
2996
  ${N.handlers}[${op(32 /* VARARG */)}] = function(${N.frame}, pc, instr)
2879
- local count = instr.${N.b} == 0 and ${N.frame}.${N.nVarargs} or (instr.${N.b} - 1)
2997
+ local count = instr[3] == 0 and ${N.frame}.${N.nVarargs} or (instr[3] - 1)
2880
2998
  for i = 1, count do
2881
- ${N.frame}.${N.R}[instr.${N.a} + i - 1] = ${N.frame}.${N.varargs}[i]
2999
+ ${N.frame}.${N.R}[instr[2] + i - 1] = ${N.frame}.${N.varargs}[i]
2882
3000
  end
2883
- if instr.${N.b} == 0 then
2884
- ${N.frame}.${N.multiTop} = instr.${N.a} + count
3001
+ if instr[3] == 0 then
3002
+ ${N.frame}.${N.multiTop} = instr[2] + count
2885
3003
  end
2886
3004
  return ${N.dispatch}(${N.frame}, pc + 1)
2887
3005
  end
2888
3006
 
2889
3007
  -- SETLIST
2890
3008
  ${N.handlers}[${op(33 /* SETLIST */)}] = function(${N.frame}, pc, instr)
2891
- local tbl = ${N.frame}.${N.R}[instr.${N.a}]
2892
- local valuesBase = instr.${N.b}
3009
+ local tbl = ${N.frame}.${N.R}[instr[2]]
3010
+ local valuesBase = instr[3]
2893
3011
  local top = ${N.frame}.${N.multiTop} or (valuesBase + 1)
2894
- local startIdx = instr.${N.c}
3012
+ local startIdx = instr[4]
2895
3013
  local n = top - valuesBase
2896
3014
  for i = 0, n - 1 do
2897
3015
  tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
@@ -2965,10 +3083,6 @@ function generateVmNames(random = Math.random) {
2965
3083
  varargs: id(),
2966
3084
  nVarargs: id(),
2967
3085
  multiTop: id(),
2968
- op: id(),
2969
- a: id(),
2970
- b: id(),
2971
- c: id(),
2972
3086
  numParams: id(),
2973
3087
  hasVarargs: id(),
2974
3088
  maxRegs: id(),
@@ -2980,6 +3094,12 @@ function generateVmNames(random = Math.random) {
2980
3094
  }
2981
3095
 
2982
3096
  // src/passes/Vmify.ts
3097
+ function markRuntimeNumbersAsStructural(body) {
3098
+ transformExpressions({ body }, (expr) => {
3099
+ if (expr.type === "NumberLiteral") vmStructuralNumbers.add(expr);
3100
+ return void 0;
3101
+ });
3102
+ }
2983
3103
  function runVmify(program, options) {
2984
3104
  const globalNames = options.builtinGlobals ?? DEFAULT_BUILTIN_GLOBALS;
2985
3105
  const random = options.random ?? Math.random;
@@ -2993,6 +3113,7 @@ function runVmify(program, options) {
2993
3113
  const runtimeSource = buildVmRuntimeSource(names, opcodeMap);
2994
3114
  const runtimeProgram = import_luau_parser4.luauparser.parse(runtimeSource);
2995
3115
  const runtimeStatements = runtimeProgram.body.statements;
3116
+ markRuntimeNumbersAsStructural(runtimeProgram.body);
2996
3117
  const protoLiteral = serializeProto(topProto, names);
2997
3118
  const newBody = [
2998
3119
  localStatement(names.globals, globalsTable),
@@ -3007,10 +3128,10 @@ function runVmify(program, options) {
3007
3128
  var PASS_ORDER = [
3008
3129
  "Vmify",
3009
3130
  "GlobalMapping",
3010
- "StringsToExpressions",
3011
- "NumbersToExpressions",
3012
3131
  "RenameVariables",
3013
3132
  "ConstantArray",
3133
+ "StringsToExpressions",
3134
+ "NumbersToExpressions",
3014
3135
  "EncryptStrings",
3015
3136
  "EncryptNumbers",
3016
3137
  "InsertJunk",