luau-obfuscator 1.0.1 → 1.0.3
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 +272 -133
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +272 -133
- package/generated/grammers.luau +11812 -0
- package/generated/tests.luau +4330 -0
- package/package.json +1 -1
- package/scripts/test.js +18 -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 +35 -11
- package/src/passes/Vmify.ts +30 -11
- package/src/passes/nodeFactory.ts +19 -0
- package/src/passes/vmify/chunk.ts +9 -5
- package/src/passes/vmify/compiler.ts +18 -0
- 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
|
@@ -30,7 +30,7 @@ local ${N.handlers} = {}
|
|
|
30
30
|
|
|
31
31
|
local function ${N.dispatch}(${N.frame}, pc)
|
|
32
32
|
local instr = ${N.frame}.${N.code}[pc]
|
|
33
|
-
return ${N.handlers}[instr
|
|
33
|
+
return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
local function ${N.rk}(${N.frame}, x)
|
|
@@ -43,156 +43,156 @@ end
|
|
|
43
43
|
|
|
44
44
|
-- MOVE
|
|
45
45
|
${N.handlers}[${op(Opcode.MOVE)}] = function(${N.frame}, pc, instr)
|
|
46
|
-
${N.frame}.${N.R}[instr
|
|
46
|
+
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
|
|
47
47
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
-- LOADK
|
|
51
51
|
${N.handlers}[${op(Opcode.LOADK)}] = function(${N.frame}, pc, instr)
|
|
52
|
-
${N.frame}.${N.R}[instr
|
|
52
|
+
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
|
|
53
53
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
-- LOADBOOL
|
|
57
57
|
${N.handlers}[${op(Opcode.LOADBOOL)}] = function(${N.frame}, pc, instr)
|
|
58
|
-
${N.frame}.${N.R}[instr
|
|
58
|
+
${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
|
|
59
59
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
-- LOADNIL
|
|
63
63
|
${N.handlers}[${op(Opcode.LOADNIL)}] = function(${N.frame}, pc, instr)
|
|
64
|
-
for i = instr
|
|
64
|
+
for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
|
|
65
65
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
-- GETUPVAL
|
|
69
69
|
${N.handlers}[${op(Opcode.GETUPVAL)}] = function(${N.frame}, pc, instr)
|
|
70
|
-
${N.frame}.${N.R}[instr
|
|
70
|
+
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
|
|
71
71
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
-- SETUPVAL
|
|
75
75
|
${N.handlers}[${op(Opcode.SETUPVAL)}] = function(${N.frame}, pc, instr)
|
|
76
|
-
${N.frame}.${N.upvals}[instr
|
|
76
|
+
${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
|
|
77
77
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
-- GETGLOBAL (b는 RK가 아니라 상수풀 순수 인덱스)
|
|
81
81
|
${N.handlers}[${op(Opcode.GETGLOBAL)}] = function(${N.frame}, pc, instr)
|
|
82
|
-
${N.frame}.${N.R}[instr
|
|
82
|
+
${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
|
|
83
83
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
-- SETGLOBAL
|
|
87
87
|
${N.handlers}[${op(Opcode.SETGLOBAL)}] = function(${N.frame}, pc, instr)
|
|
88
|
-
${N.globals}[${N.frame}.${N.K}[instr
|
|
88
|
+
${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
|
|
89
89
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
-- GETTABLE
|
|
93
93
|
${N.handlers}[${op(Opcode.GETTABLE)}] = function(${N.frame}, pc, instr)
|
|
94
|
-
${N.frame}.${N.R}[instr
|
|
94
|
+
${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
|
|
95
95
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
-- SETTABLE
|
|
99
99
|
${N.handlers}[${op(Opcode.SETTABLE)}] = function(${N.frame}, pc, instr)
|
|
100
|
-
${N.frame}.${N.R}[instr
|
|
100
|
+
${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
|
|
101
101
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
-- NEWTABLE
|
|
105
105
|
${N.handlers}[${op(Opcode.NEWTABLE)}] = function(${N.frame}, pc, instr)
|
|
106
|
-
${N.frame}.${N.R}[instr
|
|
106
|
+
${N.frame}.${N.R}[instr[2]] = {}
|
|
107
107
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
-- SELF (R[a] = R[b][key]; R[a+1] = R[b])
|
|
111
111
|
${N.handlers}[${op(Opcode.SELF)}] = function(${N.frame}, pc, instr)
|
|
112
|
-
local obj = ${N.frame}.${N.R}[instr
|
|
113
|
-
${N.frame}.${N.R}[instr
|
|
114
|
-
${N.frame}.${N.R}[instr
|
|
112
|
+
local obj = ${N.frame}.${N.R}[instr[3]]
|
|
113
|
+
${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
|
|
114
|
+
${N.frame}.${N.R}[instr[2] + 1] = obj
|
|
115
115
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
-- ADD SUB MUL DIV MOD POW
|
|
119
119
|
${N.handlers}[${op(Opcode.ADD)}] = function(${N.frame}, pc, instr)
|
|
120
|
-
${N.frame}.${N.R}[instr
|
|
120
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
|
|
121
121
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
122
122
|
end
|
|
123
123
|
${N.handlers}[${op(Opcode.SUB)}] = function(${N.frame}, pc, instr)
|
|
124
|
-
${N.frame}.${N.R}[instr
|
|
124
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
|
|
125
125
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
126
126
|
end
|
|
127
127
|
${N.handlers}[${op(Opcode.MUL)}] = function(${N.frame}, pc, instr)
|
|
128
|
-
${N.frame}.${N.R}[instr
|
|
128
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
|
|
129
129
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
130
130
|
end
|
|
131
131
|
${N.handlers}[${op(Opcode.DIV)}] = function(${N.frame}, pc, instr)
|
|
132
|
-
${N.frame}.${N.R}[instr
|
|
132
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
|
|
133
133
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
134
134
|
end
|
|
135
135
|
${N.handlers}[${op(Opcode.MOD)}] = function(${N.frame}, pc, instr)
|
|
136
|
-
${N.frame}.${N.R}[instr
|
|
136
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
|
|
137
137
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
138
138
|
end
|
|
139
139
|
${N.handlers}[${op(Opcode.POW)}] = function(${N.frame}, pc, instr)
|
|
140
|
-
${N.frame}.${N.R}[instr
|
|
140
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
|
|
141
141
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
-- IDIV (Luau 바닥 나눗셈)
|
|
145
145
|
${N.handlers}[${op(Opcode.IDIV)}] = function(${N.frame}, pc, instr)
|
|
146
|
-
${N.frame}.${N.R}[instr
|
|
146
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
|
|
147
147
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
-- CONCAT
|
|
151
151
|
${N.handlers}[${op(Opcode.CONCAT)}] = function(${N.frame}, pc, instr)
|
|
152
|
-
${N.frame}.${N.R}[instr
|
|
152
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
|
|
153
153
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
-- UNM
|
|
157
157
|
${N.handlers}[${op(Opcode.UNM)}] = function(${N.frame}, pc, instr)
|
|
158
|
-
${N.frame}.${N.R}[instr
|
|
158
|
+
${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
|
|
159
159
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
-- NOT
|
|
163
163
|
${N.handlers}[${op(Opcode.NOT)}] = function(${N.frame}, pc, instr)
|
|
164
|
-
${N.frame}.${N.R}[instr
|
|
164
|
+
${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
|
|
165
165
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
166
166
|
end
|
|
167
167
|
|
|
168
168
|
-- LEN
|
|
169
169
|
${N.handlers}[${op(Opcode.LEN)}] = function(${N.frame}, pc, instr)
|
|
170
|
-
${N.frame}.${N.R}[instr
|
|
170
|
+
${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
|
|
171
171
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
-- JMP (pc += a)
|
|
175
175
|
${N.handlers}[${op(Opcode.JMP)}] = function(${N.frame}, pc, instr)
|
|
176
|
-
return ${N.dispatch}(${N.frame}, pc + instr
|
|
176
|
+
return ${N.dispatch}(${N.frame}, pc + instr[2])
|
|
177
177
|
end
|
|
178
178
|
|
|
179
179
|
-- EQ LT LE (값 생성형 비교)
|
|
180
180
|
${N.handlers}[${op(Opcode.EQ)}] = function(${N.frame}, pc, instr)
|
|
181
|
-
${N.frame}.${N.R}[instr
|
|
181
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
|
|
182
182
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
183
183
|
end
|
|
184
184
|
${N.handlers}[${op(Opcode.LT)}] = function(${N.frame}, pc, instr)
|
|
185
|
-
${N.frame}.${N.R}[instr
|
|
185
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
|
|
186
186
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
187
187
|
end
|
|
188
188
|
${N.handlers}[${op(Opcode.LE)}] = function(${N.frame}, pc, instr)
|
|
189
|
-
${N.frame}.${N.R}[instr
|
|
189
|
+
${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
|
|
190
190
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
191
191
|
end
|
|
192
192
|
|
|
193
193
|
-- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 — 보통 다음 명령은 JMP)
|
|
194
194
|
${N.handlers}[${op(Opcode.TEST)}] = function(${N.frame}, pc, instr)
|
|
195
|
-
if (not ${N.frame}.${N.R}[instr
|
|
195
|
+
if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
|
|
196
196
|
return ${N.dispatch}(${N.frame}, pc + 2)
|
|
197
197
|
end
|
|
198
198
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
@@ -200,7 +200,7 @@ end
|
|
|
200
200
|
|
|
201
201
|
-- CALL
|
|
202
202
|
${N.handlers}[${op(Opcode.CALL)}] = function(${N.frame}, pc, instr)
|
|
203
|
-
local a, b, c = instr
|
|
203
|
+
local a, b, c = instr[2], instr[3], instr[4]
|
|
204
204
|
local fn = ${N.frame}.${N.R}[a]
|
|
205
205
|
local nargs
|
|
206
206
|
if b == 0 then
|
|
@@ -225,7 +225,7 @@ end
|
|
|
225
225
|
|
|
226
226
|
-- RETURN
|
|
227
227
|
${N.handlers}[${op(Opcode.RETURN)}] = function(${N.frame}, pc, instr)
|
|
228
|
-
local a, b = instr
|
|
228
|
+
local a, b = instr[2], instr[3]
|
|
229
229
|
if b == 0 then
|
|
230
230
|
return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
|
|
231
231
|
end
|
|
@@ -234,14 +234,14 @@ end
|
|
|
234
234
|
|
|
235
235
|
-- FORPREP
|
|
236
236
|
${N.handlers}[${op(Opcode.FORPREP)}] = function(${N.frame}, pc, instr)
|
|
237
|
-
local a = instr
|
|
237
|
+
local a = instr[2]
|
|
238
238
|
${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
|
|
239
|
-
return ${N.dispatch}(${N.frame}, pc + instr
|
|
239
|
+
return ${N.dispatch}(${N.frame}, pc + instr[3])
|
|
240
240
|
end
|
|
241
241
|
|
|
242
242
|
-- FORLOOP
|
|
243
243
|
${N.handlers}[${op(Opcode.FORLOOP)}] = function(${N.frame}, pc, instr)
|
|
244
|
-
local a = instr
|
|
244
|
+
local a = instr[2]
|
|
245
245
|
local step = ${N.frame}.${N.R}[a + 2]
|
|
246
246
|
${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] + step
|
|
247
247
|
local ok
|
|
@@ -252,26 +252,26 @@ ${N.handlers}[${op(Opcode.FORLOOP)}] = function(${N.frame}, pc, instr)
|
|
|
252
252
|
end
|
|
253
253
|
if ok then
|
|
254
254
|
${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
|
|
255
|
-
return ${N.dispatch}(${N.frame}, pc + instr
|
|
255
|
+
return ${N.dispatch}(${N.frame}, pc + instr[3])
|
|
256
256
|
end
|
|
257
257
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
258
258
|
end
|
|
259
259
|
|
|
260
260
|
-- CLOSURE
|
|
261
261
|
${N.handlers}[${op(Opcode.CLOSURE)}] = function(${N.frame}, pc, instr)
|
|
262
|
-
local childProto = ${N.frame}.${N.protos}[instr
|
|
262
|
+
local childProto = ${N.frame}.${N.protos}[instr[3] + 1]
|
|
263
263
|
local n = #childProto.${N.upvalDescs}
|
|
264
264
|
local capturedUpvals = table.create(n)
|
|
265
265
|
for i = 1, n do
|
|
266
266
|
local desc = childProto.${N.upvalDescs}[i]
|
|
267
267
|
local captureInstr = ${N.frame}.${N.code}[pc + i]
|
|
268
268
|
if desc.${N.kind} == "local" then
|
|
269
|
-
capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr
|
|
269
|
+
capturedUpvals[i] = ${N.frame}.${N.R}[captureInstr[3]]
|
|
270
270
|
else
|
|
271
|
-
capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr
|
|
271
|
+
capturedUpvals[i] = ${N.frame}.${N.upvals}[captureInstr[3] + 1]
|
|
272
272
|
end
|
|
273
273
|
end
|
|
274
|
-
${N.frame}.${N.R}[instr
|
|
274
|
+
${N.frame}.${N.R}[instr[2]] = function(...)
|
|
275
275
|
return ${N.execute}(childProto, capturedUpvals, ...)
|
|
276
276
|
end
|
|
277
277
|
return ${N.dispatch}(${N.frame}, pc + 1 + n)
|
|
@@ -279,22 +279,22 @@ end
|
|
|
279
279
|
|
|
280
280
|
-- VARARG
|
|
281
281
|
${N.handlers}[${op(Opcode.VARARG)}] = function(${N.frame}, pc, instr)
|
|
282
|
-
local count = instr
|
|
282
|
+
local count = instr[3] == 0 and ${N.frame}.${N.nVarargs} or (instr[3] - 1)
|
|
283
283
|
for i = 1, count do
|
|
284
|
-
${N.frame}.${N.R}[instr
|
|
284
|
+
${N.frame}.${N.R}[instr[2] + i - 1] = ${N.frame}.${N.varargs}[i]
|
|
285
285
|
end
|
|
286
|
-
if instr
|
|
287
|
-
${N.frame}.${N.multiTop} = instr
|
|
286
|
+
if instr[3] == 0 then
|
|
287
|
+
${N.frame}.${N.multiTop} = instr[2] + count
|
|
288
288
|
end
|
|
289
289
|
return ${N.dispatch}(${N.frame}, pc + 1)
|
|
290
290
|
end
|
|
291
291
|
|
|
292
292
|
-- SETLIST
|
|
293
293
|
${N.handlers}[${op(Opcode.SETLIST)}] = function(${N.frame}, pc, instr)
|
|
294
|
-
local tbl = ${N.frame}.${N.R}[instr
|
|
295
|
-
local valuesBase = instr
|
|
294
|
+
local tbl = ${N.frame}.${N.R}[instr[2]]
|
|
295
|
+
local valuesBase = instr[3]
|
|
296
296
|
local top = ${N.frame}.${N.multiTop} or (valuesBase + 1)
|
|
297
|
-
local startIdx = instr
|
|
297
|
+
local startIdx = instr[4]
|
|
298
298
|
local n = top - valuesBase
|
|
299
299
|
for i = 0, n - 1 do
|
|
300
300
|
tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
|
|
@@ -331,4 +331,4 @@ function ${N.execute}(proto, upvals, ...)
|
|
|
331
331
|
return ${N.dispatch}(${N.frame}, 1)
|
|
332
332
|
end
|
|
333
333
|
`
|
|
334
|
-
}
|
|
334
|
+
}
|
|
@@ -3,7 +3,7 @@ import type { Proto, ConstValue } from "./chunk"
|
|
|
3
3
|
import { Opcode } from "./opcodes"
|
|
4
4
|
import type { VmNames } from "./names"
|
|
5
5
|
import {
|
|
6
|
-
table, positionalField, namedField, numberLiteral, stringLiteral, booleanLiteral, nilLiteral,
|
|
6
|
+
table, positionalField, namedField, numberLiteral, vmNumberLiteral, stringLiteral, booleanLiteral, nilLiteral,
|
|
7
7
|
} from "../nodeFactory"
|
|
8
8
|
|
|
9
9
|
/** proto.consts[]를 Luau 배열 리터럴로: { [1] = v1, [2] = v2, ... } (1-based는 그대로 유지) */
|
|
@@ -26,14 +26,14 @@ function serializeConstValue(v: ConstValue): Expression {
|
|
|
26
26
|
* 테이블을 바로 찾아낼 수 있기 때문. runtime.ts가 같은 names로 instr.<key>를 읽으므로
|
|
27
27
|
* 반드시 컴파일 시점에 쓴 것과 동일한 VmNames 인스턴스를 넘겨야 한다.
|
|
28
28
|
*/
|
|
29
|
-
function serializeInstrs(proto: Proto
|
|
29
|
+
function serializeInstrs(proto: Proto): Expression {
|
|
30
30
|
const fields: TableField[] = proto.code.map((instr) =>
|
|
31
31
|
positionalField(
|
|
32
32
|
table([
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
positionalField(vmNumberLiteral(instr.op as number)),
|
|
34
|
+
positionalField(vmNumberLiteral(instr.a)),
|
|
35
|
+
positionalField(vmNumberLiteral(instr.b)),
|
|
36
|
+
positionalField(vmNumberLiteral(instr.c)),
|
|
37
37
|
]),
|
|
38
38
|
))
|
|
39
39
|
return table(fields)
|
|
@@ -44,7 +44,7 @@ function serializeUpvalDescs(proto: Proto, names: VmNames): Expression {
|
|
|
44
44
|
positionalField(
|
|
45
45
|
table([
|
|
46
46
|
namedField(names.kind, stringLiteral(d.kind)),
|
|
47
|
-
namedField(names.index,
|
|
47
|
+
namedField(names.index, vmNumberLiteral(d.index)),
|
|
48
48
|
]),
|
|
49
49
|
))
|
|
50
50
|
return table(fields)
|
|
@@ -54,10 +54,10 @@ function serializeUpvalDescs(proto: Proto, names: VmNames): Expression {
|
|
|
54
54
|
* (필드 키는 전부 names에서 온 무작위 이름). */
|
|
55
55
|
export function serializeProto(proto: Proto, names: VmNames): Expression {
|
|
56
56
|
const fields: TableField[] = [
|
|
57
|
-
namedField(names.numParams,
|
|
57
|
+
namedField(names.numParams, vmNumberLiteral(proto.numParams)),
|
|
58
58
|
namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
|
|
59
|
-
namedField(names.maxRegs,
|
|
60
|
-
namedField(names.code, serializeInstrs(proto
|
|
59
|
+
namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
|
|
60
|
+
namedField(names.code, serializeInstrs(proto)),
|
|
61
61
|
namedField(names.consts, serializeConsts(proto.consts)),
|
|
62
62
|
namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
|
|
63
63
|
namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names))))),
|
|
@@ -65,4 +65,4 @@ export function serializeProto(proto: Proto, names: VmNames): Expression {
|
|
|
65
65
|
return table(fields)
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export { Opcode }
|
|
68
|
+
export { Opcode }
|
package/src/passes/walk.ts
CHANGED
|
@@ -10,79 +10,177 @@ function visitArray(arr: Expression[], visitor: ExpressionVisitor): void {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
interface ChildAccessor {
|
|
14
|
+
get(): Expression
|
|
15
|
+
set(v: Expression): void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* expr의 "자식 Expression"들에 대한 getter/setter 목록을 반환한다.
|
|
20
|
+
* (FunctionExpression은 별도 처리하므로 여기서는 빈 배열을 반환한다.)
|
|
21
|
+
*/
|
|
22
|
+
function getExpressionChildren(expr: Expression): ChildAccessor[] {
|
|
14
23
|
switch (expr.type) {
|
|
15
|
-
case "InterpolatedStringExpression":
|
|
24
|
+
case "InterpolatedStringExpression": {
|
|
25
|
+
const children: ChildAccessor[] = []
|
|
16
26
|
for (const part of expr.parts) {
|
|
17
27
|
if (part.kind === "expression") {
|
|
18
|
-
|
|
28
|
+
children.push({
|
|
29
|
+
get: () => part.expression,
|
|
30
|
+
set: (v) => { part.expression = v },
|
|
31
|
+
})
|
|
19
32
|
}
|
|
20
33
|
}
|
|
21
|
-
|
|
34
|
+
return children
|
|
35
|
+
}
|
|
22
36
|
|
|
23
37
|
case "FunctionExpression":
|
|
24
|
-
|
|
25
|
-
break
|
|
38
|
+
return []
|
|
26
39
|
|
|
27
|
-
case "TableExpression":
|
|
40
|
+
case "TableExpression": {
|
|
41
|
+
const children: ChildAccessor[] = []
|
|
28
42
|
for (const field of expr.fields) {
|
|
29
43
|
if (field.type === "TableFieldPositional") {
|
|
30
|
-
field.value
|
|
44
|
+
children.push({ get: () => field.value, set: (v) => { field.value = v } })
|
|
31
45
|
} else if (field.type === "TableFieldNamed") {
|
|
32
|
-
field.value
|
|
46
|
+
children.push({ get: () => field.value, set: (v) => { field.value = v } })
|
|
33
47
|
} else if (field.type === "TableFieldComputed") {
|
|
34
|
-
field.key
|
|
35
|
-
field.value
|
|
48
|
+
children.push({ get: () => field.key, set: (v) => { field.key = v } })
|
|
49
|
+
children.push({ get: () => field.value, set: (v) => { field.value = v } })
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
|
-
|
|
52
|
+
return children
|
|
53
|
+
}
|
|
39
54
|
|
|
40
55
|
case "BinaryExpression":
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
return [
|
|
57
|
+
{ get: () => expr.left, set: (v) => { expr.left = v } },
|
|
58
|
+
{ get: () => expr.right, set: (v) => { expr.right = v } },
|
|
59
|
+
]
|
|
44
60
|
|
|
45
61
|
case "UnaryExpression":
|
|
46
|
-
expr.argument
|
|
47
|
-
break
|
|
62
|
+
return [{ get: () => expr.argument, set: (v) => { expr.argument = v } }]
|
|
48
63
|
|
|
49
64
|
case "MemberExpression":
|
|
50
|
-
expr.object
|
|
51
|
-
break
|
|
65
|
+
return [{ get: () => expr.object, set: (v) => { expr.object = v } }]
|
|
52
66
|
|
|
53
67
|
case "IndexExpression":
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
return [
|
|
69
|
+
{ get: () => expr.object, set: (v) => { expr.object = v } },
|
|
70
|
+
{ get: () => expr.index, set: (v) => { expr.index = v } },
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
case "CallExpression": {
|
|
74
|
+
const children: ChildAccessor[] = [
|
|
75
|
+
{ get: () => expr.callee, set: (v) => { expr.callee = v } },
|
|
76
|
+
]
|
|
77
|
+
for (let i = 0; i < expr.arguments.length; i++) {
|
|
78
|
+
const idx = i
|
|
79
|
+
children.push({
|
|
80
|
+
get: () => expr.arguments[idx],
|
|
81
|
+
set: (v) => { expr.arguments[idx] = v },
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
return children
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case "MethodCallExpression": {
|
|
88
|
+
const children: ChildAccessor[] = [
|
|
89
|
+
{ get: () => expr.object, set: (v) => { expr.object = v } },
|
|
90
|
+
]
|
|
91
|
+
for (let i = 0; i < expr.arguments.length; i++) {
|
|
92
|
+
const idx = i
|
|
93
|
+
children.push({
|
|
94
|
+
get: () => expr.arguments[idx],
|
|
95
|
+
set: (v) => { expr.arguments[idx] = v },
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
return children
|
|
99
|
+
}
|
|
67
100
|
|
|
68
101
|
case "ParenthesizedExpression":
|
|
69
|
-
expr.expression
|
|
70
|
-
break
|
|
102
|
+
return [{ get: () => expr.expression, set: (v) => { expr.expression = v } }]
|
|
71
103
|
|
|
72
104
|
case "TypeAssertionExpression":
|
|
73
|
-
expr.expression
|
|
74
|
-
break
|
|
105
|
+
return [{ get: () => expr.expression, set: (v) => { expr.expression = v } }]
|
|
75
106
|
|
|
76
|
-
case "IfElseExpression":
|
|
107
|
+
case "IfElseExpression": {
|
|
108
|
+
const children: ChildAccessor[] = []
|
|
77
109
|
for (const clause of expr.clauses) {
|
|
78
|
-
clause.condition
|
|
79
|
-
clause.body
|
|
110
|
+
children.push({ get: () => clause.condition, set: (v) => { clause.condition = v } })
|
|
111
|
+
children.push({ get: () => clause.body, set: (v) => { clause.body = v } })
|
|
112
|
+
}
|
|
113
|
+
children.push({ get: () => expr.alternate, set: (v) => { expr.alternate = v } })
|
|
114
|
+
return children
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
default:
|
|
118
|
+
return []
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface WalkFrame {
|
|
123
|
+
expr: Expression
|
|
124
|
+
children: ChildAccessor[]
|
|
125
|
+
index: number
|
|
126
|
+
setInParent: ((v: Expression) => void) | null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Expression 트리를 후위순회(post-order)로 변형한다.
|
|
131
|
+
*
|
|
132
|
+
* 원래는 재귀 함수였지만, 문자열/숫자 난독화 패스가 만들어내는 매우 깊은
|
|
133
|
+
* BinaryExpression 체인(예: 수천 개의 `..` concat)을 순회할 때 네이티브
|
|
134
|
+
* 콜스택이 트리 깊이만큼 쌓여 "Maximum call stack size exceeded"가
|
|
135
|
+
* 발생했다. 그래서 재귀 대신 힙에 할당되는 명시적 스택을 사용해
|
|
136
|
+
* 트리 깊이와 무관하게 동작하도록 구현했다.
|
|
137
|
+
*/
|
|
138
|
+
export function mapExpression(root: Expression, visitor: ExpressionVisitor): Expression {
|
|
139
|
+
// FunctionExpression은 본문(statement 트리)을 별도로 순회해야 하므로 특별 취급한다.
|
|
140
|
+
// 함수 중첩 깊이는 보통 얕아서(재귀해도 안전) 그대로 재귀를 사용한다.
|
|
141
|
+
if (root.type === "FunctionExpression") {
|
|
142
|
+
walkFunctionBody(root.func, visitor)
|
|
143
|
+
return visitor(root) ?? root
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let result: Expression = root
|
|
147
|
+
const stack: WalkFrame[] = [{
|
|
148
|
+
expr: root,
|
|
149
|
+
children: getExpressionChildren(root),
|
|
150
|
+
index: 0,
|
|
151
|
+
setInParent: (v) => { result = v },
|
|
152
|
+
}]
|
|
153
|
+
|
|
154
|
+
while (stack.length > 0) {
|
|
155
|
+
const frame = stack[stack.length - 1]
|
|
156
|
+
|
|
157
|
+
if (frame.index < frame.children.length) {
|
|
158
|
+
const child = frame.children[frame.index]
|
|
159
|
+
frame.index++
|
|
160
|
+
const childExpr = child.get()
|
|
161
|
+
|
|
162
|
+
if (childExpr.type === "FunctionExpression") {
|
|
163
|
+
walkFunctionBody(childExpr.func, visitor)
|
|
164
|
+
child.set(visitor(childExpr) ?? childExpr)
|
|
165
|
+
continue
|
|
80
166
|
}
|
|
81
|
-
|
|
82
|
-
|
|
167
|
+
|
|
168
|
+
stack.push({
|
|
169
|
+
expr: childExpr,
|
|
170
|
+
children: getExpressionChildren(childExpr),
|
|
171
|
+
index: 0,
|
|
172
|
+
setInParent: child.set,
|
|
173
|
+
})
|
|
174
|
+
continue
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 모든 자식 처리가 끝났으면 visitor를 실행하고, 부모에 반영한 뒤 pop한다.
|
|
178
|
+
const finished = visitor(frame.expr) ?? frame.expr
|
|
179
|
+
frame.setInParent?.(finished)
|
|
180
|
+
stack.pop()
|
|
83
181
|
}
|
|
84
182
|
|
|
85
|
-
return
|
|
183
|
+
return result
|
|
86
184
|
}
|
|
87
185
|
|
|
88
186
|
function walkFunctionBody(func: FunctionBody, visitor: ExpressionVisitor): void {
|
|
@@ -170,4 +268,4 @@ function walkStatement(stmt: Statement, visitor: ExpressionVisitor): void {
|
|
|
170
268
|
/** Program 전체를 순회하며 모든 Expression 노드에 visitor를 적용(제자리 변형). */
|
|
171
269
|
export function transformExpressions(program: Program, visitor: ExpressionVisitor): void {
|
|
172
270
|
walkBlock(program.body, visitor)
|
|
173
|
-
}
|
|
271
|
+
}
|
package/src/pipeline.ts
CHANGED
|
@@ -17,10 +17,10 @@ type PassFn = (program: Program, options: any) => void
|
|
|
17
17
|
export const PASS_ORDER: (keyof ObfuscateConfig)[] = [
|
|
18
18
|
"Vmify",
|
|
19
19
|
"GlobalMapping",
|
|
20
|
-
"StringsToExpressions",
|
|
21
|
-
"NumbersToExpressions",
|
|
22
20
|
"RenameVariables",
|
|
23
21
|
"ConstantArray",
|
|
22
|
+
"StringsToExpressions",
|
|
23
|
+
"NumbersToExpressions",
|
|
24
24
|
"EncryptStrings",
|
|
25
25
|
"EncryptNumbers",
|
|
26
26
|
"InsertJunk",
|
|
@@ -38,13 +38,10 @@ export const PASS_MAP: Partial<Record<keyof ObfuscateConfig, PassFn>> = {
|
|
|
38
38
|
EncryptNumbers: runEncryptNumbers,
|
|
39
39
|
InsertJunk: runInsertJunk,
|
|
40
40
|
WrapInFunction: runWrapInFunction,
|
|
41
|
-
Vmify: runVmify
|
|
41
|
+
Vmify: runVmify,
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export function runPipeline(program: Program, config: ObfuscateConfig): void {
|
|
45
|
-
// 타입 주석/별칭은 값 트리와 독립적으로 원본 이름을 참조하고 있어서
|
|
46
|
-
// (예: typeof(x)) 어떤 pass보다도 먼저 지워야 함. 끄면 무조건 깨지므로
|
|
47
|
-
// config로 조절 가능한 옵션이 아니라 파이프라인 진입 시 항상 실행.
|
|
48
45
|
runStripTypes(program)
|
|
49
46
|
|
|
50
47
|
for (const key of PASS_ORDER) {
|