iris-decompiler 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/README.md +78 -0
- package/bin/cli.js +54 -0
- package/dist/base64.d.ts +1 -0
- package/dist/base64.js +38 -0
- package/dist/decompile_internal.d.ts +57 -0
- package/dist/decompile_internal.js +42 -0
- package/dist/decompiler.d.ts +4 -0
- package/dist/decompiler.js +2677 -0
- package/dist/disasm.d.ts +3 -0
- package/dist/disasm.js +321 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +109 -0
- package/dist/parser.d.ts +4 -0
- package/dist/parser.js +582 -0
- package/dist/types.d.ts +174 -0
- package/dist/types.js +299 -0
- package/package.json +25 -0
package/dist/parser.js
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseBytecode = parseBytecode;
|
|
4
|
+
const types_1 = require("./types");
|
|
5
|
+
const LBC_VERSION_MIN = 3;
|
|
6
|
+
const LBC_VERSION_MAX = 13;
|
|
7
|
+
const LBC_VERSION_CLASSES = 100;
|
|
8
|
+
const K_ROBLOX_SHUFFLE_MARKER_OP = types_1.Op.PREPVARARGS;
|
|
9
|
+
class Reader {
|
|
10
|
+
constructor(data) {
|
|
11
|
+
this.offset = 0;
|
|
12
|
+
this.data = data;
|
|
13
|
+
this.size = data.length;
|
|
14
|
+
}
|
|
15
|
+
readU8() {
|
|
16
|
+
if (this.offset + 1 > this.size)
|
|
17
|
+
return null;
|
|
18
|
+
return this.data[this.offset++];
|
|
19
|
+
}
|
|
20
|
+
readU32() {
|
|
21
|
+
if (this.offset + 4 > this.size)
|
|
22
|
+
return null;
|
|
23
|
+
let v = 0;
|
|
24
|
+
v |= this.data[this.offset++];
|
|
25
|
+
v |= this.data[this.offset++] << 8;
|
|
26
|
+
v |= this.data[this.offset++] << 16;
|
|
27
|
+
v |= this.data[this.offset++] << 24;
|
|
28
|
+
return v >>> 0;
|
|
29
|
+
}
|
|
30
|
+
readI32() {
|
|
31
|
+
if (this.offset + 4 > this.size)
|
|
32
|
+
return null;
|
|
33
|
+
let v = 0;
|
|
34
|
+
v |= this.data[this.offset++];
|
|
35
|
+
v |= this.data[this.offset++] << 8;
|
|
36
|
+
v |= this.data[this.offset++] << 16;
|
|
37
|
+
v |= this.data[this.offset++] << 24;
|
|
38
|
+
return v; // signed 32-bit
|
|
39
|
+
}
|
|
40
|
+
readU64() {
|
|
41
|
+
if (this.offset + 8 > this.size)
|
|
42
|
+
return null;
|
|
43
|
+
// JS numbers can't hold full 64-bit; read as two 32-bit halves and combine.
|
|
44
|
+
let lo = 0;
|
|
45
|
+
lo |= this.data[this.offset++];
|
|
46
|
+
lo |= this.data[this.offset++] << 8;
|
|
47
|
+
lo |= this.data[this.offset++] << 16;
|
|
48
|
+
lo |= this.data[this.offset++] << 24;
|
|
49
|
+
let hi = 0;
|
|
50
|
+
hi |= this.data[this.offset++];
|
|
51
|
+
hi |= this.data[this.offset++] << 8;
|
|
52
|
+
hi |= this.data[this.offset++] << 16;
|
|
53
|
+
hi |= this.data[this.offset++] << 24;
|
|
54
|
+
return (hi * 0x100000000 + lo) >>> 0;
|
|
55
|
+
}
|
|
56
|
+
readF32() {
|
|
57
|
+
if (this.offset + 4 > this.size)
|
|
58
|
+
return null;
|
|
59
|
+
const buf = new Uint8Array(4);
|
|
60
|
+
for (let i = 0; i < 4; i++)
|
|
61
|
+
buf[i] = this.data[this.offset++];
|
|
62
|
+
return new DataView(buf.buffer).getFloat32(0, true);
|
|
63
|
+
}
|
|
64
|
+
readF64() {
|
|
65
|
+
if (this.offset + 8 > this.size)
|
|
66
|
+
return null;
|
|
67
|
+
const buf = new Uint8Array(8);
|
|
68
|
+
for (let i = 0; i < 8; i++)
|
|
69
|
+
buf[i] = this.data[this.offset++];
|
|
70
|
+
return new DataView(buf.buffer).getFloat64(0, true);
|
|
71
|
+
}
|
|
72
|
+
skip(n) {
|
|
73
|
+
if (this.offset + n > this.size)
|
|
74
|
+
return false;
|
|
75
|
+
this.offset += n;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
take(n) {
|
|
79
|
+
if (!this.skip(n))
|
|
80
|
+
return null;
|
|
81
|
+
return this.data.subarray(this.offset - n, this.offset);
|
|
82
|
+
}
|
|
83
|
+
varint() {
|
|
84
|
+
let out = 0;
|
|
85
|
+
for (let shift = 0; shift < 35; shift += 7) {
|
|
86
|
+
const b = this.readU8();
|
|
87
|
+
if (b === null)
|
|
88
|
+
return null;
|
|
89
|
+
out |= (b & 0x7f) << shift;
|
|
90
|
+
if ((b & 0x80) === 0)
|
|
91
|
+
return out >>> 0;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
varint64() {
|
|
96
|
+
let out = 0;
|
|
97
|
+
for (let shift = 0; shift < 70; shift += 7) {
|
|
98
|
+
const b = this.readU8();
|
|
99
|
+
if (b === null)
|
|
100
|
+
return null;
|
|
101
|
+
out |= (b & 0x7f) << shift;
|
|
102
|
+
if ((b & 0x80) === 0)
|
|
103
|
+
return out >>> 0;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function recoverOpcodeKey(mainCode) {
|
|
109
|
+
if (mainCode.length === 0)
|
|
110
|
+
return 1;
|
|
111
|
+
const wire = mainCode[0] & 0xff;
|
|
112
|
+
if (wire === K_ROBLOX_SHUFFLE_MARKER_OP)
|
|
113
|
+
return 1;
|
|
114
|
+
for (let key = 1; key < 256; key += 2) {
|
|
115
|
+
if (((wire * key) & 0xff) === K_ROBLOX_SHUFFLE_MARKER_OP)
|
|
116
|
+
return key;
|
|
117
|
+
}
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
function decodeOpcodeShuffle(code, key) {
|
|
121
|
+
if (key === 1)
|
|
122
|
+
return;
|
|
123
|
+
for (let i = 0; i < code.length; i++) {
|
|
124
|
+
const wire = code[i] & 0xff;
|
|
125
|
+
const real = (wire * key) & 0xff;
|
|
126
|
+
code[i] = (code[i] & 0xffffff00) | real;
|
|
127
|
+
if (opHasAuxAt(code[i]) && i + 1 < code.length)
|
|
128
|
+
i++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function opHasAuxAt(insn) {
|
|
132
|
+
const op = insn & 0xff;
|
|
133
|
+
switch (op) {
|
|
134
|
+
case types_1.Op.GETGLOBAL:
|
|
135
|
+
case types_1.Op.SETGLOBAL:
|
|
136
|
+
case types_1.Op.GETIMPORT:
|
|
137
|
+
case types_1.Op.GETTABLEKS:
|
|
138
|
+
case types_1.Op.SETTABLEKS:
|
|
139
|
+
case types_1.Op.NAMECALL:
|
|
140
|
+
case types_1.Op.JUMPIFEQ:
|
|
141
|
+
case types_1.Op.JUMPIFLE:
|
|
142
|
+
case types_1.Op.JUMPIFLT:
|
|
143
|
+
case types_1.Op.JUMPIFNOTEQ:
|
|
144
|
+
case types_1.Op.JUMPIFNOTLE:
|
|
145
|
+
case types_1.Op.JUMPIFNOTLT:
|
|
146
|
+
case types_1.Op.NEWTABLE:
|
|
147
|
+
case types_1.Op.SETLIST:
|
|
148
|
+
case types_1.Op.FORGLOOP:
|
|
149
|
+
case types_1.Op.LOADKX:
|
|
150
|
+
case types_1.Op.FASTCALL2:
|
|
151
|
+
case types_1.Op.FASTCALL2K:
|
|
152
|
+
case types_1.Op.FASTCALL3:
|
|
153
|
+
case types_1.Op.JUMPXEQKNIL:
|
|
154
|
+
case types_1.Op.JUMPXEQKB:
|
|
155
|
+
case types_1.Op.JUMPXEQKN:
|
|
156
|
+
case types_1.Op.JUMPXEQKS:
|
|
157
|
+
case types_1.Op.GETUDATAKS:
|
|
158
|
+
case types_1.Op.SETUDATAKS:
|
|
159
|
+
case types_1.Op.NAMECALLUDATA:
|
|
160
|
+
case types_1.Op.NEWCLASSMEMBER:
|
|
161
|
+
case types_1.Op.CALLFB:
|
|
162
|
+
case types_1.Op.CMPPROTO:
|
|
163
|
+
case types_1.Op.NEWCLASS:
|
|
164
|
+
return true;
|
|
165
|
+
default:
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function parseProto(r, p, index, version, typesversion) {
|
|
170
|
+
const proto = p.protos[index];
|
|
171
|
+
proto.id = index;
|
|
172
|
+
let payloadStart = 0;
|
|
173
|
+
if (version >= 12) {
|
|
174
|
+
const protoSize = r.varint();
|
|
175
|
+
if (protoSize === null)
|
|
176
|
+
return false;
|
|
177
|
+
payloadStart = r.offset;
|
|
178
|
+
const maxstack = r.readU8();
|
|
179
|
+
const numparams = r.readU8();
|
|
180
|
+
const nups = r.readU8();
|
|
181
|
+
const isVararg = r.readU8();
|
|
182
|
+
if (maxstack === null || numparams === null || nups === null || isVararg === null)
|
|
183
|
+
return false;
|
|
184
|
+
proto.maxstack = maxstack;
|
|
185
|
+
proto.numparams = numparams;
|
|
186
|
+
proto.nups = nups;
|
|
187
|
+
proto.is_vararg = isVararg;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const maxstack = r.readU8();
|
|
191
|
+
const numparams = r.readU8();
|
|
192
|
+
const nups = r.readU8();
|
|
193
|
+
const isVararg = r.readU8();
|
|
194
|
+
if (maxstack === null || numparams === null || nups === null || isVararg === null)
|
|
195
|
+
return false;
|
|
196
|
+
proto.maxstack = maxstack;
|
|
197
|
+
proto.numparams = numparams;
|
|
198
|
+
proto.nups = nups;
|
|
199
|
+
proto.is_vararg = isVararg;
|
|
200
|
+
}
|
|
201
|
+
if (version >= 4) {
|
|
202
|
+
const flags = r.readU8();
|
|
203
|
+
if (flags === null)
|
|
204
|
+
return false;
|
|
205
|
+
proto.flags = flags;
|
|
206
|
+
const typesize = r.varint();
|
|
207
|
+
if (typesize === null)
|
|
208
|
+
return false;
|
|
209
|
+
if (typesize) {
|
|
210
|
+
if (!r.skip(typesize))
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const sizecode = r.varint();
|
|
215
|
+
if (sizecode === null)
|
|
216
|
+
return false;
|
|
217
|
+
proto.code = new Array(sizecode).fill(0);
|
|
218
|
+
for (let i = 0; i < sizecode; i++) {
|
|
219
|
+
const c = r.readU32();
|
|
220
|
+
if (c === null)
|
|
221
|
+
return false;
|
|
222
|
+
proto.code[i] = c >>> 0;
|
|
223
|
+
}
|
|
224
|
+
const sizek = r.varint();
|
|
225
|
+
if (sizek === null)
|
|
226
|
+
return false;
|
|
227
|
+
proto.k = new Array(sizek);
|
|
228
|
+
for (let j = 0; j < sizek; j++)
|
|
229
|
+
proto.k[j] = (0, types_1.makeConstant)();
|
|
230
|
+
for (let j = 0; j < sizek; j++) {
|
|
231
|
+
const tag = r.readU8();
|
|
232
|
+
if (tag === null)
|
|
233
|
+
return false;
|
|
234
|
+
const c = proto.k[j];
|
|
235
|
+
switch (tag) {
|
|
236
|
+
case 0:
|
|
237
|
+
c.kind = types_1.ConstKind.Nil;
|
|
238
|
+
break;
|
|
239
|
+
case 1: {
|
|
240
|
+
const v = r.readU8();
|
|
241
|
+
if (v === null)
|
|
242
|
+
return false;
|
|
243
|
+
c.kind = types_1.ConstKind.Boolean;
|
|
244
|
+
c.boolean = v !== 0;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case 2: {
|
|
248
|
+
const num = r.readF64();
|
|
249
|
+
if (num === null)
|
|
250
|
+
return false;
|
|
251
|
+
c.kind = types_1.ConstKind.Number;
|
|
252
|
+
c.number = num;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
case 3: {
|
|
256
|
+
const id = r.varint();
|
|
257
|
+
if (id === null)
|
|
258
|
+
return false;
|
|
259
|
+
if (id - 1 < 0 || id - 1 >= p.strings.length) {
|
|
260
|
+
console.error(`proto ${index} const ${j}: string id ${id} missing (have ${p.strings.length} strings) @${r.offset}`);
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
c.kind = types_1.ConstKind.String;
|
|
264
|
+
c.str = p.strings[id - 1];
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case 4: {
|
|
268
|
+
const id = r.readU32();
|
|
269
|
+
if (id === null)
|
|
270
|
+
return false;
|
|
271
|
+
c.kind = types_1.ConstKind.Import;
|
|
272
|
+
c.importId = id >>> 0;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case 5: {
|
|
276
|
+
const keys = r.varint();
|
|
277
|
+
if (keys === null)
|
|
278
|
+
return false;
|
|
279
|
+
c.kind = types_1.ConstKind.Table;
|
|
280
|
+
for (let i = 0; i < keys; i++) {
|
|
281
|
+
const key = r.varint();
|
|
282
|
+
if (key === null)
|
|
283
|
+
return false;
|
|
284
|
+
const e = { key: key | 0, value: -1 };
|
|
285
|
+
c.table.push(e);
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case 6: {
|
|
290
|
+
const fid = r.varint();
|
|
291
|
+
if (fid === null)
|
|
292
|
+
return false;
|
|
293
|
+
if (fid >= p.protos.length)
|
|
294
|
+
return false;
|
|
295
|
+
c.kind = types_1.ConstKind.Closure;
|
|
296
|
+
c.closureProto = fid | 0;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
case 7: {
|
|
300
|
+
const x = r.readF32();
|
|
301
|
+
const y = r.readF32();
|
|
302
|
+
const z = r.readF32();
|
|
303
|
+
const w = r.readF32();
|
|
304
|
+
if (x === null || y === null || z === null || w === null)
|
|
305
|
+
return false;
|
|
306
|
+
c.kind = types_1.ConstKind.Vector;
|
|
307
|
+
c.vec = [x, y, z];
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case 8: {
|
|
311
|
+
const keys = r.varint();
|
|
312
|
+
if (keys === null)
|
|
313
|
+
return false;
|
|
314
|
+
c.kind = types_1.ConstKind.TableWithConstants;
|
|
315
|
+
for (let i = 0; i < keys; i++) {
|
|
316
|
+
const key = r.varint();
|
|
317
|
+
const value = r.readI32();
|
|
318
|
+
if (key === null || value === null)
|
|
319
|
+
return false;
|
|
320
|
+
const e = { key: key | 0, value: value | 0 };
|
|
321
|
+
c.table.push(e);
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case 9: {
|
|
326
|
+
const isNegative = r.readU8();
|
|
327
|
+
const magnitude = r.varint64();
|
|
328
|
+
if (isNegative === null || magnitude === null)
|
|
329
|
+
return false;
|
|
330
|
+
c.kind = types_1.ConstKind.Integer;
|
|
331
|
+
c.integer = isNegative ? (~magnitude + 1) | 0 : magnitude | 0;
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case 10: {
|
|
335
|
+
const a = r.varint();
|
|
336
|
+
const b = r.varint();
|
|
337
|
+
const count = r.varint();
|
|
338
|
+
if (a === null || b === null || count === null)
|
|
339
|
+
return false;
|
|
340
|
+
for (let i = 0; i < count; i++) {
|
|
341
|
+
const mid = r.varint();
|
|
342
|
+
if (mid === null)
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
c.kind = types_1.ConstKind.ClassShape;
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
case 11: {
|
|
349
|
+
const x = r.readF64();
|
|
350
|
+
const y = r.readF64();
|
|
351
|
+
const z = r.readF64();
|
|
352
|
+
const w = r.readF64();
|
|
353
|
+
if (x === null || y === null || z === null || w === null)
|
|
354
|
+
return false;
|
|
355
|
+
c.kind = types_1.ConstKind.VectorD;
|
|
356
|
+
c.vec = [x, y, z];
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
default:
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const sizep = r.varint();
|
|
364
|
+
if (sizep === null)
|
|
365
|
+
return false;
|
|
366
|
+
proto.children = new Array(sizep);
|
|
367
|
+
for (let i = 0; i < sizep; i++) {
|
|
368
|
+
const fid = r.varint();
|
|
369
|
+
if (fid === null)
|
|
370
|
+
return false;
|
|
371
|
+
if (fid >= p.protos.length)
|
|
372
|
+
return false;
|
|
373
|
+
proto.children[i] = fid >>> 0;
|
|
374
|
+
}
|
|
375
|
+
const linedefined = r.varint();
|
|
376
|
+
if (linedefined === null)
|
|
377
|
+
return false;
|
|
378
|
+
proto.linedefined = linedefined | 0;
|
|
379
|
+
{
|
|
380
|
+
const nameid = r.varint();
|
|
381
|
+
if (nameid === null)
|
|
382
|
+
return false;
|
|
383
|
+
const s = p.strings[nameid - 1];
|
|
384
|
+
if (s)
|
|
385
|
+
proto.debugname = s;
|
|
386
|
+
}
|
|
387
|
+
const lineinfo = r.readU8();
|
|
388
|
+
if (lineinfo === null)
|
|
389
|
+
return false;
|
|
390
|
+
if (lineinfo) {
|
|
391
|
+
const linegaplog2 = r.readU8();
|
|
392
|
+
if (linegaplog2 === null)
|
|
393
|
+
return false;
|
|
394
|
+
const bytes = new Array(sizecode);
|
|
395
|
+
for (let i = 0; i < sizecode; i++) {
|
|
396
|
+
const b = r.readU8();
|
|
397
|
+
if (b === null)
|
|
398
|
+
return false;
|
|
399
|
+
bytes[i] = b;
|
|
400
|
+
}
|
|
401
|
+
const intervals = ((sizecode - 1) >> linegaplog2) + 1;
|
|
402
|
+
const abs = new Array(intervals);
|
|
403
|
+
for (let i = 0; i < intervals; i++) {
|
|
404
|
+
const v = r.readI32();
|
|
405
|
+
if (v === null)
|
|
406
|
+
return false;
|
|
407
|
+
abs[i] = v;
|
|
408
|
+
}
|
|
409
|
+
proto.lineinfo = new Array(sizecode).fill(-1);
|
|
410
|
+
let groupLine = 0;
|
|
411
|
+
let j = 0;
|
|
412
|
+
for (let group = 0; group < intervals && j < sizecode; group++) {
|
|
413
|
+
groupLine += abs[group];
|
|
414
|
+
const groupEnd = Math.min(sizecode, (group + 1) << linegaplog2);
|
|
415
|
+
let running = 0;
|
|
416
|
+
for (; j < groupEnd; j++) {
|
|
417
|
+
running += bytes[j];
|
|
418
|
+
proto.lineinfo[j] = groupLine + running;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const debuginfo = r.readU8();
|
|
423
|
+
if (debuginfo === null)
|
|
424
|
+
return false;
|
|
425
|
+
if (debuginfo) {
|
|
426
|
+
const sizelocvars = r.varint();
|
|
427
|
+
if (sizelocvars === null)
|
|
428
|
+
return false;
|
|
429
|
+
proto.locvars = [];
|
|
430
|
+
for (let i = 0; i < sizelocvars; i++) {
|
|
431
|
+
const nameid = r.varint();
|
|
432
|
+
const start = r.varint();
|
|
433
|
+
const end = r.varint();
|
|
434
|
+
const reg = r.readU8();
|
|
435
|
+
if (nameid === null || start === null || end === null || reg === null)
|
|
436
|
+
return false;
|
|
437
|
+
const lv = { name: "", startpc: start | 0, endpc: end | 0, reg };
|
|
438
|
+
const s = p.strings[nameid - 1];
|
|
439
|
+
if (s)
|
|
440
|
+
lv.name = s;
|
|
441
|
+
proto.locvars.push(lv);
|
|
442
|
+
}
|
|
443
|
+
const sizeupvalues = r.varint();
|
|
444
|
+
if (sizeupvalues === null)
|
|
445
|
+
return false;
|
|
446
|
+
proto.upnames = [];
|
|
447
|
+
for (let i = 0; i < sizeupvalues; i++) {
|
|
448
|
+
const nameid = r.varint();
|
|
449
|
+
if (nameid === null)
|
|
450
|
+
return false;
|
|
451
|
+
proto.upnames.push(p.strings[nameid - 1] || "");
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (version >= 11) {
|
|
455
|
+
const fbsize = r.varint();
|
|
456
|
+
if (fbsize === null)
|
|
457
|
+
return false;
|
|
458
|
+
for (let i = 0; i < fbsize; i++) {
|
|
459
|
+
const slottype = r.readU8();
|
|
460
|
+
const pc = r.varint();
|
|
461
|
+
if (slottype === null || pc === null)
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (version >= 12) {
|
|
466
|
+
if ((proto.flags & (1 << 3)) !== 0) {
|
|
467
|
+
const cost = r.varint64();
|
|
468
|
+
if (cost === null)
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
function parseBytecode(data, out, err) {
|
|
475
|
+
out = Object.assign(out, (0, types_1.makeProgram)());
|
|
476
|
+
const r = new Reader(data);
|
|
477
|
+
const versionRaw = r.readU8();
|
|
478
|
+
if (versionRaw === null) {
|
|
479
|
+
err.msg = "bytecode is too small to be valid";
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
const version = versionRaw >>> 0;
|
|
483
|
+
if (version === 0) {
|
|
484
|
+
if (r.offset < data.length) {
|
|
485
|
+
err.msg = new TextDecoder().decode(data.subarray(r.offset));
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
err.msg = "failed to load bytecode";
|
|
489
|
+
}
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
if ((version < LBC_VERSION_MIN || version > LBC_VERSION_MAX) && version !== LBC_VERSION_CLASSES) {
|
|
493
|
+
err.msg = `bytecode version mismatch (expected [3..13], got ${version})`;
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
let typesversion = 0;
|
|
497
|
+
if (version >= 4) {
|
|
498
|
+
const tv = r.readU8();
|
|
499
|
+
if (tv === null) {
|
|
500
|
+
err.msg = "unexpected end of bytecode";
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
typesversion = tv >>> 0;
|
|
504
|
+
if (typesversion < 1 || typesversion > 3) {
|
|
505
|
+
err.msg = `bytecode type info version mismatch (expected [1..3], got ${typesversion})`;
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const stringCount = r.varint();
|
|
510
|
+
if (stringCount === null) {
|
|
511
|
+
err.msg = "unexpected end of bytecode reading string count";
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
out.strings = new Array(stringCount);
|
|
515
|
+
for (let i = 0; i < stringCount; i++) {
|
|
516
|
+
const length = r.varint();
|
|
517
|
+
if (length === null) {
|
|
518
|
+
err.msg = "unexpected end of bytecode reading string length";
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
const bytes = r.take(length);
|
|
522
|
+
if (bytes === null) {
|
|
523
|
+
err.msg = "unexpected end of bytecode reading string data";
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
out.strings[i] = new TextDecoder().decode(bytes);
|
|
527
|
+
}
|
|
528
|
+
if (typesversion === 3) {
|
|
529
|
+
let index = r.readU8();
|
|
530
|
+
if (index === null) {
|
|
531
|
+
err.msg = "unexpected end of bytecode reading userdata remap";
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
while (index !== 0) {
|
|
535
|
+
const nameid = r.varint();
|
|
536
|
+
if (nameid === null) {
|
|
537
|
+
err.msg = "unexpected end of bytecode reading userdata remap name";
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
if (nameid === 0 || nameid > out.strings.length) {
|
|
541
|
+
err.msg = "invalid string reference in userdata remap table";
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
index = r.readU8();
|
|
545
|
+
if (index === null) {
|
|
546
|
+
err.msg = "unexpected end of bytecode reading userdata remap index";
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const protoCount = r.varint();
|
|
552
|
+
if (protoCount === null) {
|
|
553
|
+
err.msg = "unexpected end of bytecode reading proto count";
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
out.protos = new Array(protoCount);
|
|
557
|
+
for (let i = 0; i < protoCount; i++)
|
|
558
|
+
out.protos[i] = (0, types_1.makeProto)();
|
|
559
|
+
for (let i = 0; i < protoCount; i++) {
|
|
560
|
+
if (!parseProto(r, out, i, version, typesversion)) {
|
|
561
|
+
err.msg = `failed to parse proto #${i}`;
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const mainid = r.varint();
|
|
566
|
+
if (mainid === null) {
|
|
567
|
+
err.msg = "unexpected end of bytecode reading main proto id";
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
if (mainid >= out.protos.length) {
|
|
571
|
+
err.msg = "invalid main proto id";
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
out.main = mainid | 0;
|
|
575
|
+
const key = recoverOpcodeKey(out.protos[out.main].code);
|
|
576
|
+
if (key !== 1) {
|
|
577
|
+
for (const proto of out.protos)
|
|
578
|
+
decodeOpcodeShuffle(proto.code, key);
|
|
579
|
+
}
|
|
580
|
+
out.encodingKey = key | 0;
|
|
581
|
+
return true;
|
|
582
|
+
}
|