luau-obfuscator 1.0.0 → 1.0.1
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/.github/workflows/release.yml +56 -56
- package/dist/index.cjs +2014 -44
- package/dist/index.d.cts +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +2014 -44
- package/generated/final.luau +63004 -817
- package/generated/smoketest.luau +24339 -0
- package/package.json +2 -2
- package/scripts/example.luau +1174 -1174
- package/scripts/smoketest.luau +115 -0
- package/scripts/test.js +16 -8
- package/src/config.ts +2 -1
- package/src/passes/ConstantArray.ts +89 -89
- package/src/passes/EncryptNumbers.ts +121 -65
- package/src/passes/EncryptStrings.ts +162 -82
- package/src/passes/GlobalMapping.ts +193 -193
- package/src/passes/InsertJunk.ts +184 -184
- package/src/passes/Minify.ts +5 -5
- package/src/passes/NumbersToExpressions.ts +180 -2
- package/src/passes/RenameVariables.ts +2 -9
- package/src/passes/StringsToExpressions.ts +167 -2
- package/src/passes/StripTypes.ts +191 -191
- package/src/passes/Vmify.ts +73 -0
- package/src/passes/WrapInFunction.ts +31 -31
- package/src/passes/nodeFactory.ts +17 -1
- package/src/passes/vmify/chunk.ts +50 -0
- package/src/passes/vmify/compiler.ts +1026 -0
- package/src/passes/vmify/names.ts +78 -0
- package/src/passes/vmify/opcodes.ts +80 -0
- package/src/passes/vmify/registers.ts +63 -0
- package/src/passes/vmify/runtime.ts +334 -0
- package/src/passes/vmify/scope-walk.ts +175 -0
- package/src/passes/vmify/serialize.ts +68 -0
- package/src/pipeline.ts +3 -1
- package/tsconfig.json +20 -20
- package/tsup.config.ts +9 -9
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Program, Block, Statement, Expression, FunctionBody, Identifier,
|
|
3
|
+
} from "luau-parser"
|
|
4
|
+
|
|
5
|
+
/** 함수 하나(최상위 프로그램 포함)를 가리키는 식별용 토큰. 객체 identity만 씀. */
|
|
6
|
+
export type FuncMarker = { readonly tag: "top" } | { readonly tag: "fn"; readonly node: FunctionBody }
|
|
7
|
+
|
|
8
|
+
export interface EnclosingFunctionMap {
|
|
9
|
+
/** 식별자 사용/선언 노드 하나가 어느 함수 안에 있는지. */
|
|
10
|
+
ownerOf: Map<object, FuncMarker>
|
|
11
|
+
/** 함수 마커 -> 그 함수를 감싸는 바깥 함수 마커(최상위는 undefined). */
|
|
12
|
+
parentOf: Map<FuncMarker, FuncMarker | undefined>
|
|
13
|
+
topMarker: FuncMarker
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* analyzeScopes()가 주는 건 "이 식별자가 어떤 binding을 가리키는가"까지고,
|
|
18
|
+
* "그 식별자가 물리적으로 어느 함수 몸통 안에 있는가"는 안 알려주기 때문에
|
|
19
|
+
* upvalue 캡처를 판단하려면 이 정보를 직접 한 번 더 walk해서 구해야 한다.
|
|
20
|
+
* (binding 선언 위치의 함수 != 어떤 참조 위치의 함수 라면 그 참조는 upvalue를 필요로 함)
|
|
21
|
+
*/
|
|
22
|
+
export function buildEnclosingFunctionMap(program: Program): EnclosingFunctionMap {
|
|
23
|
+
const ownerOf = new Map<object, FuncMarker>()
|
|
24
|
+
const parentOf = new Map<FuncMarker, FuncMarker | undefined>()
|
|
25
|
+
const topMarker: FuncMarker = { tag: "top" }
|
|
26
|
+
parentOf.set(topMarker, undefined)
|
|
27
|
+
|
|
28
|
+
function mark(node: object, fn: FuncMarker): void {
|
|
29
|
+
ownerOf.set(node, fn)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function walkBlock(block: Block, fn: FuncMarker): void {
|
|
33
|
+
for (const stmt of block.statements) walkStatement(stmt, fn)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function walkFunctionBody(fb: FunctionBody, parent: FuncMarker): void {
|
|
37
|
+
const self: FuncMarker = { tag: "fn", node: fb }
|
|
38
|
+
parentOf.set(self, parent)
|
|
39
|
+
mark(fb, self)
|
|
40
|
+
for (const p of fb.params) mark(p, self)
|
|
41
|
+
walkBlock(fb.body, self)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function walkExpr(expr: Expression, fn: FuncMarker): void {
|
|
45
|
+
mark(expr, fn)
|
|
46
|
+
switch (expr.type) {
|
|
47
|
+
case "Identifier":
|
|
48
|
+
mark(expr, fn)
|
|
49
|
+
break
|
|
50
|
+
case "InterpolatedStringExpression":
|
|
51
|
+
for (const part of expr.parts) if (part.kind === "expression") walkExpr(part.expression, fn)
|
|
52
|
+
break
|
|
53
|
+
case "FunctionExpression":
|
|
54
|
+
walkFunctionBody(expr.func, fn)
|
|
55
|
+
break
|
|
56
|
+
case "TableExpression":
|
|
57
|
+
for (const field of expr.fields) {
|
|
58
|
+
if (field.type === "TableFieldPositional") walkExpr(field.value, fn)
|
|
59
|
+
else if (field.type === "TableFieldNamed") walkExpr(field.value, fn)
|
|
60
|
+
else {
|
|
61
|
+
walkExpr(field.key, fn)
|
|
62
|
+
walkExpr(field.value, fn)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
break
|
|
66
|
+
case "BinaryExpression":
|
|
67
|
+
walkExpr(expr.left, fn)
|
|
68
|
+
walkExpr(expr.right, fn)
|
|
69
|
+
break
|
|
70
|
+
case "UnaryExpression":
|
|
71
|
+
walkExpr(expr.argument, fn)
|
|
72
|
+
break
|
|
73
|
+
case "MemberExpression":
|
|
74
|
+
walkExpr(expr.object, fn)
|
|
75
|
+
break
|
|
76
|
+
case "IndexExpression":
|
|
77
|
+
walkExpr(expr.object, fn)
|
|
78
|
+
walkExpr(expr.index, fn)
|
|
79
|
+
break
|
|
80
|
+
case "CallExpression":
|
|
81
|
+
walkExpr(expr.callee, fn)
|
|
82
|
+
for (const a of expr.arguments) walkExpr(a, fn)
|
|
83
|
+
break
|
|
84
|
+
case "MethodCallExpression":
|
|
85
|
+
walkExpr(expr.object, fn)
|
|
86
|
+
for (const a of expr.arguments) walkExpr(a, fn)
|
|
87
|
+
break
|
|
88
|
+
case "ParenthesizedExpression":
|
|
89
|
+
case "TypeAssertionExpression":
|
|
90
|
+
walkExpr(expr.expression, fn)
|
|
91
|
+
break
|
|
92
|
+
case "IfElseExpression":
|
|
93
|
+
for (const c of expr.clauses) {
|
|
94
|
+
walkExpr(c.condition, fn)
|
|
95
|
+
walkExpr(c.body, fn)
|
|
96
|
+
}
|
|
97
|
+
walkExpr(expr.alternate, fn)
|
|
98
|
+
break
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function walkStatement(stmt: Statement, fn: FuncMarker): void {
|
|
103
|
+
mark(stmt, fn)
|
|
104
|
+
switch (stmt.type) {
|
|
105
|
+
case "LocalStatement":
|
|
106
|
+
for (const n of stmt.names) mark(n, fn)
|
|
107
|
+
for (const e of stmt.init) walkExpr(e, fn)
|
|
108
|
+
return
|
|
109
|
+
case "LocalFunctionStatement":
|
|
110
|
+
mark(stmt.name, fn)
|
|
111
|
+
walkFunctionBody(stmt.func, fn)
|
|
112
|
+
return
|
|
113
|
+
case "FunctionDeclarationStatement":
|
|
114
|
+
mark(stmt.target.base, fn)
|
|
115
|
+
walkFunctionBody(stmt.func, fn)
|
|
116
|
+
return
|
|
117
|
+
case "AssignmentStatement":
|
|
118
|
+
for (const t of stmt.targets) walkExpr(t, fn)
|
|
119
|
+
for (const v of stmt.values) walkExpr(v, fn)
|
|
120
|
+
return
|
|
121
|
+
case "CompoundAssignmentStatement":
|
|
122
|
+
walkExpr(stmt.target, fn)
|
|
123
|
+
walkExpr(stmt.value, fn)
|
|
124
|
+
return
|
|
125
|
+
case "CallStatement":
|
|
126
|
+
walkExpr(stmt.expression, fn)
|
|
127
|
+
return
|
|
128
|
+
case "DoStatement":
|
|
129
|
+
walkBlock(stmt.body, fn)
|
|
130
|
+
return
|
|
131
|
+
case "WhileStatement":
|
|
132
|
+
walkExpr(stmt.condition, fn)
|
|
133
|
+
walkBlock(stmt.body, fn)
|
|
134
|
+
return
|
|
135
|
+
case "RepeatStatement":
|
|
136
|
+
walkBlock(stmt.body, fn)
|
|
137
|
+
walkExpr(stmt.condition, fn)
|
|
138
|
+
return
|
|
139
|
+
case "IfStatement":
|
|
140
|
+
for (const c of stmt.clauses) {
|
|
141
|
+
walkExpr(c.condition, fn)
|
|
142
|
+
walkBlock(c.body, fn)
|
|
143
|
+
}
|
|
144
|
+
if (stmt.alternate) walkBlock(stmt.alternate, fn)
|
|
145
|
+
return
|
|
146
|
+
case "NumericForStatement":
|
|
147
|
+
mark(stmt.variable, fn)
|
|
148
|
+
walkExpr(stmt.start, fn)
|
|
149
|
+
walkExpr(stmt.end, fn)
|
|
150
|
+
if (stmt.step) walkExpr(stmt.step, fn)
|
|
151
|
+
walkBlock(stmt.body, fn)
|
|
152
|
+
return
|
|
153
|
+
case "GenericForStatement":
|
|
154
|
+
for (const v of stmt.variables) mark(v, fn)
|
|
155
|
+
for (const it of stmt.iterators) walkExpr(it, fn)
|
|
156
|
+
walkBlock(stmt.body, fn)
|
|
157
|
+
return
|
|
158
|
+
case "ReturnStatement":
|
|
159
|
+
for (const a of stmt.arguments) walkExpr(a, fn)
|
|
160
|
+
return
|
|
161
|
+
default:
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
walkBlock(program.body, topMarker)
|
|
167
|
+
return { ownerOf, parentOf, topMarker }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Identifier 참조 하나가 속한 함수를 찾는다(선언/참조 둘 다 이 맵에 들어있음). */
|
|
171
|
+
export function ownerFunctionOf(map: EnclosingFunctionMap, node: object): FuncMarker {
|
|
172
|
+
const fn = map.ownerOf.get(node)
|
|
173
|
+
if (!fn) throw new Error("scope-walk: node was not visited — walker/compiler are out of sync")
|
|
174
|
+
return fn
|
|
175
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Expression, TableField } from "luau-parser"
|
|
2
|
+
import type { Proto, ConstValue } from "./chunk"
|
|
3
|
+
import { Opcode } from "./opcodes"
|
|
4
|
+
import type { VmNames } from "./names"
|
|
5
|
+
import {
|
|
6
|
+
table, positionalField, namedField, numberLiteral, stringLiteral, booleanLiteral, nilLiteral,
|
|
7
|
+
} from "../nodeFactory"
|
|
8
|
+
|
|
9
|
+
/** proto.consts[]를 Luau 배열 리터럴로: { [1] = v1, [2] = v2, ... } (1-based는 그대로 유지) */
|
|
10
|
+
function serializeConsts(consts: ConstValue[]): Expression {
|
|
11
|
+
const fields: TableField[] = consts.map((v) => positionalField(serializeConstValue(v)))
|
|
12
|
+
return table(fields)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function serializeConstValue(v: ConstValue): Expression {
|
|
16
|
+
if (v === null) return nilLiteral()
|
|
17
|
+
if (typeof v === "string") return stringLiteral(v)
|
|
18
|
+
if (typeof v === "number") return numberLiteral(v)
|
|
19
|
+
return booleanLiteral(v)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Instr/Proto를 Luau 테이블 리터럴로 찍을 때 쓰는 필드 키는 고정 문자열("op","a","b","c",
|
|
24
|
+
* "numParams", ...)이 아니라 names(names.ts)에서 빌드마다 새로 뽑은 이름을 쓴다 —
|
|
25
|
+
* 이 키 이름이 고정이면 산출물에서 `{op=6, a=0, ...}` 같은 패턴만 grep해도 VM 청크
|
|
26
|
+
* 테이블을 바로 찾아낼 수 있기 때문. runtime.ts가 같은 names로 instr.<key>를 읽으므로
|
|
27
|
+
* 반드시 컴파일 시점에 쓴 것과 동일한 VmNames 인스턴스를 넘겨야 한다.
|
|
28
|
+
*/
|
|
29
|
+
function serializeInstrs(proto: Proto, names: VmNames): Expression {
|
|
30
|
+
const fields: TableField[] = proto.code.map((instr) =>
|
|
31
|
+
positionalField(
|
|
32
|
+
table([
|
|
33
|
+
namedField(names.op, numberLiteral(instr.op as number)),
|
|
34
|
+
namedField(names.a, numberLiteral(instr.a)),
|
|
35
|
+
namedField(names.b, numberLiteral(instr.b)),
|
|
36
|
+
namedField(names.c, numberLiteral(instr.c)),
|
|
37
|
+
]),
|
|
38
|
+
))
|
|
39
|
+
return table(fields)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function serializeUpvalDescs(proto: Proto, names: VmNames): Expression {
|
|
43
|
+
const fields: TableField[] = proto.upvalDescs.map((d) =>
|
|
44
|
+
positionalField(
|
|
45
|
+
table([
|
|
46
|
+
namedField(names.kind, stringLiteral(d.kind)),
|
|
47
|
+
namedField(names.index, numberLiteral(d.index)),
|
|
48
|
+
]),
|
|
49
|
+
))
|
|
50
|
+
return table(fields)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Proto 하나를 { numParams=.., hasVarargs=.., maxRegs=.., code={...}, consts={...}, upvalDescs={...}, protos={...} }로
|
|
54
|
+
* (필드 키는 전부 names에서 온 무작위 이름). */
|
|
55
|
+
export function serializeProto(proto: Proto, names: VmNames): Expression {
|
|
56
|
+
const fields: TableField[] = [
|
|
57
|
+
namedField(names.numParams, numberLiteral(proto.numParams)),
|
|
58
|
+
namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
|
|
59
|
+
namedField(names.maxRegs, numberLiteral(proto.maxRegs)),
|
|
60
|
+
namedField(names.code, serializeInstrs(proto, names)),
|
|
61
|
+
namedField(names.consts, serializeConsts(proto.consts)),
|
|
62
|
+
namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
|
|
63
|
+
namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names))))),
|
|
64
|
+
]
|
|
65
|
+
return table(fields)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { Opcode }
|
package/src/pipeline.ts
CHANGED
|
@@ -10,10 +10,12 @@ import { runEncryptStrings } from "./passes/EncryptStrings"
|
|
|
10
10
|
import { runEncryptNumbers } from "./passes/EncryptNumbers"
|
|
11
11
|
import { runInsertJunk } from "./passes/InsertJunk"
|
|
12
12
|
import { runWrapInFunction } from "./passes/WrapInFunction"
|
|
13
|
+
import { runVmify } from "./passes/Vmify"
|
|
13
14
|
|
|
14
15
|
type PassFn = (program: Program, options: any) => void
|
|
15
16
|
|
|
16
17
|
export const PASS_ORDER: (keyof ObfuscateConfig)[] = [
|
|
18
|
+
"Vmify",
|
|
17
19
|
"GlobalMapping",
|
|
18
20
|
"StringsToExpressions",
|
|
19
21
|
"NumbersToExpressions",
|
|
@@ -22,7 +24,6 @@ export const PASS_ORDER: (keyof ObfuscateConfig)[] = [
|
|
|
22
24
|
"EncryptStrings",
|
|
23
25
|
"EncryptNumbers",
|
|
24
26
|
"InsertJunk",
|
|
25
|
-
"Vmify",
|
|
26
27
|
"WrapInFunction",
|
|
27
28
|
"Minify",
|
|
28
29
|
]
|
|
@@ -37,6 +38,7 @@ export const PASS_MAP: Partial<Record<keyof ObfuscateConfig, PassFn>> = {
|
|
|
37
38
|
EncryptNumbers: runEncryptNumbers,
|
|
38
39
|
InsertJunk: runInsertJunk,
|
|
39
40
|
WrapInFunction: runWrapInFunction,
|
|
41
|
+
Vmify: runVmify
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
export function runPipeline(program: Program, config: ObfuscateConfig): void {
|
package/tsconfig.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
6
|
-
"declaration": false,
|
|
7
|
-
"declarationMap": false,
|
|
8
|
-
"sourceMap": false,
|
|
9
|
-
"outDir": "./dist",
|
|
10
|
-
"rootDir": "./src",
|
|
11
|
-
"moduleResolution": "bundler",
|
|
12
|
-
"allowSyntheticDefaultImports": true,
|
|
13
|
-
"esModuleInterop": true,
|
|
14
|
-
"forceConsistentCasingInFileNames": true,
|
|
15
|
-
"strict": true,
|
|
16
|
-
"skipLibCheck": true,
|
|
17
|
-
"stripInternal": true,
|
|
18
|
-
},
|
|
19
|
-
"include": ["src/**/*"],
|
|
20
|
-
"exclude": ["node_modules", "dist"]
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
6
|
+
"declaration": false,
|
|
7
|
+
"declarationMap": false,
|
|
8
|
+
"sourceMap": false,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"stripInternal": true,
|
|
18
|
+
},
|
|
19
|
+
"include": ["src/**/*"],
|
|
20
|
+
"exclude": ["node_modules", "dist"]
|
|
21
21
|
}
|
package/tsup.config.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { defineConfig } from "tsup";
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
4
|
-
entry: ["src/index.ts"],
|
|
5
|
-
format: ["esm", "cjs"],
|
|
6
|
-
dts: true,
|
|
7
|
-
clean: true,
|
|
8
|
-
sourcemap: false,
|
|
9
|
-
target: "esnext",
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ["src/index.ts"],
|
|
5
|
+
format: ["esm", "cjs"],
|
|
6
|
+
dts: true,
|
|
7
|
+
clean: true,
|
|
8
|
+
sourcemap: false,
|
|
9
|
+
target: "esnext",
|
|
10
10
|
});
|