luau-obfuscator 1.0.0
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 +57 -0
- package/dist/index.cjs +1097 -0
- package/dist/index.d.cts +79 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +1068 -0
- package/generated/final.luau +1029 -0
- package/package.json +38 -0
- package/scripts/example.luau +1174 -0
- package/scripts/test.js +8 -0
- package/src/config.ts +80 -0
- package/src/index.ts +22 -0
- package/src/passes/ConstantArray.ts +90 -0
- package/src/passes/EncryptNumbers.ts +66 -0
- package/src/passes/EncryptStrings.ts +83 -0
- package/src/passes/GlobalMapping.ts +194 -0
- package/src/passes/InsertJunk.ts +185 -0
- package/src/passes/Minify.ts +6 -0
- package/src/passes/NumbersToExpressions.ts +86 -0
- package/src/passes/RenameVariables.ts +32 -0
- package/src/passes/StringsToExpressions.ts +56 -0
- package/src/passes/StripTypes.ts +192 -0
- package/src/passes/WrapInFunction.ts +32 -0
- package/src/passes/nodeFactory.ts +143 -0
- package/src/passes/walk.ts +173 -0
- package/src/pipeline.ts +58 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Identifier, StringLiteral, NumberLiteral, BinaryExpression, UnaryExpression,
|
|
3
|
+
CallExpression, MemberExpression, IndexExpression,
|
|
4
|
+
ParenthesizedExpression, Expression, TableExpression, TableField,
|
|
5
|
+
LocalStatement, LocalFunctionStatement, FunctionBody, FunctionParameter,
|
|
6
|
+
TypedIdentifier, Block, Statement, AssignmentStatement, NumericForStatement,
|
|
7
|
+
ReturnStatement, DoStatement, IfStatement, IfClause, VarargExpression,
|
|
8
|
+
FunctionExpression,
|
|
9
|
+
} from "luau-parser"
|
|
10
|
+
|
|
11
|
+
/** 합성 노드는 실제 소스 위치가 없으므로 0으로 채움(printer는 구조만 봄). */
|
|
12
|
+
const POS = { start: 0, end: 0 }
|
|
13
|
+
|
|
14
|
+
export function identifier(name: string): Identifier {
|
|
15
|
+
return { type: "Identifier", name, line: POS, column: POS }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function typedIdentifier(name: string): TypedIdentifier {
|
|
19
|
+
return { type: "TypedIdentifier", name, line: POS, column: POS }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function stringLiteral(value: string): StringLiteral {
|
|
23
|
+
return { type: "StringLiteral", value, raw: JSON.stringify(value), line: POS, column: POS }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function numberLiteral(value: number): NumberLiteral {
|
|
27
|
+
return { type: "NumberLiteral", value, raw: String(value), line: POS, column: POS }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function binary(
|
|
31
|
+
operator: BinaryExpression["operator"],
|
|
32
|
+
left: Expression,
|
|
33
|
+
right: Expression,
|
|
34
|
+
): BinaryExpression {
|
|
35
|
+
return { type: "BinaryExpression", operator, left, right, line: POS, column: POS }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function unary(operator: UnaryExpression["operator"], argument: Expression): UnaryExpression {
|
|
39
|
+
return { type: "UnaryExpression", operator, argument, line: POS, column: POS }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function call(callee: Expression, args: Expression[]): CallExpression {
|
|
43
|
+
return { type: "CallExpression", callee, arguments: args, line: POS, column: POS }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function member(object: Expression, property: string): MemberExpression {
|
|
47
|
+
return { type: "MemberExpression", object, property: identifier(property), line: POS, column: POS }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function index(object: Expression, key: Expression): IndexExpression {
|
|
51
|
+
return { type: "IndexExpression", object, index: key, line: POS, column: POS }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function paren(expression: Expression): ParenthesizedExpression {
|
|
55
|
+
return { type: "ParenthesizedExpression", expression, line: POS, column: POS }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function table(fields: TableField[]): TableExpression {
|
|
59
|
+
return { type: "TableExpression", fields, line: POS, column: POS }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function computedField(key: Expression, value: Expression): TableField {
|
|
63
|
+
return { type: "TableFieldComputed", key, value }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function positionalField(value: Expression): TableField {
|
|
67
|
+
return { type: "TableFieldPositional", value }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function localStatement(name: string, init: Expression): LocalStatement {
|
|
71
|
+
return {
|
|
72
|
+
type: "LocalStatement",
|
|
73
|
+
names: [typedIdentifier(name)],
|
|
74
|
+
init: [init],
|
|
75
|
+
line: POS,
|
|
76
|
+
column: POS,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function block(statements: Statement[]): Block {
|
|
81
|
+
return { type: "Block", statements, line: POS, column: POS }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function assignmentStatement(targets: Expression[], values: Expression[]): AssignmentStatement {
|
|
85
|
+
return { type: "AssignmentStatement", targets, values, line: POS, column: POS }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function returnStatement(args: Expression[]): ReturnStatement {
|
|
89
|
+
return { type: "ReturnStatement", arguments: args, line: POS, column: POS }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function numericForStatement(
|
|
93
|
+
variableName: string,
|
|
94
|
+
start: Expression,
|
|
95
|
+
end: Expression,
|
|
96
|
+
body: Block,
|
|
97
|
+
): NumericForStatement {
|
|
98
|
+
return {
|
|
99
|
+
type: "NumericForStatement",
|
|
100
|
+
variable: typedIdentifier(variableName),
|
|
101
|
+
start,
|
|
102
|
+
end,
|
|
103
|
+
body,
|
|
104
|
+
line: POS,
|
|
105
|
+
column: POS,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function functionParam(name: string): FunctionParameter {
|
|
110
|
+
return { type: "FunctionParameter", name, line: POS, column: POS }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function functionBody(params: FunctionParameter[], body: Block, hasVarargs = false): FunctionBody {
|
|
114
|
+
return {
|
|
115
|
+
type: "FunctionBody", generics: [], params, hasVarargs, body, line: POS, column: POS,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function functionExpression(func: FunctionBody): FunctionExpression {
|
|
120
|
+
return { type: "FunctionExpression", func, line: POS, column: POS }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function localFunctionStatement(name: string, func: FunctionBody): LocalFunctionStatement {
|
|
124
|
+
return {
|
|
125
|
+
type: "LocalFunctionStatement", name: identifier(name), func, line: POS, column: POS,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function doStatement(body: Block): DoStatement {
|
|
130
|
+
return { type: "DoStatement", body, line: POS, column: POS }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function ifClause(condition: Expression, body: Block): IfClause {
|
|
134
|
+
return { type: "IfClause", condition, body, line: POS, column: POS }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function ifStatement(clauses: IfClause[], alternate?: Block): IfStatement {
|
|
138
|
+
return { type: "IfStatement", clauses, alternate, line: POS, column: POS }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function vararg(): VarargExpression {
|
|
142
|
+
return { type: "VarargExpression", line: POS, column: POS }
|
|
143
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Program, Block, Statement, Expression, FunctionBody,
|
|
3
|
+
} from "luau-parser"
|
|
4
|
+
|
|
5
|
+
export type ExpressionVisitor = (expr: Expression) => Expression | undefined | void
|
|
6
|
+
|
|
7
|
+
function visitArray(arr: Expression[], visitor: ExpressionVisitor): void {
|
|
8
|
+
for (let i = 0; i < arr.length; i++) {
|
|
9
|
+
arr[i] = mapExpression(arr[i], visitor)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function mapExpression(expr: Expression, visitor: ExpressionVisitor): Expression {
|
|
14
|
+
switch (expr.type) {
|
|
15
|
+
case "InterpolatedStringExpression":
|
|
16
|
+
for (const part of expr.parts) {
|
|
17
|
+
if (part.kind === "expression") {
|
|
18
|
+
part.expression = mapExpression(part.expression, visitor)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
break
|
|
22
|
+
|
|
23
|
+
case "FunctionExpression":
|
|
24
|
+
walkFunctionBody(expr.func, visitor)
|
|
25
|
+
break
|
|
26
|
+
|
|
27
|
+
case "TableExpression":
|
|
28
|
+
for (const field of expr.fields) {
|
|
29
|
+
if (field.type === "TableFieldPositional") {
|
|
30
|
+
field.value = mapExpression(field.value, visitor)
|
|
31
|
+
} else if (field.type === "TableFieldNamed") {
|
|
32
|
+
field.value = mapExpression(field.value, visitor)
|
|
33
|
+
} else if (field.type === "TableFieldComputed") {
|
|
34
|
+
field.key = mapExpression(field.key, visitor)
|
|
35
|
+
field.value = mapExpression(field.value, visitor)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
case "BinaryExpression":
|
|
41
|
+
expr.left = mapExpression(expr.left, visitor)
|
|
42
|
+
expr.right = mapExpression(expr.right, visitor)
|
|
43
|
+
break
|
|
44
|
+
|
|
45
|
+
case "UnaryExpression":
|
|
46
|
+
expr.argument = mapExpression(expr.argument, visitor)
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
case "MemberExpression":
|
|
50
|
+
expr.object = mapExpression(expr.object, visitor)
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
case "IndexExpression":
|
|
54
|
+
expr.object = mapExpression(expr.object, visitor)
|
|
55
|
+
expr.index = mapExpression(expr.index, visitor)
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
case "CallExpression":
|
|
59
|
+
expr.callee = mapExpression(expr.callee, visitor)
|
|
60
|
+
visitArray(expr.arguments, visitor)
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
case "MethodCallExpression":
|
|
64
|
+
expr.object = mapExpression(expr.object, visitor)
|
|
65
|
+
visitArray(expr.arguments, visitor)
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
case "ParenthesizedExpression":
|
|
69
|
+
expr.expression = mapExpression(expr.expression, visitor)
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
case "TypeAssertionExpression":
|
|
73
|
+
expr.expression = mapExpression(expr.expression, visitor)
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
case "IfElseExpression":
|
|
77
|
+
for (const clause of expr.clauses) {
|
|
78
|
+
clause.condition = mapExpression(clause.condition, visitor)
|
|
79
|
+
clause.body = mapExpression(clause.body, visitor)
|
|
80
|
+
}
|
|
81
|
+
expr.alternate = mapExpression(expr.alternate, visitor)
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return visitor(expr) ?? expr
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function walkFunctionBody(func: FunctionBody, visitor: ExpressionVisitor): void {
|
|
89
|
+
walkBlock(func.body, visitor)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function walkBlock(block: Block, visitor: ExpressionVisitor): void {
|
|
93
|
+
for (const stmt of block.statements) walkStatement(stmt, visitor)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function walkStatement(stmt: Statement, visitor: ExpressionVisitor): void {
|
|
97
|
+
switch (stmt.type) {
|
|
98
|
+
case "LocalStatement":
|
|
99
|
+
visitArray(stmt.init, visitor)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
case "LocalFunctionStatement":
|
|
103
|
+
walkFunctionBody(stmt.func, visitor)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
case "FunctionDeclarationStatement":
|
|
107
|
+
walkFunctionBody(stmt.func, visitor)
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
case "AssignmentStatement":
|
|
111
|
+
visitArray(stmt.targets, visitor)
|
|
112
|
+
visitArray(stmt.values, visitor)
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
case "CompoundAssignmentStatement":
|
|
116
|
+
stmt.target = mapExpression(stmt.target, visitor)
|
|
117
|
+
stmt.value = mapExpression(stmt.value, visitor)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
case "CallStatement":
|
|
121
|
+
stmt.expression = mapExpression(stmt.expression, visitor) as typeof stmt.expression
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
case "DoStatement":
|
|
125
|
+
walkBlock(stmt.body, visitor)
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
case "WhileStatement":
|
|
129
|
+
stmt.condition = mapExpression(stmt.condition, visitor)
|
|
130
|
+
walkBlock(stmt.body, visitor)
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
case "RepeatStatement":
|
|
134
|
+
walkBlock(stmt.body, visitor)
|
|
135
|
+
stmt.condition = mapExpression(stmt.condition, visitor)
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
case "IfStatement":
|
|
139
|
+
for (const clause of stmt.clauses) {
|
|
140
|
+
clause.condition = mapExpression(clause.condition, visitor)
|
|
141
|
+
walkBlock(clause.body, visitor)
|
|
142
|
+
}
|
|
143
|
+
if (stmt.alternate) walkBlock(stmt.alternate, visitor)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
case "NumericForStatement":
|
|
147
|
+
stmt.start = mapExpression(stmt.start, visitor)
|
|
148
|
+
stmt.end = mapExpression(stmt.end, visitor)
|
|
149
|
+
if (stmt.step) stmt.step = mapExpression(stmt.step, visitor)
|
|
150
|
+
walkBlock(stmt.body, visitor)
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
case "GenericForStatement":
|
|
154
|
+
visitArray(stmt.iterators, visitor)
|
|
155
|
+
walkBlock(stmt.body, visitor)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
case "ReturnStatement":
|
|
159
|
+
visitArray(stmt.arguments, visitor)
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
case "BreakStatement":
|
|
163
|
+
case "ContinueStatement":
|
|
164
|
+
case "TypeAliasStatement":
|
|
165
|
+
case "ExportTypeAliasStatement":
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Program 전체를 순회하며 모든 Expression 노드에 visitor를 적용(제자리 변형). */
|
|
171
|
+
export function transformExpressions(program: Program, visitor: ExpressionVisitor): void {
|
|
172
|
+
walkBlock(program.body, visitor)
|
|
173
|
+
}
|
package/src/pipeline.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Program } from "luau-parser"
|
|
2
|
+
import type { ObfuscateConfig } from "./config"
|
|
3
|
+
import { runStripTypes } from "./passes/StripTypes"
|
|
4
|
+
import { runStringsToExpressions } from "./passes/StringsToExpressions"
|
|
5
|
+
import { runNumbersToExpressions } from "./passes/NumbersToExpressions"
|
|
6
|
+
import { runRenameVariables } from "./passes/RenameVariables"
|
|
7
|
+
import { runGlobalMapping } from "./passes/GlobalMapping"
|
|
8
|
+
import { runConstantArray } from "./passes/ConstantArray"
|
|
9
|
+
import { runEncryptStrings } from "./passes/EncryptStrings"
|
|
10
|
+
import { runEncryptNumbers } from "./passes/EncryptNumbers"
|
|
11
|
+
import { runInsertJunk } from "./passes/InsertJunk"
|
|
12
|
+
import { runWrapInFunction } from "./passes/WrapInFunction"
|
|
13
|
+
|
|
14
|
+
type PassFn = (program: Program, options: any) => void
|
|
15
|
+
|
|
16
|
+
export const PASS_ORDER: (keyof ObfuscateConfig)[] = [
|
|
17
|
+
"GlobalMapping",
|
|
18
|
+
"StringsToExpressions",
|
|
19
|
+
"NumbersToExpressions",
|
|
20
|
+
"RenameVariables",
|
|
21
|
+
"ConstantArray",
|
|
22
|
+
"EncryptStrings",
|
|
23
|
+
"EncryptNumbers",
|
|
24
|
+
"InsertJunk",
|
|
25
|
+
"Vmify",
|
|
26
|
+
"WrapInFunction",
|
|
27
|
+
"Minify",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const PASS_MAP: Partial<Record<keyof ObfuscateConfig, PassFn>> = {
|
|
31
|
+
GlobalMapping: runGlobalMapping,
|
|
32
|
+
StringsToExpressions: runStringsToExpressions,
|
|
33
|
+
NumbersToExpressions: runNumbersToExpressions,
|
|
34
|
+
RenameVariables: runRenameVariables,
|
|
35
|
+
ConstantArray: runConstantArray,
|
|
36
|
+
EncryptStrings: runEncryptStrings,
|
|
37
|
+
EncryptNumbers: runEncryptNumbers,
|
|
38
|
+
InsertJunk: runInsertJunk,
|
|
39
|
+
WrapInFunction: runWrapInFunction,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function runPipeline(program: Program, config: ObfuscateConfig): void {
|
|
43
|
+
// 타입 주석/별칭은 값 트리와 독립적으로 원본 이름을 참조하고 있어서
|
|
44
|
+
// (예: typeof(x)) 어떤 pass보다도 먼저 지워야 함. 끄면 무조건 깨지므로
|
|
45
|
+
// config로 조절 가능한 옵션이 아니라 파이프라인 진입 시 항상 실행.
|
|
46
|
+
runStripTypes(program)
|
|
47
|
+
|
|
48
|
+
for (const key of PASS_ORDER) {
|
|
49
|
+
const feature = config[key]
|
|
50
|
+
if (!feature.active) continue
|
|
51
|
+
|
|
52
|
+
const pass = PASS_MAP[key]
|
|
53
|
+
if (!pass) continue
|
|
54
|
+
|
|
55
|
+
const { active, ...options } = feature as { active: true } & Record<string, unknown>
|
|
56
|
+
pass(program, options)
|
|
57
|
+
}
|
|
58
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +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"]
|
|
21
|
+
}
|