luau-obfuscator 1.0.2 → 1.0.4

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.
@@ -2,17 +2,24 @@ import { Opcode, type OpcodeMap } from "./opcodes"
2
2
  import type { VmNames } from "./names"
3
3
 
4
4
  /**
5
- * VM 런타임 v3 — opcode마다 독립된 핸들러 함수를 만들고 서로 tail call로 이어지는
6
- * 구조는 v2와 동일하다. 다만 이제 모든 전역 식별자(__vm_execute, __vm_handlers, ...)와
7
- * frame/Instr/Proto의 테이블 필드 키가 고정 문자열이 아니라, 빌드마다 무작위로 뽑힌
8
- * names(names.ts)와 opcode 번호 재배치 opcodeMap(opcodes.ts)을 받아 그때그때
9
- * 소스를 새로 찍어낸다.
5
+ * VM 런타임 v4 — opcode마다 독립된 핸들러 함수를 만드는 구조는 v3와 동일하다.
6
+ * 모든 전역 식별자(__vm_execute, __vm_handlers, ...)와 frame/Instr/Proto의 테이블
7
+ * 필드 키가 고정 문자열이 아니라, 빌드마다 무작위로 뽑힌 names(names.ts)와 opcode 번호
8
+ * 재배치 opcodeMap(opcodes.ts)을 받아 그때그때 소스를 새로 찍어낸다.
10
9
  *
11
- * 왜 이렇게 하나:
12
- * - Luau는 proper tail call을 보장하므로(스택 안 쌓임, 성능도 loop와 동급),
13
- * "재귀처럼 보이지만 실제론 loop"인 구조를 공짜로 얻을 수 있다.
10
+ * v3와의 결정적 차이 — dispatch를 tail call 재귀가 아니라 while 루프로 돌린다:
11
+ * - v3는 "Luau가 proper tail call을 보장하므로 return dispatch(frame, pc+1)가
12
+ * 스택을 안 쌓는다"는 전제로 각 핸들러가 dispatch를 꼬리 호출로 이어붙였다.
13
+ * 그런데 Luau는 (Lua 5.x와 달리) proper tail call을 하지 않는다. 정확한 스택
14
+ * 트레이스/디버깅을 위해 의도적으로 뺐다. 그래서 명령어 하나 실행할 때마다
15
+ * dispatch 프레임이 실제로 하나씩 쌓였고, 명령어 수백 개짜리 함수만 돌려도
16
+ * stack overflow가 났다.
17
+ * - v4에서는 dispatch가 while 루프를 돌며 핸들러를 호출한다. 핸들러는 dispatch를
18
+ * 부르지 않고 "다음 pc(숫자)"를 반환하고, RETURN만 결과 패킷(table.pack 결과)을
19
+ * 반환한다. 루프는 반환값이 숫자면 pc를 갱신하고, 테이블이면 그걸 풀어서 반환한다.
20
+ * 이제 함수 하나를 끝까지 도는 동안 스택 깊이는 일정하다.
14
21
  * - "거대한 while+if-chain" 자체가 디컴파일러가 제일 먼저 찾는 VM 시그니처인데,
15
- * 이 구조는 그 패턴이 아예 없다.
22
+ * 루프 본문이 if-chain이 아니라 handlers[op](...) 단일 디스패치라 그 패턴은 아니다.
16
23
  * - opcode 번호 + 식별자 이름을 빌드마다 다르게 뽑기 때문에, 한 번 리버싱해서
17
24
  * "MOVE=0, __vm_execute라는 함수가 진입점" 같은 지식을 얻어도 다음 산출물에는
18
25
  * 그대로 안 먹힌다 — 매번 새로 분석해야 함.
@@ -21,16 +28,28 @@ import type { VmNames } from "./names"
21
28
  * (compiler.ts가 emit() 시점에 같은 opcodeMap으로 인스트럭션에 번호를 박아 넣으므로
22
29
  * 반드시 컴파일과 런타임 생성에 동일한 opcodeMap 인스턴스를 넘겨야 함).
23
30
  */
