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 +246 -125
- package/dist/index.js +246 -125
- package/generated/grammers.luau +13643 -0
- package/generated/tests.luau +5567 -0
- package/package.json +1 -1
- package/scripts/test.js +20 -12
- package/src/passes/ConstantArray.ts +6 -5
- package/src/passes/EncryptNumbers.ts +2 -1
- package/src/passes/EncryptStrings.ts +11 -11
- package/src/passes/NumbersToExpressions.ts +4 -1
- package/src/passes/StringsToExpressions.ts +29 -5
- package/src/passes/Vmify.ts +16 -2
- package/src/passes/nodeFactory.ts +19 -0
- package/src/passes/vmify/chunk.ts +9 -5
- package/src/passes/vmify/names.ts +2 -8
- package/src/passes/vmify/runtime.ts +49 -49
- package/src/passes/vmify/serialize.ts +11 -11
- package/src/passes/walk.ts +141 -43
- package/src/pipeline.ts +3 -6
- package/generated/final.luau +0 -63216
- package/generated/smoketest.luau +0 -24339
- /package/{scripts/example.luau → smoketest/grammers.luau} +0 -0
- /package/{scripts/smoketest.luau → smoketest/tests.luau} +0 -0
package/package.json
CHANGED
package/scripts/test.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import { obfuscate } from '../dist/index.js'
|
|
3
|
+
import path from 'path'
|
|
3
4
|
|
|
4
|
-
const
|
|
5
|
-
const obfuscated = obfuscate(exampleScript, {
|
|
6
|
-
Minify: {active: false},
|
|
7
|
-
|
|
8
|
-
})
|
|
9
|
-
fs.writeFileSync('./generated/final.luau', obfuscated)
|
|
5
|
+
const smokedir = path.join(process.cwd(), "smoketest")
|
|
10
6
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
const GENERATE_DIR = path.join(process.cwd(), "generated")
|
|
8
|
+
|
|
9
|
+
fs.rmSync(GENERATE_DIR, { recursive: true, force: true });
|
|
10
|
+
fs.mkdirSync(GENERATE_DIR, { recursive: true });
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
for(const filename of fs.readdirSync(smokedir)) {
|
|
14
|
+
const content = fs.readFileSync(path.join(smokedir, filename), 'utf-8')
|
|
15
|
+
const timeStart = performance.now()
|
|
16
|
+
const obfuscated = obfuscate(content, {
|
|
17
|
+
Minify: {active: false},
|
|
18
|
+
NumbersToExpressions: {active: false},
|
|
19
|
+
StringsToExpressions: {active: false}
|
|
20
|
+
})
|
|
21
|
+
const timeEnd = performance.now()
|
|
22
|
+
fs.writeFileSync(path.join(GENERATE_DIR, `${filename}`), obfuscated)
|
|
23
|
+
console.log(`obfuscating ${filename} took ${(timeEnd - timeStart) / 1000}s, length: ${content.length} -> ${obfuscated.length}`)
|
|
24
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Program, TableField } from "luau-parser"
|
|
2
2
|
import { transformExpressions } from "./walk"
|
|
3
3
|
import {
|
|
4
|
-
stringLiteral, numberLiteral, index as indexExpr, identifier, table, localStatement,
|
|
4
|
+
stringLiteral, numberLiteral, vmNumberLiteral, index as indexExpr, identifier, table, localStatement,
|
|
5
|
+
vmStructuralNumbers,
|
|
5
6
|
} from "./nodeFactory"
|
|
6
7
|
|
|
7
8
|
export interface ConstantArrayOptions {
|
|
@@ -41,7 +42,7 @@ export function runConstantArray(program: Program, options: ConstantArrayOptions
|
|
|
41
42
|
seen.add(k)
|
|
42
43
|
order.push({ kind: "string", value: expr.value })
|
|
43
44
|
}
|
|
44
|
-
} else if (expr.type === "NumberLiteral") {
|
|
45
|
+
} else if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
|
|
45
46
|
const k = keyOf("number", expr.value)
|
|
46
47
|
if (!seen.has(k)) {
|
|
47
48
|
seen.add(k)
|
|
@@ -70,11 +71,11 @@ export function runConstantArray(program: Program, options: ConstantArrayOptions
|
|
|
70
71
|
transformExpressions(program, (expr) => {
|
|
71
72
|
if (expr.type === "StringLiteral") {
|
|
72
73
|
const idx = indexOf.get(keyOf("string", expr.value))!
|
|
73
|
-
return indexExpr(identifier(arrayName),
|
|
74
|
+
return indexExpr(identifier(arrayName), vmNumberLiteral(idx))
|
|
74
75
|
}
|
|
75
|
-
if (expr.type === "NumberLiteral") {
|
|
76
|
+
if (expr.type === "NumberLiteral" && !vmStructuralNumbers.has(expr)) {
|
|
76
77
|
const idx = indexOf.get(keyOf("number", expr.value))!
|
|
77
|
-
return indexExpr(identifier(arrayName),
|
|
78
|
+
return indexExpr(identifier(arrayName), vmNumberLiteral(idx))
|
|
78
79
|
}
|
|
79
80
|
return undefined
|
|
80
81
|
})
|
|
@@ -3,7 +3,7 @@ import { transformExpressions } from "./walk"
|
|
|
3
3
|
import {
|
|
4
4
|
identifier, numberLiteral, call, member,
|
|
5
5
|
localFunctionStatement, functionParam, functionBody, block, returnStatement,
|
|
6
|
-
localStatement, binary,
|
|
6
|
+
localStatement, binary, vmStructuralNumbers,
|
|
7
7
|
} from "./nodeFactory"
|
|
8
8
|
|
|
9
9
|
export interface EncryptNumbersOptions {}
|
|
@@ -96,6 +96,7 @@ export function runEncryptNumbers(program: Program, _options: EncryptNumbersOpti
|
|
|
96
96
|
|
|
97
97
|
transformExpressions(program, (expr: Expression) => {
|
|
98
98
|
if (expr.type !== "NumberLiteral") return
|
|
99
|
+
if (vmStructuralNumbers.has(expr)) return
|
|
99
100
|
if (!isEncryptable(expr.value)) return
|
|
100
101
|
|
|
101
102
|
used = true
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Program, Expression } from "luau-parser"
|
|
2
2
|
import { transformExpressions } from "./walk"
|
|
3
3
|
import {
|
|
4
|
-
identifier,
|
|
4
|
+
identifier, vmNumberLiteral, call, member, index, table, positionalField,
|
|
5
5
|
localFunctionStatement, functionParam, functionBody, block, localStatement,
|
|
6
6
|
assignmentStatement, numericForStatement, returnStatement, unary, binary,
|
|
7
7
|
} from "./nodeFactory"
|
|
@@ -57,15 +57,15 @@ function obfuscatedNumber(value: number): Expression {
|
|
|
57
57
|
const variant = randomInt(0, 2)
|
|
58
58
|
if (variant === 0) {
|
|
59
59
|
const a = randomInt(0, value)
|
|
60
|
-
return binary("+",
|
|
60
|
+
return binary("+", vmNumberLiteral(a), vmNumberLiteral(value - a))
|
|
61
61
|
}
|
|
62
62
|
if (variant === 1) {
|
|
63
63
|
const a = randomInt(0, 0xFFFFFF)
|
|
64
|
-
return binary("-",
|
|
64
|
+
return binary("-", vmNumberLiteral(value + a), vmNumberLiteral(a))
|
|
65
65
|
}
|
|
66
66
|
const a = randomInt(0, 0xFFFFFF)
|
|
67
67
|
const b = (value ^ a) >>> 0
|
|
68
|
-
return call(member(identifier("bit32"), "bxor"), [
|
|
68
|
+
return call(member(identifier("bit32"), "bxor"), [vmNumberLiteral(a), vmNumberLiteral(b)])
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
@@ -92,17 +92,17 @@ function buildDecoderStatement(name: string) {
|
|
|
92
92
|
|
|
93
93
|
const idxExpr = binary(
|
|
94
94
|
"+",
|
|
95
|
-
binary("%", binary("-", identifier(iVar),
|
|
96
|
-
|
|
95
|
+
binary("%", binary("-", identifier(iVar), vmNumberLiteral(1)), identifier(nKeysVar)),
|
|
96
|
+
vmNumberLiteral(1),
|
|
97
97
|
)
|
|
98
98
|
const keyByte = index(identifier(keysParam), idxExpr)
|
|
99
99
|
const mixed = call(member(identifier("bit32"), "band"), [
|
|
100
|
-
call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar),
|
|
101
|
-
|
|
100
|
+
call(member(identifier("bit32"), "lrotate"), [keyByte, binary("%", identifier(iVar), vmNumberLiteral(8))]),
|
|
101
|
+
vmNumberLiteral(0xFF),
|
|
102
102
|
])
|
|
103
103
|
const posByte = call(member(identifier("bit32"), "band"), [
|
|
104
104
|
binary("*", identifier(iVar), identifier(multParam)),
|
|
105
|
-
|
|
105
|
+
vmNumberLiteral(0xFF),
|
|
106
106
|
])
|
|
107
107
|
const keystream = call(member(identifier("bit32"), "bxor"), [mixed, posByte])
|
|
108
108
|
const finalByte = call(member(identifier("bit32"), "bxor"), [
|
|
@@ -115,7 +115,7 @@ function buildDecoderStatement(name: string) {
|
|
|
115
115
|
localStatement(outVar, table([])),
|
|
116
116
|
numericForStatement(
|
|
117
117
|
iVar,
|
|
118
|
-
|
|
118
|
+
vmNumberLiteral(1),
|
|
119
119
|
unary("#", identifier(dataParam)),
|
|
120
120
|
block([
|
|
121
121
|
assignmentStatement(
|
|
@@ -150,7 +150,7 @@ export function runEncryptStrings(program: Program, _options: EncryptStringsOpti
|
|
|
150
150
|
const plainBytes = toUtf8Bytes(expr.value)
|
|
151
151
|
const cipherBytes = plainBytes.map((b, idx) => b ^ keystreamByte(keys, mult, idx + 1))
|
|
152
152
|
|
|
153
|
-
const dataTable = table(cipherBytes.map((b) => positionalField(
|
|
153
|
+
const dataTable = table(cipherBytes.map((b) => positionalField(vmNumberLiteral(b))))
|
|
154
154
|
const keysTable = table(keys.map((k) => positionalField(obfuscatedNumber(k))))
|
|
155
155
|
const multExpr = obfuscatedNumber(mult)
|
|
156
156
|
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
numberLiteral, binary, paren, identifier, call, index, table, positionalField,
|
|
5
5
|
localStatement, localFunctionStatement, functionParam, functionBody, functionExpression,
|
|
6
6
|
block, assignmentStatement, numericForStatement, whileStatement, ifStatement, ifClause,
|
|
7
|
-
returnStatement, unary,
|
|
7
|
+
returnStatement, unary, vmStructuralNumbers,
|
|
8
8
|
} from "./nodeFactory"
|
|
9
9
|
|
|
10
10
|
export interface NumbersToExpressionsOptions {
|
|
@@ -252,6 +252,9 @@ export function runNumbersToExpressions(program: Program, options: NumbersToExpr
|
|
|
252
252
|
|
|
253
253
|
transformExpressions(program, (expr) => {
|
|
254
254
|
if (expr.type !== "NumberLiteral") return
|
|
255
|
+
// Vmify가 만든 op/a/b/c 같은 내부 메타데이터는 건드리지 않는다
|
|
256
|
+
// (개수가 코드 크기에 비례해 폭증하고, 로직은 이미 Vmify가 구조적으로 숨긴 값).
|
|
257
|
+
if (vmStructuralNumbers.has(expr)) return
|
|
255
258
|
|
|
256
259
|
// 루프/재귀 조립은 정수 배분(합이 정확히 일치)에 의존하므로 정수에만 적용하고,
|
|
257
260
|
// 실수/범위를 벗어난 값은 기존 산술식 위장으로 처리한다.
|
|
@@ -53,13 +53,24 @@ function stringCharCall(bytes: number[]): Expression {
|
|
|
53
53
|
return call(member(identifier("string"), "char"), bytes.map(numberLiteral))
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* 청크를 소스에 보이는 순서 그대로 ".."로 이어붙인다.
|
|
58
|
+
*
|
|
59
|
+
* 예전에는 왼쪽으로만 쌓는 선형 체인(`(((a..b)..c)..d)...`)이었는데, 이러면
|
|
60
|
+
* depth == chunks.length라서 큰 문자열 리터럴(수백만 바이트) 하나만 있어도
|
|
61
|
+
* 표현식 중첩 깊이가 수백만까지 치솟는다. 이 트랜스파일러 자체의 스택뿐 아니라
|
|
62
|
+
* 실제 Luau/Lua 컴파일러도 문법 중첩 레벨에 제한(대략 200선)이 있어서, 그 한계를
|
|
63
|
+
* 넘는 순간 우리 쪽이 아니라 Roblox/Luau 파서에서 컴파일이 실패한다.
|
|
64
|
+
* 그래서 균형 이진 트리로 묶어 depth를 O(log n)으로 낮춘다 — 결합 순서만 바뀔 뿐
|
|
65
|
+
* ".."는 결합법칙이 성립하므로 결과 문자열은 동일하다.
|
|
66
|
+
*/
|
|
57
67
|
function buildConcatChain(chunks: number[][]): Expression {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
function build(lo: number, hi: number): Expression {
|
|
69
|
+
if (hi - lo === 1) return stringCharCall(chunks[lo])
|
|
70
|
+
const mid = lo + Math.floor((hi - lo) / 2)
|
|
71
|
+
return binary("..", build(lo, mid), build(mid, hi))
|
|
61
72
|
}
|
|
62
|
-
return
|
|
73
|
+
return build(0, chunks.length)
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
/** [0, n)의 무작위 순열 (Fisher-Yates) */
|
|
@@ -199,6 +210,14 @@ function pickControlFlowStrategy(): ControlFlowStrategy {
|
|
|
199
210
|
return Math.random() < 0.5 ? "forLoop" : "recursive"
|
|
200
211
|
}
|
|
201
212
|
|
|
213
|
+
// 바이트 단위로 쪼개면 청크 하나당 AST 노드(CallExpression+MemberExpression+인자들)가
|
|
214
|
+
// 하나씩 생긴다. 수 MB짜리 문자열 리터럴(예: 임베드된 데이터 블롭)을 5~10바이트 청크로
|
|
215
|
+
// 쪼개면 수십~수백만 개 노드가 나와서 이 트랜스파일러 자체가 OOM으로 죽는다.
|
|
216
|
+
// 그 크기의 문자열은 바이트 단위로 흩뿌려봐야 어차피 노출 표면이 커서 난독화 효과도
|
|
217
|
+
// 떨어지므로, 청크 개수가 이 상한을 넘으면 통째로 하나의 string.char(...)/원본 리터럴로
|
|
218
|
+
// 두고 건드리지 않는다 (EncryptStrings 등 다른 패스가 필요하면 별도로 암호화하면 됨).
|
|
219
|
+
const MAX_CHUNKS_PER_STRING = 20000
|
|
220
|
+
|
|
202
221
|
export function runStringsToExpressions(program: Program, options: StringsToExpressionsOptions): void {
|
|
203
222
|
const controlFlowProbability = options.controlFlowProbability ?? CONTROL_FLOW_PROBABILITY_DEFAULT
|
|
204
223
|
|
|
@@ -207,6 +226,11 @@ export function runStringsToExpressions(program: Program, options: StringsToExpr
|
|
|
207
226
|
if (expr.value.length === 0) return
|
|
208
227
|
|
|
209
228
|
const bytes = toUtf8Bytes(expr.value)
|
|
229
|
+
|
|
230
|
+
// 너무 큰 문자열은 청크 단위 폭발을 피하기 위해 손대지 않는다.
|
|
231
|
+
const estimatedChunks = Math.ceil(bytes.length / Math.max(1, options.min))
|
|
232
|
+
if (estimatedChunks > MAX_CHUNKS_PER_STRING) return
|
|
233
|
+
|
|
210
234
|
const chunks = splitByteChunks(bytes, options.min, options.max)
|
|
211
235
|
|
|
212
236
|
// 청크가 하나뿐이면 순서를 흩뜨려도 얻는 게 없으니 그냥 원래 방식대로.
|
package/src/passes/Vmify.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Program, Statement } from "luau-parser"
|
|
1
|
+
import type { Program, Statement, Expression } from "luau-parser"
|
|
2
2
|
import { luauparser } from "luau-parser"
|
|
3
3
|
import { VmCompiler, DEFAULT_BUILTIN_GLOBALS } from "./vmify/compiler"
|
|
4
4
|
import { serializeProto } from "./vmify/serialize"
|
|
@@ -7,7 +7,20 @@ import { createOpcodeMap } from "./vmify/opcodes"
|
|
|
7
7
|
import { generateVmNames } from "./vmify/names"
|
|
8
8
|
import {
|
|
9
9
|
localStatement, identifier, call, table, namedField, returnStatement, vararg,
|
|
10
|
+
vmStructuralNumbers,
|
|
10
11
|
} from "./nodeFactory"
|
|
12
|
+
import { transformExpressions } from "./walk"
|
|
13
|
+
|
|
14
|
+
/** 파싱된 서브트리(런타임 인터프리터 소스) 안의 모든 NumberLiteral을 vmStructuralNumbers로
|
|
15
|
+
* 표시한다 — 인터프리터 자신의 +1/-1/패딩 같은 구현 디테일 상수는 사용자 데이터가 아닌데,
|
|
16
|
+
* 수십 개 opcode 핸들러에 반복 등장해서 EncryptNumbers/NumbersToExpressions/ConstantArray가
|
|
17
|
+
* 건드리면 곱셈적으로 부풀어 오른다. */
|
|
18
|
+
function markRuntimeNumbersAsStructural(body: { statements: Statement[] }): void {
|
|
19
|
+
transformExpressions({ body } as Program, (expr) => {
|
|
20
|
+
if (expr.type === "NumberLiteral") vmStructuralNumbers.add(expr)
|
|
21
|
+
return undefined
|
|
22
|
+
})
|
|
23
|
+
}
|
|
11
24
|
|
|
12
25
|
export interface VmifyOptions {
|
|
13
26
|
/**
|
|
@@ -59,6 +72,7 @@ export function runVmify(program: Program, options: VmifyOptions): void {
|
|
|
59
72
|
const runtimeSource = buildVmRuntimeSource(names, opcodeMap)
|
|
60
73
|
const runtimeProgram = luauparser.parse(runtimeSource)
|
|
61
74
|
const runtimeStatements: Statement[] = runtimeProgram.body.statements
|
|
75
|
+
markRuntimeNumbersAsStructural(runtimeProgram.body)
|
|
62
76
|
|
|
63
77
|
const protoLiteral = serializeProto(topProto, names)
|
|
64
78
|
|
|
@@ -70,4 +84,4 @@ export function runVmify(program: Program, options: VmifyOptions): void {
|
|
|
70
84
|
]
|
|
71
85
|
|
|
72
86
|
program.body.statements = newBody
|
|
73
|
-
}
|
|
87
|
+
}
|
|
@@ -27,6 +27,25 @@ export function numberLiteral(value: number): NumberLiteral {
|
|
|
27
27
|
return { type: "NumberLiteral", value, raw: String(value), line: POS, column: POS }
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Vmify가 인스트럭션 op/a/b/c, numParams, maxRegs 같은 "VM 구조 메타데이터"용으로
|
|
32
|
+
* 찍어내는 숫자는 이 표시를 달아 만든다. 이런 값은 원본 소스에 쓰인 상수가 아니라
|
|
33
|
+
* Vmify 자신이 만들어낸, 개수가 코드 크기에 비례해 수천~수만 개까지 불어날 수 있는
|
|
34
|
+
* 내부 값이고, 로직 자체는 이미 Vmify 컴파일로 구조적으로 숨겨져 있다.
|
|
35
|
+
* NumbersToExpressions/EncryptNumbers 같은 뒤쪽 패스가 이 값들까지 하나하나
|
|
36
|
+
* for-loop/재귀 함수/디코더 호출로 부풀리면, 두 패스가 곱셈적으로 상호작용해
|
|
37
|
+
* 출력이 기하급수적으로 커진다 (실측: 4400줄 샘플에서 Vmify+NumbersToExpressions만
|
|
38
|
+
* 켜면 0.9MB가 아니라 7.7MB가 나옴). 그래서 이 표시가 붙은 노드는 뒤쪽 숫자
|
|
39
|
+
* 난독화 패스들이 건드리지 않고 그대로 둔다.
|
|
40
|
+
*/
|
|
41
|
+
export const vmStructuralNumbers = new WeakSet<NumberLiteral>()
|
|
42
|
+
|
|
43
|
+
export function vmNumberLiteral(value: number): NumberLiteral {
|
|
44
|
+
const node = numberLiteral(value)
|
|
45
|
+
vmStructuralNumbers.add(node)
|
|
46
|
+
return node
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
export function binary(
|
|
31
50
|
operator: BinaryExpression["operator"],
|
|
32
51
|
left: Expression,
|
|
@@ -21,6 +21,8 @@ export interface Proto {
|
|
|
21
21
|
maxRegs: number
|
|
22
22
|
code: Instr[]
|
|
23
23
|
consts: ConstValue[]
|
|
24
|
+
/** consts 배열과 별도로 유지하는 value -> index 조회용 인덱스 (interning을 O(1)로). */
|
|
25
|
+
constIndex: Map<ConstValue, number>
|
|
24
26
|
/** 이 함수가 캡처하는 upvalue들이 "부모" 기준 어디서 오는지. */
|
|
25
27
|
upvalDescs: UpvalDesc[]
|
|
26
28
|
/** 중첩 함수 표현식들. CLOSURE 명령의 B는 이 배열의 인덱스. */
|
|
@@ -35,6 +37,7 @@ export function createProto(id: number): Proto {
|
|
|
35
37
|
maxRegs: 0,
|
|
36
38
|
code: [],
|
|
37
39
|
consts: [],
|
|
40
|
+
constIndex: new Map(),
|
|
38
41
|
upvalDescs: [],
|
|
39
42
|
protos: [],
|
|
40
43
|
}
|
|
@@ -42,9 +45,10 @@ export function createProto(id: number): Proto {
|
|
|
42
45
|
|
|
43
46
|
/** 상수 풀에 값 추가(중복 제거) 후 인덱스 반환. */
|
|
44
47
|
export function internConst(proto: Proto, value: ConstValue): number {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
+
const existing = proto.constIndex.get(value)
|
|
49
|
+
if (existing !== undefined) return existing
|
|
48
50
|
proto.consts.push(value)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
const idx = proto.consts.length - 1
|
|
52
|
+
proto.constIndex.set(value, idx)
|
|
53
|
+
return idx
|
|
54
|
+
}
|
|
@@ -31,12 +31,7 @@ export interface VmNames {
|
|
|
31
31
|
varargs: string
|
|
32
32
|
nVarargs: string
|
|
33
33
|
multiTop: string
|
|
34
|
-
//
|
|
35
|
-
op: string
|
|
36
|
-
a: string
|
|
37
|
-
b: string
|
|
38
|
-
c: string
|
|
39
|
-
// Proto 테이블 필드
|
|
34
|
+
// Proto 테이블 필드 (인스트럭션 자체는 이제 [op,a,b,c] 위치 기반 테이블이라 이름이 필요 없음)
|
|
40
35
|
numParams: string
|
|
41
36
|
hasVarargs: string
|
|
42
37
|
maxRegs: string
|
|
@@ -71,8 +66,7 @@ export function generateVmNames(random: () => number = Math.random): VmNames {
|
|
|
71
66
|
frame: id(),
|
|
72
67
|
R: id(), K: id(), code: id(), protos: id(), upvals: id(), varargs: id(),
|
|
73
68
|
nVarargs: id(), multiTop: id(),
|
|
74
|
-
op: id(), a: id(), b: id(), c: id(),
|
|
75
69
|
numParams: id(), hasVarargs: id(), maxRegs: id(), consts: id(), upvalDescs: id(),
|
|
76
70
|
kind: id(), index: id(),
|
|
77
71
|
}
|
|
78
|
-
}
|
|
72
|
+
}
|