24
- export function buildVmRuntimeSource(names: VmNames, opcodeMap: OpcodeMap): string {
31
+ export function buildVmRuntimeSource(names: VmNames, opcodeMap: OpcodeMap, debug = false): string {
25
32
  const N = names
26
33
  const op = (o: Opcode) => opcodeMap[o]
27
34
 
28
35
  return `
29
36
  local ${N.handlers} = {}
30
37
 
38
+ -- 각 핸들러는 "다음 pc(숫자)"를 반환한다. RETURN 핸들러만 table.pack 결과(테이블)를
39
+ -- 반환하며, 그게 함수 실행 종료 신호다. Luau는 proper tail call을 하지 않으므로
40
+ -- 핸들러들이 서로 꼬리 호출로 이어지면 명령어당 스택 프레임이 쌓여 터진다 — 그래서
41
+ -- 여기서 while 루프로 돌려 스택 깊이를 함수 실행 내내 일정하게 유지한다.
31
42
  local function ${N.dispatch}(${N.frame}, pc)
32
- local instr = ${N.frame}.${N.code}[pc]
33
- return ${N.handlers}[instr[1]](${N.frame}, pc, instr)
43
+ local ${N.handlers} = ${N.handlers}
44
+ while true do
45
+ local instr = ${N.frame}.${N.code}[pc]
46
+ local next = ${N.handlers}[instr[1]](${N.frame}, pc, instr)
47
+ if type(next) == "number" then
48
+ pc = next
49
+ else
50
+ return table.unpack(next, 1, next.n)
51
+ end
52
+ end
34
53
  end
35
54
 
36
55
  local function ${N.rk}(${N.frame}, x)
@@ -44,67 +63,67 @@ end
44
63
  -- MOVE
45
64
  ${N.handlers}[${op(Opcode.MOVE)}] = function(${N.frame}, pc, instr)
46
65
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]]
47
- return ${N.dispatch}(${N.frame}, pc + 1)
66
+ return pc + 1
48
67
  end
49
68
 
50
69
  -- LOADK
51
70
  ${N.handlers}[${op(Opcode.LOADK)}] = function(${N.frame}, pc, instr)
52
71
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.K}[instr[3] + 1]
53
- return ${N.dispatch}(${N.frame}, pc + 1)
72
+ return pc + 1
54
73
  end
55
74
 
56
75
  -- LOADBOOL
57
76
  ${N.handlers}[${op(Opcode.LOADBOOL)}] = function(${N.frame}, pc, instr)
58
77
  ${N.frame}.${N.R}[instr[2]] = (instr[3] ~= 0)
59
- return ${N.dispatch}(${N.frame}, pc + 1)
78
+ return pc + 1
60
79
  end
61
80
 
62
81
  -- LOADNIL
63
82
  ${N.handlers}[${op(Opcode.LOADNIL)}] = function(${N.frame}, pc, instr)
64
83
  for i = instr[2], instr[3] do ${N.frame}.${N.R}[i] = nil end
65
- return ${N.dispatch}(${N.frame}, pc + 1)
84
+ return pc + 1
66
85
  end
67
86
 
68
87
  -- GETUPVAL
69
88
  ${N.handlers}[${op(Opcode.GETUPVAL)}] = function(${N.frame}, pc, instr)
70
89
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.upvals}[instr[3] + 1]
71
- return ${N.dispatch}(${N.frame}, pc + 1)
90
+ return pc + 1
72
91
  end
73
92
 
74
93
  -- SETUPVAL
75
94
  ${N.handlers}[${op(Opcode.SETUPVAL)}] = function(${N.frame}, pc, instr)
76
95
  ${N.frame}.${N.upvals}[instr[3] + 1] = ${N.frame}.${N.R}[instr[2]]
77
- return ${N.dispatch}(${N.frame}, pc + 1)
96
+ return pc + 1
78
97
  end
79
98
 
80
99
  -- GETGLOBAL (b는 RK가 아니라 상수풀 순수 인덱스)
81
100
  ${N.handlers}[${op(Opcode.GETGLOBAL)}] = function(${N.frame}, pc, instr)
82
101
  ${N.frame}.${N.R}[instr[2]] = ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]]
83
- return ${N.dispatch}(${N.frame}, pc + 1)
102
+ return pc + 1
84
103
  end
85
104
 
86
105
  -- SETGLOBAL
87
106
  ${N.handlers}[${op(Opcode.SETGLOBAL)}] = function(${N.frame}, pc, instr)
88
107
  ${N.globals}[${N.frame}.${N.K}[instr[3] + 1]] = ${N.frame}.${N.R}[instr[2]]
89
- return ${N.dispatch}(${N.frame}, pc + 1)
108
+ return pc + 1
90
109
  end
91
110
 
92
111
  -- GETTABLE
93
112
  ${N.handlers}[${op(Opcode.GETTABLE)}] = function(${N.frame}, pc, instr)
94
113
  ${N.frame}.${N.R}[instr[2]] = ${N.frame}.${N.R}[instr[3]][${N.rk}(${N.frame}, instr[4])]
95
- return ${N.dispatch}(${N.frame}, pc + 1)
114
+ return pc + 1
96
115
  end
97
116
 
98
117
  -- SETTABLE
99
118
  ${N.handlers}[${op(Opcode.SETTABLE)}] = function(${N.frame}, pc, instr)
100
119
  ${N.frame}.${N.R}[instr[2]][${N.rk}(${N.frame}, instr[3])] = ${N.rk}(${N.frame}, instr[4])
101
- return ${N.dispatch}(${N.frame}, pc + 1)
120
+ return pc + 1
102
121
  end
103
122
 
104
123
  -- NEWTABLE
105
124
  ${N.handlers}[${op(Opcode.NEWTABLE)}] = function(${N.frame}, pc, instr)
106
125
  ${N.frame}.${N.R}[instr[2]] = {}
107
- return ${N.dispatch}(${N.frame}, pc + 1)
126
+ return pc + 1
108
127
  end
109
128
 
110
129
  -- SELF (R[a] = R[b][key]; R[a+1] = R[b])
@@ -112,90 +131,90 @@ ${N.handlers}[${op(Opcode.SELF)}] = function(${N.frame}, pc, instr)
112
131
  local obj = ${N.frame}.${N.R}[instr[3]]
113
132
  ${N.frame}.${N.R}[instr[2]] = obj[${N.rk}(${N.frame}, instr[4])]
114
133
  ${N.frame}.${N.R}[instr[2] + 1] = obj
115
- return ${N.dispatch}(${N.frame}, pc + 1)
134
+ return pc + 1
116
135
  end
117
136
 
118
137
  -- ADD SUB MUL DIV MOD POW
119
138
  ${N.handlers}[${op(Opcode.ADD)}] = function(${N.frame}, pc, instr)
120
139
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) + ${N.rk}(${N.frame}, instr[4])
121
- return ${N.dispatch}(${N.frame}, pc + 1)
140
+ return pc + 1
122
141
  end
123
142
  ${N.handlers}[${op(Opcode.SUB)}] = function(${N.frame}, pc, instr)
124
143
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) - ${N.rk}(${N.frame}, instr[4])
125
- return ${N.dispatch}(${N.frame}, pc + 1)
144
+ return pc + 1
126
145
  end
127
146
  ${N.handlers}[${op(Opcode.MUL)}] = function(${N.frame}, pc, instr)
128
147
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) * ${N.rk}(${N.frame}, instr[4])
129
- return ${N.dispatch}(${N.frame}, pc + 1)
148
+ return pc + 1
130
149
  end
131
150
  ${N.handlers}[${op(Opcode.DIV)}] = function(${N.frame}, pc, instr)
132
151
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) / ${N.rk}(${N.frame}, instr[4])
133
- return ${N.dispatch}(${N.frame}, pc + 1)
152
+ return pc + 1
134
153
  end
135
154
  ${N.handlers}[${op(Opcode.MOD)}] = function(${N.frame}, pc, instr)
136
155
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) % ${N.rk}(${N.frame}, instr[4])
137
- return ${N.dispatch}(${N.frame}, pc + 1)
156
+ return pc + 1
138
157
  end
139
158
  ${N.handlers}[${op(Opcode.POW)}] = function(${N.frame}, pc, instr)
140
159
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) ^ ${N.rk}(${N.frame}, instr[4])
141
- return ${N.dispatch}(${N.frame}, pc + 1)
160
+ return pc + 1
142
161
  end
143
162
 
144
163
  -- IDIV (Luau 바닥 나눗셈)
145
164
  ${N.handlers}[${op(Opcode.IDIV)}] = function(${N.frame}, pc, instr)
146
165
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) // ${N.rk}(${N.frame}, instr[4])
147
- return ${N.dispatch}(${N.frame}, pc + 1)
166
+ return pc + 1
148
167
  end
149
168
 
150
169
  -- CONCAT
151
170
  ${N.handlers}[${op(Opcode.CONCAT)}] = function(${N.frame}, pc, instr)
152
171
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) .. ${N.rk}(${N.frame}, instr[4])
153
- return ${N.dispatch}(${N.frame}, pc + 1)
172
+ return pc + 1
154
173
  end
155
174
 
156
175
  -- UNM
157
176
  ${N.handlers}[${op(Opcode.UNM)}] = function(${N.frame}, pc, instr)
158
177
  ${N.frame}.${N.R}[instr[2]] = -${N.frame}.${N.R}[instr[3]]
159
- return ${N.dispatch}(${N.frame}, pc + 1)
178
+ return pc + 1
160
179
  end
161
180
 
162
181
  -- NOT
163
182
  ${N.handlers}[${op(Opcode.NOT)}] = function(${N.frame}, pc, instr)
164
183
  ${N.frame}.${N.R}[instr[2]] = not ${N.frame}.${N.R}[instr[3]]
165
- return ${N.dispatch}(${N.frame}, pc + 1)
184
+ return pc + 1
166
185
  end
167
186
 
168
187
  -- LEN
169
188
  ${N.handlers}[${op(Opcode.LEN)}] = function(${N.frame}, pc, instr)
170
189
  ${N.frame}.${N.R}[instr[2]] = #${N.frame}.${N.R}[instr[3]]
171
- return ${N.dispatch}(${N.frame}, pc + 1)
190
+ return pc + 1
172
191
  end
173
192
 
174
193
  -- JMP (pc += a)
175
194
  ${N.handlers}[${op(Opcode.JMP)}] = function(${N.frame}, pc, instr)
176
- return ${N.dispatch}(${N.frame}, pc + instr[2])
195
+ return pc + instr[2]
177
196
  end
178
197
 
179
198
  -- EQ LT LE (값 생성형 비교)
180
199
  ${N.handlers}[${op(Opcode.EQ)}] = function(${N.frame}, pc, instr)
181
200
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) == ${N.rk}(${N.frame}, instr[4])
182
- return ${N.dispatch}(${N.frame}, pc + 1)
201
+ return pc + 1
183
202
  end
184
203
  ${N.handlers}[${op(Opcode.LT)}] = function(${N.frame}, pc, instr)
185
204
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) < ${N.rk}(${N.frame}, instr[4])
186
- return ${N.dispatch}(${N.frame}, pc + 1)
205
+ return pc + 1
187
206
  end
188
207
  ${N.handlers}[${op(Opcode.LE)}] = function(${N.frame}, pc, instr)
189
208
  ${N.frame}.${N.R}[instr[2]] = ${N.rk}(${N.frame}, instr[3]) <= ${N.rk}(${N.frame}, instr[4])
190
- return ${N.dispatch}(${N.frame}, pc + 1)
209
+ return pc + 1
191
210
  end
192
211
 
193
212
  -- TEST (if (not R[a]) != (c!=0) then pc += 2 else pc += 1 — 보통 다음 명령은 JMP)
194
213
  ${N.handlers}[${op(Opcode.TEST)}] = function(${N.frame}, pc, instr)
195
214
  if (not ${N.frame}.${N.R}[instr[2]]) ~= (instr[4] ~= 0) then
196
- return ${N.dispatch}(${N.frame}, pc + 2)
215
+ return pc + 2
197
216
  end
198
- return ${N.dispatch}(${N.frame}, pc + 1)
217
+ return pc + 1
199
218
  end
200
219
 
201
220
  -- CALL
@@ -210,7 +229,13 @@ ${N.handlers}[${op(Opcode.CALL)}] = function(${N.frame}, pc, instr)
210
229
  end
211
230
  local args = table.create(nargs)
212
231
  for i = 1, nargs do args[i] = ${N.frame}.${N.R}[a + i] end
213
- local rets = table.pack(fn(table.unpack(args, 1, nargs)))
232
+ ${debug ? ` if type(fn) ~= "function" then
233
+ local mt = getmetatable(fn)
234
+ if not (mt and mt.__call) then
235
+ error(("[vmdebug] attempt to call a %s value: '%s' (source line %d)"):format(type(fn), tostring(instr[6]), tonumber(instr[5]) or -1), 0)
236
+ end
237
+ end
238
+ ` : ""} local rets = table.pack(fn(table.unpack(args, 1, nargs)))
214
239
  if c == 1 then
215
240
  -- discard
216
241
  elseif c == 0 then
@@ -220,23 +245,27 @@ ${N.handlers}[${op(Opcode.CALL)}] = function(${N.frame}, pc, instr)
220
245
  local want = c - 1
221
246
  for i = 1, want do ${N.frame}.${N.R}[a + i - 1] = rets[i] end
222
247
  end
223
- return ${N.dispatch}(${N.frame}, pc + 1)
248
+ return pc + 1
224
249
  end
225
250
 
226
251
  -- RETURN
252
+ -- 다른 핸들러는 "다음 pc(숫자)"를 반환하지만, RETURN만은 결과값을 table.pack으로 싸서
253
+ -- 테이블을 반환한다 — dispatch 루프는 반환값이 숫자가 아니면 이걸 함수 종료 신호로 보고
254
+ -- table.unpack(next, 1, next.n)으로 풀어 최종 반환한다. table.pack이 n을 채워 주므로
255
+ -- 중간 nil이 있어도 반환 개수가 정확히 보존된다.
227
256
  ${N.handlers}[${op(Opcode.RETURN)}] = function(${N.frame}, pc, instr)
228
257
  local a, b = instr[2], instr[3]
229
258
  if b == 0 then
230
- return table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1)
259
+ return table.pack(table.unpack(${N.frame}.${N.R}, a, (${N.frame}.${N.multiTop} or (a + 1)) - 1))
231
260
  end
232
- return table.unpack(${N.frame}.${N.R}, a, a + b - 2)
261
+ return table.pack(table.unpack(${N.frame}.${N.R}, a, a + b - 2))
233
262
  end
234
263
 
235
264
  -- FORPREP
236
265
  ${N.handlers}[${op(Opcode.FORPREP)}] = function(${N.frame}, pc, instr)
237
266
  local a = instr[2]
238
267
  ${N.frame}.${N.R}[a] = ${N.frame}.${N.R}[a] - ${N.frame}.${N.R}[a + 2]
239
- return ${N.dispatch}(${N.frame}, pc + instr[3])
268
+ return pc + instr[3]
240
269
  end
241
270
 
242
271
  -- FORLOOP
@@ -252,9 +281,9 @@ ${N.handlers}[${op(Opcode.FORLOOP)}] = function(${N.frame}, pc, instr)
252
281
  end
253
282
  if ok then
254
283
  ${N.frame}.${N.R}[a + 3] = ${N.frame}.${N.R}[a]
255
- return ${N.dispatch}(${N.frame}, pc + instr[3])
284
+ return pc + instr[3]
256
285
  end
257
- return ${N.dispatch}(${N.frame}, pc + 1)
286
+ return pc + 1
258
287
  end
259
288
 
260
289
  -- CLOSURE
@@ -274,7 +303,7 @@ ${N.handlers}[${op(Opcode.CLOSURE)}] = function(${N.frame}, pc, instr)
274
303
  ${N.frame}.${N.R}[instr[2]] = function(...)
275
304
  return ${N.execute}(childProto, capturedUpvals, ...)
276
305
  end
277
- return ${N.dispatch}(${N.frame}, pc + 1 + n)
306
+ return pc + 1 + n
278
307
  end
279
308
 
280
309
  -- VARARG
@@ -286,7 +315,7 @@ ${N.handlers}[${op(Opcode.VARARG)}] = function(${N.frame}, pc, instr)
286
315
  if instr[3] == 0 then
287
316
  ${N.frame}.${N.multiTop} = instr[2] + count
288
317
  end
289
- return ${N.dispatch}(${N.frame}, pc + 1)
318
+ return pc + 1
290
319
  end
291
320
 
292
321
  -- SETLIST
@@ -299,7 +328,7 @@ ${N.handlers}[${op(Opcode.SETLIST)}] = function(${N.frame}, pc, instr)
299
328
  for i = 0, n - 1 do
300
329
  tbl[startIdx + i] = ${N.frame}.${N.R}[valuesBase + i]
301
330
  end
302
- return ${N.dispatch}(${N.frame}, pc + 1)
331
+ return pc + 1
303
332
  end
304
333
 
305
334
  function ${N.execute}(proto, upvals, ...)
@@ -26,16 +26,22 @@ function serializeConstValue(v: ConstValue): Expression {
26
26
  * 테이블을 바로 찾아낼 수 있기 때문. runtime.ts가 같은 names로 instr.<key>를 읽으므로
27
27
  * 반드시 컴파일 시점에 쓴 것과 동일한 VmNames 인스턴스를 넘겨야 한다.
28
28
  */
29
- function serializeInstrs(proto: Proto): Expression {
30
- const fields: TableField[] = proto.code.map((instr) =>
31
- positionalField(
32
- table([
33
- positionalField(vmNumberLiteral(instr.op as number)),
34
- positionalField(vmNumberLiteral(instr.a)),
35
- positionalField(vmNumberLiteral(instr.b)),
36
- positionalField(vmNumberLiteral(instr.c)),
37
- ]),
38
- ))
29
+ function serializeInstrs(proto: Proto, debug: boolean): Expression {
30
+ const fields: TableField[] = proto.code.map((instr) => {
31
+ const tuple = [
32
+ positionalField(vmNumberLiteral(instr.op as number)),
33
+ positionalField(vmNumberLiteral(instr.a)),
34
+ positionalField(vmNumberLiteral(instr.b)),
35
+ positionalField(vmNumberLiteral(instr.c)),
36
+ ]
37
+ if (debug) {
38
+ // [5] = 소스 줄(없으면 0), [6] = 호출 대상 이름(없으면 ""). 런타임이 non-callable을
39
+ // 잡았을 때 이 두 값으로 원본 위치를 찍는다. instr[1..4]는 그대로라 안 건드림.
40
+ tuple.push(positionalField(numberLiteral(instr.dbgLine ?? 0)))
41
+ tuple.push(positionalField(stringLiteral(instr.dbgName ?? "")))
42
+ }
43
+ return positionalField(table(tuple))
44
+ })
39
45
  return table(fields)
40
46
  }
41
47
 
@@ -52,15 +58,15 @@ function serializeUpvalDescs(proto: Proto, names: VmNames): Expression {
52
58
 
53
59
  /** Proto 하나를 { numParams=.., hasVarargs=.., maxRegs=.., code={...}, consts={...}, upvalDescs={...}, protos={...} }로
54
60
  * (필드 키는 전부 names에서 온 무작위 이름). */
55
- export function serializeProto(proto: Proto, names: VmNames): Expression {
61
+ export function serializeProto(proto: Proto, names: VmNames, debug = false): Expression {
56
62
  const fields: TableField[] = [
57
63
  namedField(names.numParams, vmNumberLiteral(proto.numParams)),
58
64
  namedField(names.hasVarargs, booleanLiteral(proto.hasVarargs)),
59
65
  namedField(names.maxRegs, vmNumberLiteral(proto.maxRegs)),
60
- namedField(names.code, serializeInstrs(proto)),
66
+ namedField(names.code, serializeInstrs(proto, debug)),
61
67
  namedField(names.consts, serializeConsts(proto.consts)),
62
68
  namedField(names.upvalDescs, serializeUpvalDescs(proto, names)),
63
- namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names))))),
69
+ namedField(names.protos, table(proto.protos.map((p) => positionalField(serializeProto(p, names, debug))))),
64
70
  ]
65
71
  return table(fields)
66
72
  }