jsilnet 0.0.0-stage → 0.1.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.
@@ -0,0 +1,2530 @@
1
+ import { FormatError, UnsupportedFormatError } from "../binary/errors.js";
2
+
3
+ const msfMagic = new TextEncoder().encode("Microsoft C/C++ MSF 7.00\r\n\u001aDS\0\0\0");
4
+ const absentStream = 0xffffffff;
5
+ const codeViewScopeSymbolKinds = new Set([
6
+ 0x1102, 0x1103, 0x1104, 0x110f, 0x1110, 0x112a, 0x112b,
7
+ 0x1132, 0x1146, 0x1147, 0x114d, 0x115d
8
+ ]);
9
+ const codeViewNumericFormats = new Map([
10
+ [0x8000, [1, (view, offset) => view.getInt8(offset)]],
11
+ [0x8001, [2, (view, offset) => view.getInt16(offset, true)]],
12
+ [0x8002, [2, (view, offset) => view.getUint16(offset, true)]],
13
+ [0x8003, [4, (view, offset) => view.getInt32(offset, true)]],
14
+ [0x8004, [4, (view, offset) => view.getUint32(offset, true)]],
15
+ [0x8005, [4, (view, offset) => view.getFloat32(offset, true)]],
16
+ [0x8006, [8, (view, offset) => view.getFloat64(offset, true)]],
17
+ [0x8009, [8, (view, offset) => view.getBigInt64(offset, true)]],
18
+ [0x800a, [8, (view, offset) => view.getBigUint64(offset, true)]]
19
+ ]);
20
+
21
+ function formatGuid(bytes) {
22
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
23
+ const first = view.getUint32(0, true).toString(16).padStart(8, "0");
24
+ const second = view.getUint16(4, true).toString(16).padStart(4, "0");
25
+ const third = view.getUint16(6, true).toString(16).padStart(4, "0");
26
+ const tail = [...bytes.subarray(8)]
27
+ .map((byte) => byte.toString(16).padStart(2, "0"))
28
+ .join("");
29
+ return `${first}-${second}-${third}-${tail.slice(0, 4)}-${tail.slice(4)}`;
30
+ }
31
+
32
+ function parsePdbNamedStreams(infoStream, streams) {
33
+ let offset = 28;
34
+ const readU32 = (context) => {
35
+ if (offset > infoStream.length - 4) {
36
+ throw new FormatError(`Classic PDB information stream is truncated at ${context}`);
37
+ }
38
+ const value = new DataView(infoStream.buffer, infoStream.byteOffset + offset, 4)
39
+ .getUint32(0, true);
40
+ offset += 4;
41
+ return value;
42
+ };
43
+ const stringBufferSize = readU32("named-stream string buffer size");
44
+ if (stringBufferSize > infoStream.length - offset) {
45
+ throw new FormatError("Classic PDB named-stream string buffer exceeds its information stream");
46
+ }
47
+ const stringBuffer = infoStream.subarray(offset, offset + stringBufferSize);
48
+ offset += stringBufferSize;
49
+ const size = readU32("named-stream hash size");
50
+ const capacity = readU32("named-stream hash capacity");
51
+ if (capacity === 0 || size > Math.floor(capacity * 2 / 3) + 1) {
52
+ throw new FormatError("Classic PDB named-stream hash table size or capacity is invalid");
53
+ }
54
+ const readBitVector = (label) => {
55
+ const wordCount = readU32(`${label} word count`);
56
+ if (wordCount > Math.ceil(capacity / 32)) {
57
+ throw new FormatError(`Classic PDB named-stream ${label} bit vector exceeds its capacity`);
58
+ }
59
+ const indices = [];
60
+ for (let wordIndex = 0; wordIndex < wordCount; wordIndex++) {
61
+ const word = readU32(`${label} bit vector`);
62
+ for (let bit = 0; bit < 32; bit++) {
63
+ if ((word & (1 << bit)) === 0) continue;
64
+ const index = wordIndex * 32 + bit;
65
+ if (index >= capacity) {
66
+ throw new FormatError(`Classic PDB named-stream ${label} bit vector sets an out-of-range slot`);
67
+ }
68
+ indices.push(index);
69
+ }
70
+ }
71
+ return indices;
72
+ };
73
+ const present = readBitVector("present");
74
+ const deleted = readBitVector("deleted");
75
+ const deletedSlots = new Set(deleted);
76
+ if (present.length !== size || present.some((index) => deletedSlots.has(index))) {
77
+ throw new FormatError("Classic PDB named-stream hash bit vectors are inconsistent");
78
+ }
79
+ const namedStreams = new Map();
80
+ for (let index = 0; index < present.length; index++) {
81
+ const nameOffset = readU32("named-stream name offset");
82
+ const streamIndex = readU32("named-stream index");
83
+ if (nameOffset >= stringBuffer.length ||
84
+ streamIndex >= streams.length || streams[streamIndex].bytes === null) {
85
+ throw new FormatError("Classic PDB named-stream entry references missing data");
86
+ }
87
+ const terminator = stringBuffer.indexOf(0, nameOffset);
88
+ if (terminator < 0) {
89
+ throw new FormatError("Classic PDB named-stream name is not null-terminated");
90
+ }
91
+ let name;
92
+ try {
93
+ name = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true })
94
+ .decode(stringBuffer.subarray(nameOffset, terminator));
95
+ } catch (error) {
96
+ throw new FormatError(`Classic PDB named-stream name is invalid UTF-8: ${error.message}`);
97
+ }
98
+ if (namedStreams.has(name)) throw new FormatError("Classic PDB named-stream map contains duplicate names");
99
+ namedStreams.set(name, streamIndex);
100
+ }
101
+ readU32("named-stream map terminator");
102
+ if ((infoStream.length - offset) % 4 !== 0) {
103
+ throw new FormatError("Classic PDB information stream has a truncated feature signature");
104
+ }
105
+ const featureSignatures = [];
106
+ while (offset < infoStream.length) featureSignatures.push(readU32("feature signature"));
107
+ return { namedStreams, featureSignatures };
108
+ }
109
+
110
+ function parseDbiModules(dbiStream, moduleInfoSize, streams, pdbStrings) {
111
+ const modules = [];
112
+ let offset = 64;
113
+ const end = offset + moduleInfoSize;
114
+ const view = new DataView(dbiStream.buffer, dbiStream.byteOffset, dbiStream.byteLength);
115
+ const readName = (context) => {
116
+ const start = offset;
117
+ while (offset < end && dbiStream[offset] !== 0) offset++;
118
+ if (offset >= end) throw new FormatError(`Classic PDB DBI ${context} is not null-terminated`);
119
+ const bytes = dbiStream.subarray(start, offset++);
120
+ try {
121
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
122
+ } catch (error) {
123
+ throw new FormatError(`Classic PDB DBI ${context} is invalid UTF-8: ${error.message}`);
124
+ }
125
+ };
126
+ while (offset < end) {
127
+ if (end - offset < 64) {
128
+ throw new FormatError("Classic PDB DBI module-info record is truncated");
129
+ }
130
+ const recordStart = offset;
131
+ const streamIndex = view.getUint16(recordStart + 34, true);
132
+ const symbolBytes = view.getUint32(recordStart + 36, true);
133
+ const c11Bytes = view.getUint32(recordStart + 40, true);
134
+ const c13Bytes = view.getUint32(recordStart + 44, true);
135
+ const fileCount = view.getUint16(recordStart + 48, true);
136
+ offset += 64;
137
+ const moduleName = readName("module name");
138
+ const objectName = readName("object name");
139
+ const alignedOffset = (offset + 3) & ~3;
140
+ if (alignedOffset > end) throw new FormatError("Classic PDB DBI module-info padding exceeds its substream");
141
+ offset = alignedOffset;
142
+ if (streamIndex !== 0xffff) {
143
+ const moduleStream = streams[streamIndex]?.bytes;
144
+ if (!moduleStream) {
145
+ throw new FormatError("Classic PDB DBI module references a missing stream");
146
+ }
147
+ if (symbolBytes + c11Bytes + c13Bytes > moduleStream.length) {
148
+ throw new FormatError("Classic PDB DBI module data lengths exceed its module stream");
149
+ }
150
+ } else if (symbolBytes !== 0 || c11Bytes !== 0 || c13Bytes !== 0) {
151
+ throw new FormatError("Classic PDB DBI module has data lengths but no module stream");
152
+ }
153
+ const symbols = symbolBytes === 0
154
+ ? null
155
+ : parseCodeViewSymbolRecords(streams[streamIndex].bytes.subarray(0, symbolBytes));
156
+ const c13Offset = symbolBytes + c11Bytes;
157
+ const c13 = c13Bytes === 0
158
+ ? null
159
+ : parseCodeViewC13Subsections(
160
+ streams[streamIndex].bytes.subarray(c13Offset, c13Offset + c13Bytes),
161
+ pdbStrings
162
+ );
163
+ modules.push({
164
+ moduleName,
165
+ objectName,
166
+ moduleInfoOffset: recordStart,
167
+ streamIndex,
168
+ symbolBytes,
169
+ c11Bytes,
170
+ c13Bytes,
171
+ symbols,
172
+ c13,
173
+ fileCount
174
+ });
175
+ }
176
+ return modules;
177
+ }
178
+
179
+ function parseDbiFileInfo(dbiStream, fileInfoOffset, fileInfoSize, modules) {
180
+ if (fileInfoSize === 0) return modules.map((module) => ({ ...module, sourceFiles: [] }));
181
+ if (fileInfoSize < 4) throw new FormatError("Classic PDB DBI file-info substream is truncated");
182
+ const view = new DataView(dbiStream.buffer, dbiStream.byteOffset, dbiStream.byteLength);
183
+ let offset = fileInfoOffset;
184
+ const end = fileInfoOffset + fileInfoSize;
185
+ const moduleCount = view.getUint16(offset, true);
186
+ offset += 4;
187
+ if (moduleCount !== modules.length) {
188
+ throw new FormatError("Classic PDB DBI file-info module count does not match module descriptors");
189
+ }
190
+ if (moduleCount > Math.floor((end - offset) / 4)) {
191
+ throw new FormatError("Classic PDB DBI file-info module arrays exceed their substream");
192
+ }
193
+ const moduleIndices = [];
194
+ for (let index = 0; index < moduleCount; index++) {
195
+ moduleIndices.push(view.getUint16(offset, true));
196
+ offset += 2;
197
+ }
198
+ const fileCounts = [];
199
+ let totalFiles = 0;
200
+ for (let index = 0; index < moduleCount; index++) {
201
+ const count = view.getUint16(offset, true);
202
+ offset += 2;
203
+ fileCounts.push(count);
204
+ totalFiles += count;
205
+ }
206
+ if (totalFiles > Math.floor((end - offset) / 4)) {
207
+ throw new FormatError("Classic PDB DBI file-name offsets exceed their substream");
208
+ }
209
+ const fileNameOffsets = [];
210
+ for (let index = 0; index < totalFiles; index++) {
211
+ fileNameOffsets.push(view.getUint32(offset, true));
212
+ offset += 4;
213
+ }
214
+ const names = dbiStream.subarray(offset, end);
215
+ const filesByModule = modules.map(() => []);
216
+ let fileIndex = 0;
217
+ for (let index = 0; index < moduleCount; index++) {
218
+ if (modules[index].fileCount !== fileCounts[index]) {
219
+ throw new FormatError("Classic PDB DBI module file count disagrees with file-info data");
220
+ }
221
+ for (let count = 0; count < fileCounts[index]; count++) {
222
+ const nameOffset = fileNameOffsets[fileIndex++];
223
+ if (nameOffset >= names.length) {
224
+ throw new FormatError("Classic PDB DBI source-file name offset is out of range");
225
+ }
226
+ const terminator = names.indexOf(0, nameOffset);
227
+ if (terminator < 0) {
228
+ throw new FormatError("Classic PDB DBI source-file name is not null-terminated");
229
+ }
230
+ try {
231
+ filesByModule[index].push(
232
+ new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(names.subarray(nameOffset, terminator))
233
+ );
234
+ } catch (error) {
235
+ throw new FormatError(`Classic PDB DBI source-file name is invalid UTF-8: ${error.message}`);
236
+ }
237
+ }
238
+ }
239
+ return modules.map((module, index) => ({
240
+ ...module,
241
+ fileInfoModuleIndex: moduleIndices[index],
242
+ sourceFiles: filesByModule[index]
243
+ }));
244
+ }
245
+
246
+ function readCodeViewName(bytes, offset, context, allowZeroPadding = false) {
247
+ const end = bytes.indexOf(0, offset);
248
+ if (end < 0) throw new FormatError(`Classic PDB ${context} name is not null-terminated`);
249
+ const padding = bytes.subarray(end + 1);
250
+ if (padding.length > 0) {
251
+ const paddingLength = padding[0] - 0xf0;
252
+ const validPadding = paddingLength === padding.length &&
253
+ padding.every((byte, index) => byte === 0xf0 + padding.length - index);
254
+ const zeroPadding = allowZeroPadding && padding.length <= 3 &&
255
+ padding.every((byte) => byte === 0);
256
+ if (!zeroPadding && !validPadding) {
257
+ throw new FormatError(`Classic PDB ${context} name has invalid CodeView padding`);
258
+ }
259
+ }
260
+ try {
261
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes.subarray(offset, end));
262
+ } catch (error) {
263
+ throw new FormatError(`Classic PDB ${context} name is invalid UTF-8: ${error.message}`);
264
+ }
265
+ }
266
+
267
+ function decodeCodeViewNumeric(data, offset) {
268
+ if (offset > data.length - 2) {
269
+ throw new FormatError("Classic PDB constant symbol numeric value is truncated");
270
+ }
271
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
272
+ const leaf = view.getUint16(offset, true);
273
+ if (leaf < 0x8000) return { value: leaf, size: 2 };
274
+ const format = codeViewNumericFormats.get(leaf);
275
+ if (!format) return null;
276
+ const valueOffset = offset + 2;
277
+ if (valueOffset > data.length - format[0]) {
278
+ throw new FormatError("Classic PDB constant symbol numeric value is truncated");
279
+ }
280
+ return { value: format[1](view, valueOffset), size: 2 + format[0] };
281
+ }
282
+
283
+ function encodeCodeViewNumeric(value) {
284
+ if (typeof value === "number") {
285
+ if (!Number.isFinite(value)) {
286
+ throw new TypeError("Classic PDB constant-symbol values must be finite numbers or BigInts");
287
+ }
288
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
289
+ throw new RangeError("Classic PDB constant-symbol integer numbers must be safe integers");
290
+ }
291
+ if (!Number.isInteger(value) || Object.is(value, -0)) {
292
+ const bytes = new Uint8Array(10);
293
+ const view = new DataView(bytes.buffer);
294
+ view.setUint16(0, 0x8006, true);
295
+ view.setFloat64(2, value, true);
296
+ return bytes;
297
+ }
298
+ value = BigInt(value);
299
+ } else if (typeof value !== "bigint") {
300
+ throw new TypeError("Classic PDB constant-symbol values must be finite numbers or BigInts");
301
+ }
302
+
303
+ const encodeLeaf = (leaf, size, write) => {
304
+ const bytes = new Uint8Array(size + 2);
305
+ const view = new DataView(bytes.buffer);
306
+ view.setUint16(0, leaf, true);
307
+ write(view, 2);
308
+ return bytes;
309
+ };
310
+ if (value >= 0n && value <= 0x7fffn) {
311
+ const bytes = new Uint8Array(2);
312
+ new DataView(bytes.buffer).setUint16(0, Number(value), true);
313
+ return bytes;
314
+ }
315
+ if (value < 0n && value >= -0x80n) {
316
+ return encodeLeaf(0x8000, 1, (view, offset) => view.setInt8(offset, Number(value)));
317
+ }
318
+ if (value < 0n && value >= -0x8000n) {
319
+ return encodeLeaf(0x8001, 2, (view, offset) => view.setInt16(offset, Number(value), true));
320
+ }
321
+ if (value >= 0n && value <= 0xffffn) {
322
+ return encodeLeaf(0x8002, 2, (view, offset) => view.setUint16(offset, Number(value), true));
323
+ }
324
+ if (value < 0n && value >= -0x80000000n) {
325
+ return encodeLeaf(0x8003, 4, (view, offset) => view.setInt32(offset, Number(value), true));
326
+ }
327
+ if (value >= 0n && value <= 0xffffffffn) {
328
+ return encodeLeaf(0x8004, 4, (view, offset) => view.setUint32(offset, Number(value), true));
329
+ }
330
+ if (value < 0n && value >= -(1n << 63n)) {
331
+ return encodeLeaf(0x8009, 8, (view, offset) => view.setBigInt64(offset, value, true));
332
+ }
333
+ if (value >= 0n && value <= (1n << 64n) - 1n) {
334
+ return encodeLeaf(0x800a, 8, (view, offset) => view.setBigUint64(offset, value, true));
335
+ }
336
+ throw new RangeError("Classic PDB constant-symbol integer values must fit in 64 bits");
337
+ }
338
+
339
+ function getCodeViewSymbolNameOffset(record) {
340
+ if (record.kind === 0x1107) {
341
+ const numeric = decodeCodeViewNumeric(record.data, 4);
342
+ return numeric ? 4 + numeric.size : null;
343
+ }
344
+ return new Map([
345
+ [0x1101, 4],
346
+ [0x1102, 21],
347
+ [0x1103, 18],
348
+ [0x1104, 18],
349
+ [0x1105, 7],
350
+ [0x1106, 6],
351
+ [0x1108, 4],
352
+ [0x110b, 8],
353
+ [0x110c, 10],
354
+ [0x110d, 10],
355
+ [0x110e, 10],
356
+ [0x110f, 35],
357
+ [0x1110, 35],
358
+ [0x1111, 10],
359
+ [0x1112, 10],
360
+ [0x1113, 10],
361
+ [0x111c, 10],
362
+ [0x111d, 10],
363
+ [0x1124, 0],
364
+ [0x112a, 37],
365
+ [0x112b, 37],
366
+ [0x113e, 6],
367
+ [0x1146, 35],
368
+ [0x1147, 35]
369
+ ]).get(record.kind) ?? null;
370
+ }
371
+
372
+ function decodeCodeViewSymbol(kind, data) {
373
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
374
+ if ((kind === 0x0006 || kind === 0x114e) && data.length === 0) {
375
+ return { kind: "scopeEnd" };
376
+ }
377
+ if (kind === 0x1101) {
378
+ if (data.length < 6) throw new FormatError("Classic PDB object-name symbol is truncated");
379
+ return {
380
+ kind: "objectName",
381
+ signature: view.getUint32(0, true),
382
+ name: readCodeViewName(data, 4, "object-name")
383
+ };
384
+ }
385
+ if (kind === 0x1103) {
386
+ if (data.length < 19) throw new FormatError("Classic PDB block symbol is truncated");
387
+ return {
388
+ kind: "block",
389
+ parent: view.getUint32(0, true),
390
+ end: view.getUint32(4, true),
391
+ codeSize: view.getUint32(8, true),
392
+ offset: view.getUint32(12, true),
393
+ segment: view.getUint16(16, true),
394
+ name: readCodeViewName(data, 18, "block", true)
395
+ };
396
+ }
397
+ if (kind === 0x1104) {
398
+ if (data.length < 19) throw new FormatError("Classic PDB with-scope symbol is truncated");
399
+ return {
400
+ kind: "withScope",
401
+ parent: view.getUint32(0, true),
402
+ end: view.getUint32(4, true),
403
+ codeSize: view.getUint32(8, true),
404
+ offset: view.getUint32(12, true),
405
+ segment: view.getUint16(16, true),
406
+ expression: readCodeViewName(data, 18, "with-scope")
407
+ };
408
+ }
409
+ if (kind === 0x110e) {
410
+ if (data.length < 11) throw new FormatError("Classic PDB S_PUB32 record is truncated");
411
+ return {
412
+ kind: "public",
413
+ flags: view.getUint32(0, true),
414
+ offset: view.getUint32(4, true),
415
+ segment: view.getUint16(8, true),
416
+ name: readCodeViewName(data, 10, "S_PUB32")
417
+ };
418
+ }
419
+ if ([0x110c, 0x110d, 0x111c, 0x111d].includes(kind)) {
420
+ if (data.length < 11) throw new FormatError("Classic PDB data symbol record is truncated");
421
+ return {
422
+ kind: "data",
423
+ typeIndex: view.getUint32(0, true),
424
+ offset: view.getUint32(4, true),
425
+ segment: view.getUint16(8, true),
426
+ name: readCodeViewName(data, 10, "data symbol")
427
+ };
428
+ }
429
+ if (kind === 0x1107) {
430
+ if (data.length < 6) throw new FormatError("Classic PDB constant symbol is truncated");
431
+ const numeric = decodeCodeViewNumeric(data, 4);
432
+ if (!numeric) return null;
433
+ return {
434
+ kind: "constant",
435
+ typeIndex: view.getUint32(0, true),
436
+ value: numeric.value,
437
+ name: readCodeViewName(data, 4 + numeric.size, "constant symbol")
438
+ };
439
+ }
440
+ if (kind === 0x1112 || kind === 0x1113) {
441
+ if (data.length < 11) throw new FormatError("Classic PDB thread-data symbol is truncated");
442
+ return {
443
+ kind: "threadData",
444
+ typeIndex: view.getUint32(0, true),
445
+ offset: view.getUint32(4, true),
446
+ segment: view.getUint16(8, true),
447
+ name: readCodeViewName(data, 10, "thread-data symbol")
448
+ };
449
+ }
450
+ if (kind === 0x1102) {
451
+ if (data.length < 22) throw new FormatError("Classic PDB thunk symbol is truncated");
452
+ return {
453
+ kind: "thunk",
454
+ parent: view.getUint32(0, true),
455
+ end: view.getUint32(4, true),
456
+ next: view.getUint32(8, true),
457
+ offset: view.getUint32(12, true),
458
+ segment: view.getUint16(16, true),
459
+ length: view.getUint16(18, true),
460
+ ordinal: data[20],
461
+ name: readCodeViewName(data, 21, "thunk symbol")
462
+ };
463
+ }
464
+ if (kind === 0x1012) {
465
+ if (data.length < 26) throw new FormatError("Classic PDB frame-procedure symbol is truncated");
466
+ return {
467
+ kind: "frameProcedure",
468
+ frameSize: view.getUint32(0, true),
469
+ paddingSize: view.getUint32(4, true),
470
+ paddingOffset: view.getUint32(8, true),
471
+ savedRegisterSize: view.getUint32(12, true),
472
+ exceptionHandlerOffset: view.getUint32(16, true),
473
+ exceptionHandlerSegment: view.getUint16(20, true),
474
+ flags: view.getUint32(22, true)
475
+ };
476
+ }
477
+ if (kind === 0x1124) {
478
+ return {
479
+ kind: "namespace",
480
+ name: readCodeViewName(data, 0, "namespace")
481
+ };
482
+ }
483
+ if (kind === 0x113c) {
484
+ if (data.length < 23) throw new FormatError("Classic PDB compile-info symbol is truncated");
485
+ return {
486
+ kind: "compileInfo",
487
+ flags: view.getUint32(0, true),
488
+ machine: view.getUint16(4, true),
489
+ frontEndVersion: {
490
+ major: view.getUint16(6, true),
491
+ minor: view.getUint16(8, true),
492
+ build: view.getUint16(10, true),
493
+ qfe: view.getUint16(12, true)
494
+ },
495
+ backEndVersion: {
496
+ major: view.getUint16(14, true),
497
+ minor: view.getUint16(16, true),
498
+ build: view.getUint16(18, true),
499
+ qfe: view.getUint16(20, true)
500
+ },
501
+ version: readCodeViewName(data, 22, "compile-info", true)
502
+ };
503
+ }
504
+ if ([0x110f, 0x1110, 0x1146, 0x1147].includes(kind)) {
505
+ if (data.length < 36) throw new FormatError("Classic PDB procedure symbol record is truncated");
506
+ return {
507
+ kind: "procedure",
508
+ parent: view.getUint32(0, true),
509
+ end: view.getUint32(4, true),
510
+ next: view.getUint32(8, true),
511
+ codeSize: view.getUint32(12, true),
512
+ debugStart: view.getUint32(16, true),
513
+ debugEnd: view.getUint32(20, true),
514
+ typeIndex: view.getUint32(24, true),
515
+ offset: view.getUint32(28, true),
516
+ segment: view.getUint16(32, true),
517
+ flags: data[34],
518
+ name: readCodeViewName(data, 35, "procedure")
519
+ };
520
+ }
521
+ if (kind === 0x112a || kind === 0x112b) {
522
+ if (data.length < 38) throw new FormatError("Classic PDB managed procedure symbol is truncated");
523
+ return {
524
+ kind: "managedProcedure",
525
+ parent: view.getUint32(0, true),
526
+ end: view.getUint32(4, true),
527
+ next: view.getUint32(8, true),
528
+ codeSize: view.getUint32(12, true),
529
+ debugStart: view.getUint32(16, true),
530
+ debugEnd: view.getUint32(20, true),
531
+ methodToken: view.getUint32(24, true),
532
+ offset: view.getUint32(28, true),
533
+ segment: view.getUint16(32, true),
534
+ flags: data[34],
535
+ returnRegister: view.getUint16(35, true),
536
+ name: readCodeViewName(data, 37, "managed procedure", true)
537
+ };
538
+ }
539
+ if (kind === 0x113e) {
540
+ if (data.length < 7) throw new FormatError("Classic PDB local symbol is truncated");
541
+ return {
542
+ kind: "local",
543
+ typeIndex: view.getUint32(0, true),
544
+ flags: view.getUint16(4, true),
545
+ name: readCodeViewName(data, 6, "local")
546
+ };
547
+ }
548
+ if (kind === 0x110b) {
549
+ if (data.length < 9) throw new FormatError("Classic PDB BP-relative symbol is truncated");
550
+ return {
551
+ kind: "bpRelative",
552
+ offset: view.getInt32(0, true),
553
+ typeIndex: view.getUint32(4, true),
554
+ name: readCodeViewName(data, 8, "BP-relative")
555
+ };
556
+ }
557
+ if (kind === 0x1111) {
558
+ if (data.length < 11) throw new FormatError("Classic PDB register-relative symbol is truncated");
559
+ return {
560
+ kind: "registerRelative",
561
+ offset: view.getInt32(0, true),
562
+ typeIndex: view.getUint32(4, true),
563
+ register: view.getUint16(8, true),
564
+ name: readCodeViewName(data, 10, "register-relative")
565
+ };
566
+ }
567
+ if (kind === 0x1108) {
568
+ if (data.length < 5) throw new FormatError("Classic PDB UDT symbol is truncated");
569
+ return {
570
+ kind: "userDefinedType",
571
+ typeIndex: view.getUint32(0, true),
572
+ name: readCodeViewName(data, 4, "UDT")
573
+ };
574
+ }
575
+ if (kind === 0x1106) {
576
+ if (data.length < 7) throw new FormatError("Classic PDB register symbol is truncated");
577
+ return {
578
+ kind: "register",
579
+ typeIndex: view.getUint32(0, true),
580
+ register: view.getUint16(4, true),
581
+ name: readCodeViewName(data, 6, "register")
582
+ };
583
+ }
584
+ if (kind === 0x1105) {
585
+ if (data.length < 8) throw new FormatError("Classic PDB label symbol is truncated");
586
+ return {
587
+ kind: "label",
588
+ offset: view.getUint32(0, true),
589
+ segment: view.getUint16(4, true),
590
+ flags: data[6],
591
+ name: readCodeViewName(data, 7, "label")
592
+ };
593
+ }
594
+ return null;
595
+ }
596
+
597
+ function parseCodeViewFileChecksums(data) {
598
+ const checksums = new Map();
599
+ let offset = 0;
600
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
601
+ while (offset < data.length) {
602
+ if (data.length - offset < 6) {
603
+ throw new FormatError("Classic PDB file-checksum record is truncated");
604
+ }
605
+ const recordStart = offset;
606
+ const fileNameOffset = view.getUint32(offset, true);
607
+ const checksumSize = data[offset + 4];
608
+ const checksumKind = data[offset + 5];
609
+ const expectedSize = new Map([[0, 0], [1, 16], [2, 20], [3, 32]]).get(checksumKind);
610
+ if (expectedSize === undefined || checksumSize !== expectedSize) {
611
+ throw new FormatError("Classic PDB file-checksum kind or length is invalid");
612
+ }
613
+ offset += 6;
614
+ if (checksumSize > data.length - offset) {
615
+ throw new FormatError("Classic PDB file-checksum bytes exceed their subsection");
616
+ }
617
+ const checksum = data.slice(offset, offset + checksumSize);
618
+ offset += checksumSize;
619
+ const alignedOffset = (offset + 3) & ~3;
620
+ if (alignedOffset > data.length || data.subarray(offset, alignedOffset).some((byte) => byte !== 0)) {
621
+ throw new FormatError("Classic PDB file-checksum padding is invalid");
622
+ }
623
+ offset = alignedOffset;
624
+ if (checksums.has(recordStart)) {
625
+ throw new FormatError("Classic PDB file-checksum offsets are duplicated");
626
+ }
627
+ checksums.set(recordStart, { fileNameOffset, checksumKind, checksum });
628
+ }
629
+ return checksums;
630
+ }
631
+
632
+ function parseCodeViewLines(data, fileChecksums, pdbStrings) {
633
+ if (data.length < 12) throw new FormatError("Classic PDB line subsection header is truncated");
634
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
635
+ const relocationOffset = view.getUint32(0, true);
636
+ const segment = view.getUint16(4, true);
637
+ const flags = view.getUint16(6, true);
638
+ const codeSize = view.getUint32(8, true);
639
+ if ((flags & ~1) !== 0) throw new UnsupportedFormatError("Classic PDB line subsection has unsupported flags");
640
+ const hasColumns = (flags & 1) !== 0;
641
+ const blocks = [];
642
+ let offset = 12;
643
+ while (offset < data.length) {
644
+ if (data.length - offset < 12) {
645
+ throw new FormatError("Classic PDB line block header is truncated");
646
+ }
647
+ const fileChecksumOffset = view.getUint32(offset, true);
648
+ const lineCount = view.getUint32(offset + 4, true);
649
+ const blockSize = view.getUint32(offset + 8, true);
650
+ const minimumSize = 12 + lineCount * (hasColumns ? 12 : 8);
651
+ if (blockSize < minimumSize || blockSize > data.length - offset) {
652
+ throw new FormatError("Classic PDB line block size is invalid");
653
+ }
654
+ const checksum = fileChecksums.get(fileChecksumOffset);
655
+ if (!checksum) {
656
+ throw new FormatError("Classic PDB line block references a missing file-checksum record");
657
+ }
658
+ const fileName = pdbStrings?.get(checksum.fileNameOffset) ?? null;
659
+ if (pdbStrings && fileName === null) {
660
+ throw new FormatError("Classic PDB line checksum references a missing string-table name");
661
+ }
662
+ const lines = [];
663
+ let lineOffset = offset + 12;
664
+ for (let index = 0; index < lineCount; index++) {
665
+ const codeOffset = view.getUint32(lineOffset, true);
666
+ const lineFlags = view.getUint32(lineOffset + 4, true);
667
+ if (codeOffset > codeSize) {
668
+ throw new FormatError("Classic PDB line offset exceeds its code contribution");
669
+ }
670
+ lines.push({
671
+ offset: codeOffset,
672
+ startLine: lineFlags & 0x00ffffff,
673
+ endLineDelta: (lineFlags >>> 24) & 0x7f,
674
+ isStatement: (lineFlags & 0x80000000) !== 0
675
+ });
676
+ lineOffset += 8;
677
+ }
678
+ if (hasColumns) {
679
+ for (const line of lines) {
680
+ line.startColumn = view.getUint16(lineOffset, true);
681
+ line.endColumn = view.getUint16(lineOffset + 2, true);
682
+ lineOffset += 4;
683
+ }
684
+ }
685
+ blocks.push({
686
+ fileChecksumOffset,
687
+ fileNameOffset: checksum.fileNameOffset,
688
+ fileName,
689
+ checksumKind: checksum.checksumKind,
690
+ checksum: checksum.checksum,
691
+ lines
692
+ });
693
+ offset += blockSize;
694
+ }
695
+ return { relocationOffset, segment, flags, codeSize, blocks };
696
+ }
697
+
698
+ export function parseCodeViewC13Subsections(input, pdbStrings = null) {
699
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
700
+ const subsections = [];
701
+ let offset = 0;
702
+ while (offset < bytes.length) {
703
+ if (bytes.length - offset < 8) {
704
+ throw new FormatError("Classic PDB C13 subsection header is truncated");
705
+ }
706
+ const view = new DataView(bytes.buffer, bytes.byteOffset + offset, 8);
707
+ const kind = view.getUint32(0, true);
708
+ const size = view.getUint32(4, true);
709
+ const dataStart = offset + 8;
710
+ const dataEnd = dataStart + size;
711
+ if (dataEnd > bytes.length) {
712
+ throw new FormatError("Classic PDB C13 subsection exceeds its module stream");
713
+ }
714
+ subsections.push({ kind, data: bytes.slice(dataStart, dataEnd) });
715
+ const alignedEnd = (dataEnd + 3) & ~3;
716
+ if (alignedEnd > bytes.length) {
717
+ throw new FormatError("Classic PDB C13 subsection alignment exceeds its module stream");
718
+ }
719
+ offset = alignedEnd;
720
+ }
721
+ const stringTables = subsections.filter((subsection) => subsection.kind === 0xf3);
722
+ const checksumTables = subsections.filter((subsection) => subsection.kind === 0xf4);
723
+ if (stringTables.length > 1 || checksumTables.length > 1) {
724
+ throw new FormatError("Classic PDB C13 stream contains duplicate string or checksum subsections");
725
+ }
726
+ const fileChecksums = checksumTables.length > 0
727
+ ? parseCodeViewFileChecksums(checksumTables[0].data)
728
+ : new Map();
729
+ const lines = subsections
730
+ .filter((subsection) => subsection.kind === 0xf2)
731
+ .map((subsection) => parseCodeViewLines(subsection.data, fileChecksums, pdbStrings));
732
+ return { subsections, fileChecksums, lines };
733
+ }
734
+
735
+ export function parsePdbStringTable(input) {
736
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
737
+ if (bytes.length < 16) throw new FormatError("Classic PDB /names stream is truncated");
738
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
739
+ const signature = view.getUint32(0, true);
740
+ const hashVersion = view.getUint32(4, true);
741
+ const byteSize = view.getUint32(8, true);
742
+ if (signature !== 0xeffeeffe) {
743
+ throw new FormatError("Classic PDB /names stream has an invalid signature");
744
+ }
745
+ if (hashVersion !== 1 && hashVersion !== 2) {
746
+ throw new UnsupportedFormatError(`Unsupported classic PDB string-table hash version ${hashVersion}`);
747
+ }
748
+ if (byteSize > bytes.length - 16) {
749
+ throw new FormatError("Classic PDB /names string bytes exceed the stream bounds");
750
+ }
751
+ const stringBytes = bytes.subarray(12, 12 + byteSize);
752
+ const strings = new Map();
753
+ let stringOffset = 0;
754
+ while (stringOffset < stringBytes.length) {
755
+ const terminator = stringBytes.indexOf(0, stringOffset);
756
+ if (terminator < 0) throw new FormatError("Classic PDB /names string is not null-terminated");
757
+ let value;
758
+ try {
759
+ value = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true })
760
+ .decode(stringBytes.subarray(stringOffset, terminator));
761
+ } catch (error) {
762
+ throw new FormatError(`Classic PDB /names string is invalid UTF-8: ${error.message}`);
763
+ }
764
+ strings.set(stringOffset, value);
765
+ stringOffset = terminator + 1;
766
+ }
767
+ let offset = 12 + byteSize;
768
+ if (bytes.length - offset < 8) throw new FormatError("Classic PDB /names hash data is truncated");
769
+ const hashCount = view.getUint32(offset, true);
770
+ offset += 4;
771
+ if (hashCount > Math.floor((bytes.length - offset - 4) / 4)) {
772
+ throw new FormatError("Classic PDB /names hash table exceeds the stream bounds");
773
+ }
774
+ const nameIds = [];
775
+ for (let index = 0; index < hashCount; index++) {
776
+ const id = view.getUint32(offset, true);
777
+ offset += 4;
778
+ if (id !== 0 && !strings.has(id)) {
779
+ throw new FormatError("Classic PDB /names hash table references a missing string");
780
+ }
781
+ nameIds.push(id);
782
+ }
783
+ const nameCount = view.getUint32(offset, true);
784
+ offset += 4;
785
+ if (offset !== bytes.length) throw new FormatError("Trailing bytes after classic PDB /names stream");
786
+ return { signature, hashVersion, byteSize, nameCount, strings, nameIds };
787
+ }
788
+
789
+ export function parseCodeViewSymbolRecords(input) {
790
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
791
+ if (bytes.length < 4) throw new FormatError("Classic PDB module symbol data is truncated");
792
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
793
+ const signature = view.getUint32(0, true);
794
+ if (![1, 2, 4].includes(signature)) {
795
+ throw new UnsupportedFormatError(`Unsupported CodeView module symbol signature ${signature}`);
796
+ }
797
+ const records = [];
798
+ let offset = 4;
799
+ while (offset < bytes.length) {
800
+ if (bytes.length - offset < 2) {
801
+ throw new FormatError("Classic PDB module symbol-record length is truncated");
802
+ }
803
+ const length = view.getUint16(offset, true);
804
+ if (length < 2 || length > bytes.length - offset - 2) {
805
+ throw new FormatError("Classic PDB module symbol record exceeds its stream bounds");
806
+ }
807
+ const kind = view.getUint16(offset + 2, true);
808
+ const data = bytes.slice(offset + 4, offset + 2 + length);
809
+ const decoded = decodeCodeViewSymbol(kind, data);
810
+ records.push({
811
+ offset,
812
+ kind,
813
+ data,
814
+ ...(decoded ? { decoded } : {})
815
+ });
816
+ offset += 2 + length;
817
+ }
818
+ return { signature, records };
819
+ }
820
+
821
+ export function parseClassicPdb(input) {
822
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
823
+ if (bytes.length < 56) throw new FormatError("Classic PDB MSF superblock is truncated");
824
+ if (!msfMagic.every((byte, index) => bytes[index] === byte)) {
825
+ throw new FormatError("Classic PDB has an invalid MSF 7.0 signature");
826
+ }
827
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
828
+ const blockSize = view.getUint32(32, true);
829
+ const freeBlockMapBlock = view.getUint32(36, true);
830
+ const numBlocks = view.getUint32(40, true);
831
+ const directoryByteLength = view.getUint32(44, true);
832
+ const blockMapAddress = view.getUint32(52, true);
833
+ if (blockSize < 512 || blockSize > 65536 || (blockSize & (blockSize - 1)) !== 0) {
834
+ throw new UnsupportedFormatError(`Unsupported classic PDB MSF block size ${blockSize}`);
835
+ }
836
+ if (numBlocks < 4 || numBlocks > Math.floor(bytes.length / blockSize)) {
837
+ throw new FormatError("Classic PDB MSF block count exceeds the file bounds");
838
+ }
839
+ if (freeBlockMapBlock !== 1 && freeBlockMapBlock !== 2) {
840
+ throw new FormatError("Classic PDB MSF free-block map address is invalid");
841
+ }
842
+ if (directoryByteLength === 0 || blockMapAddress < 3 || blockMapAddress >= numBlocks) {
843
+ throw new FormatError("Classic PDB MSF directory metadata is invalid");
844
+ }
845
+
846
+ const directoryBlockCount = Math.ceil(directoryByteLength / blockSize);
847
+ if (directoryBlockCount > Math.floor(blockSize / 4)) {
848
+ throw new UnsupportedFormatError("Classic PDB MSF directory block map exceeds one block");
849
+ }
850
+ const reservedBlocks = new Set([0, 1, 2, blockMapAddress]);
851
+ const allocatedBlocks = new Set(reservedBlocks);
852
+ const claimBlock = (block, context) => {
853
+ if (!Number.isInteger(block) || block < 0 || block >= numBlocks) {
854
+ throw new FormatError(`Classic PDB ${context} block index is out of range`);
855
+ }
856
+ if (allocatedBlocks.has(block)) {
857
+ throw new FormatError(`Classic PDB ${context} overlaps another MSF structure`);
858
+ }
859
+ allocatedBlocks.add(block);
860
+ };
861
+ const readBlock = (block) => {
862
+ const offset = block * blockSize;
863
+ return bytes.subarray(offset, offset + blockSize);
864
+ };
865
+
866
+ const blockMapOffset = blockMapAddress * blockSize;
867
+ const blockMapView = new DataView(bytes.buffer, bytes.byteOffset + blockMapOffset, blockSize);
868
+ const directoryBlocks = [];
869
+ for (let index = 0; index < directoryBlockCount; index++) {
870
+ const block = blockMapView.getUint32(index * 4, true);
871
+ claimBlock(block, "directory");
872
+ directoryBlocks.push(block);
873
+ }
874
+ const directory = new Uint8Array(directoryByteLength);
875
+ for (let index = 0; index < directoryBlocks.length; index++) {
876
+ const start = index * blockSize;
877
+ directory.set(
878
+ readBlock(directoryBlocks[index]).subarray(0, Math.min(blockSize, directoryByteLength - start)),
879
+ start
880
+ );
881
+ }
882
+
883
+ if (directory.length < 4) throw new FormatError("Classic PDB MSF stream directory is truncated");
884
+ const directoryView = new DataView(directory.buffer, directory.byteOffset, directory.byteLength);
885
+ const streamCount = directoryView.getUint32(0, true);
886
+ if (streamCount < 2 || streamCount > Math.floor((directory.length - 4) / 4)) {
887
+ throw new FormatError("Classic PDB MSF stream count exceeds directory bounds");
888
+ }
889
+ let directoryOffset = 4;
890
+ const streamSizes = [];
891
+ for (let index = 0; index < streamCount; index++) {
892
+ streamSizes.push(directoryView.getUint32(directoryOffset, true));
893
+ directoryOffset += 4;
894
+ }
895
+ const streams = [];
896
+ for (let index = 0; index < streamCount; index++) {
897
+ const size = streamSizes[index];
898
+ if (size === absentStream) {
899
+ streams.push({ index, size: null, bytes: null, blockIndices: [] });
900
+ continue;
901
+ }
902
+ const blockCount = Math.ceil(size / blockSize);
903
+ if (blockCount > Math.floor((directory.length - directoryOffset) / 4)) {
904
+ throw new FormatError(`Classic PDB MSF stream ${index} block list exceeds directory bounds`);
905
+ }
906
+ const streamBlocks = [];
907
+ for (let blockIndex = 0; blockIndex < blockCount; blockIndex++) {
908
+ const block = directoryView.getUint32(directoryOffset, true);
909
+ directoryOffset += 4;
910
+ claimBlock(block, `stream ${index}`);
911
+ streamBlocks.push(block);
912
+ }
913
+ const streamBytes = new Uint8Array(size);
914
+ for (let blockIndex = 0; blockIndex < streamBlocks.length; blockIndex++) {
915
+ const start = blockIndex * blockSize;
916
+ streamBytes.set(
917
+ readBlock(streamBlocks[blockIndex]).subarray(0, Math.min(blockSize, size - start)),
918
+ start
919
+ );
920
+ }
921
+ streams.push({ index, size, bytes: streamBytes, blockIndices: streamBlocks });
922
+ }
923
+ if (directoryOffset !== directory.length) {
924
+ throw new FormatError("Trailing bytes after classic PDB MSF stream directory");
925
+ }
926
+
927
+ const infoStream = streams[1]?.bytes;
928
+ if (!infoStream || infoStream.length < 28) {
929
+ throw new FormatError("Classic PDB information stream is missing or truncated");
930
+ }
931
+ const infoView = new DataView(infoStream.buffer, infoStream.byteOffset, infoStream.byteLength);
932
+ const id = infoStream.subarray(12, 28);
933
+ const infoDetails = parsePdbNamedStreams(infoStream, streams);
934
+ const namesStreamIndex = infoDetails.namedStreams.get("/names");
935
+ const pdbStringTable = namesStreamIndex === undefined
936
+ ? null
937
+ : parsePdbStringTable(streams[namesStreamIndex].bytes);
938
+ const info = {
939
+ version: infoView.getUint32(0, true),
940
+ signature: infoView.getUint32(4, true),
941
+ age: infoView.getUint32(8, true),
942
+ guid: formatGuid(id),
943
+ stringTable: pdbStringTable,
944
+ ...infoDetails
945
+ };
946
+ let dbi = null;
947
+ const dbiStream = streams[3]?.bytes;
948
+ if (dbiStream) {
949
+ if (dbiStream.length < 64) throw new FormatError("Classic PDB DBI stream is truncated");
950
+ const dbiView = new DataView(dbiStream.buffer, dbiStream.byteOffset, dbiStream.byteLength);
951
+ if (dbiView.getInt32(0, true) !== -1) {
952
+ throw new FormatError("Classic PDB DBI version signature is invalid");
953
+ }
954
+ const dbiVersion = dbiView.getUint32(4, true);
955
+ if (dbiVersion < 19990903) {
956
+ throw new UnsupportedFormatError("Classic PDB DBI version is older than version 7");
957
+ }
958
+ const dbiAge = dbiView.getUint32(8, true);
959
+ if (dbiAge !== info.age) {
960
+ throw new FormatError("Classic PDB DBI age does not match the information stream");
961
+ }
962
+ const substreamSizes = {
963
+ moduleInfo: dbiView.getUint32(24, true),
964
+ sectionContributions: dbiView.getUint32(28, true),
965
+ sectionMap: dbiView.getUint32(32, true),
966
+ fileInfo: dbiView.getUint32(36, true),
967
+ typeServerMap: dbiView.getUint32(40, true),
968
+ optionalDebugHeader: dbiView.getUint32(48, true),
969
+ editAndContinue: dbiView.getUint32(52, true)
970
+ };
971
+ const requiredLength = 64 + Object.values(substreamSizes)
972
+ .reduce((sum, size) => sum + size, 0);
973
+ if (requiredLength !== dbiStream.length) {
974
+ throw new FormatError("Classic PDB DBI stream length does not match its substream sizes");
975
+ }
976
+ for (const name of ["moduleInfo", "sectionContributions", "sectionMap", "fileInfo", "typeServerMap"]) {
977
+ if (substreamSizes[name] % 4 !== 0) {
978
+ throw new FormatError(`Classic PDB DBI ${name} substream is misaligned`);
979
+ }
980
+ }
981
+ if (substreamSizes.optionalDebugHeader % 2 !== 0) {
982
+ throw new FormatError("Classic PDB DBI optional debug header is misaligned");
983
+ }
984
+ const streamReferences = {
985
+ globalSymbols: dbiView.getUint16(12, true),
986
+ publicSymbols: dbiView.getUint16(16, true),
987
+ symbolRecords: dbiView.getUint16(20, true)
988
+ };
989
+ for (const [name, streamIndex] of Object.entries(streamReferences)) {
990
+ if (streamIndex !== 0xffff &&
991
+ (streamIndex >= streams.length || streams[streamIndex].bytes === null)) {
992
+ throw new FormatError(`Classic PDB DBI ${name} reference targets a missing stream`);
993
+ }
994
+ }
995
+ const modules = parseDbiModules(
996
+ dbiStream,
997
+ substreamSizes.moduleInfo,
998
+ streams,
999
+ pdbStringTable?.strings ?? null
1000
+ );
1001
+ const fileInfoOffset = 64 + substreamSizes.moduleInfo +
1002
+ substreamSizes.sectionContributions + substreamSizes.sectionMap;
1003
+ dbi = {
1004
+ version: dbiVersion,
1005
+ age: dbiAge,
1006
+ buildNumber: dbiView.getUint16(14, true),
1007
+ pdbDllVersion: dbiView.getUint16(18, true),
1008
+ pdbDllRebuild: dbiView.getUint16(22, true),
1009
+ machine: dbiView.getUint16(58, true),
1010
+ flags: dbiView.getUint16(56, true),
1011
+ streamReferences,
1012
+ substreamSizes,
1013
+ modules: parseDbiFileInfo(
1014
+ dbiStream, fileInfoOffset, substreamSizes.fileInfo, modules
1015
+ )
1016
+ };
1017
+ }
1018
+
1019
+ return {
1020
+ format: "MSF 7.0",
1021
+ blockSize,
1022
+ numBlocks,
1023
+ msf: {
1024
+ directoryByteLength,
1025
+ directoryBlocks,
1026
+ blockMapAddress
1027
+ },
1028
+ info,
1029
+ dbi,
1030
+ streams
1031
+ };
1032
+ }
1033
+
1034
+ function replaceClassicPdbStreamBytes(bytes, pdb, streamIndex, streamOffset, replacement) {
1035
+ const stream = pdb.streams[streamIndex];
1036
+ if (!stream?.blockIndices?.length) {
1037
+ throw new FormatError("Classic PDB replacement stream has no allocated MSF blocks");
1038
+ }
1039
+ if (!Number.isInteger(streamOffset) || streamOffset < 0 ||
1040
+ streamOffset > stream.size - replacement.length) {
1041
+ throw new FormatError("Classic PDB replacement exceeds its stream bounds");
1042
+ }
1043
+ const output = Uint8Array.from(bytes);
1044
+ for (let index = 0; index < replacement.length; index++) {
1045
+ const position = streamOffset + index;
1046
+ const blockIndex = Math.floor(position / pdb.blockSize);
1047
+ const block = stream.blockIndices[blockIndex];
1048
+ if (block === undefined) throw new FormatError("Classic PDB replacement exceeds its MSF block list");
1049
+ output[block * pdb.blockSize + position % pdb.blockSize] = replacement[index];
1050
+ }
1051
+ return output;
1052
+ }
1053
+
1054
+ function writeClassicPdbStream(bytes, pdb, streamIndex, streamBytes) {
1055
+ const stream = pdb.streams[streamIndex];
1056
+ if (!stream?.blockIndices?.length ||
1057
+ Math.ceil(streamBytes.length / pdb.blockSize) !== stream.blockIndices.length) {
1058
+ throw new UnsupportedFormatError(
1059
+ "Classic PDB replacement must preserve the stream's allocated MSF block count"
1060
+ );
1061
+ }
1062
+ const output = Uint8Array.from(bytes);
1063
+ for (let index = 0; index < stream.blockIndices.length; index++) {
1064
+ const block = stream.blockIndices[index];
1065
+ const sourceOffset = index * pdb.blockSize;
1066
+ const copyLength = Math.min(pdb.blockSize, streamBytes.length - sourceOffset);
1067
+ const targetOffset = block * pdb.blockSize;
1068
+ if (copyLength > 0) output.set(streamBytes.subarray(sourceOffset, sourceOffset + copyLength), targetOffset);
1069
+ output.fill(0, targetOffset + Math.max(copyLength, 0), targetOffset + pdb.blockSize);
1070
+ }
1071
+ return output;
1072
+ }
1073
+
1074
+ function rebuildClassicPdbStreams(pdb, streamReplacements) {
1075
+ const blockSize = pdb.blockSize;
1076
+ const streams = pdb.streams.map((stream) => ({
1077
+ bytes: stream.bytes === null
1078
+ ? null
1079
+ : Uint8Array.from(streamReplacements.get(stream.index) ?? stream.bytes)
1080
+ }));
1081
+ const streamBlockCounts = streams.map(({ bytes }) =>
1082
+ bytes === null ? 0 : Math.ceil(bytes.length / blockSize)
1083
+ );
1084
+ const directoryByteLength = 4 + streams.length * 4 +
1085
+ streamBlockCounts.reduce((sum, count) => sum + count * 4, 0);
1086
+ const directoryBlockCount = Math.ceil(directoryByteLength / blockSize);
1087
+ if (directoryBlockCount > Math.floor(blockSize / 4)) {
1088
+ throw new UnsupportedFormatError("Classic PDB MSF directory block map exceeds one block");
1089
+ }
1090
+ const directoryBlockIndices = Array.from(
1091
+ { length: directoryBlockCount },
1092
+ (_, index) => 4 + index
1093
+ );
1094
+ let nextBlock = 4 + directoryBlockCount;
1095
+ const streamBlockIndices = streamBlockCounts.map((count) => {
1096
+ const indices = Array.from({ length: count }, (_, index) => nextBlock + index);
1097
+ nextBlock += count;
1098
+ return indices;
1099
+ });
1100
+ if (nextBlock > blockSize * 8 ||
1101
+ nextBlock * blockSize > 0x7fffffff) {
1102
+ throw new UnsupportedFormatError(
1103
+ "Classic PDB MSF rebuild exceeds supported allocation limits"
1104
+ );
1105
+ }
1106
+ const output = new Uint8Array(nextBlock * blockSize);
1107
+ output.set(msfMagic);
1108
+ const superblock = new DataView(output.buffer);
1109
+ superblock.setUint32(32, blockSize, true);
1110
+ superblock.setUint32(36, 1, true);
1111
+ superblock.setUint32(40, nextBlock, true);
1112
+ superblock.setUint32(44, directoryByteLength, true);
1113
+ superblock.setUint32(48, 0, true);
1114
+ superblock.setUint32(52, 3, true);
1115
+
1116
+ const directory = new Uint8Array(directoryByteLength);
1117
+ const directoryView = new DataView(directory.buffer);
1118
+ directoryView.setUint32(0, streams.length, true);
1119
+ let directoryOffset = 4;
1120
+ for (const { bytes } of streams) {
1121
+ directoryView.setUint32(
1122
+ directoryOffset,
1123
+ bytes === null ? absentStream : bytes.length,
1124
+ true
1125
+ );
1126
+ directoryOffset += 4;
1127
+ }
1128
+ for (const indices of streamBlockIndices) {
1129
+ for (const block of indices) {
1130
+ directoryView.setUint32(directoryOffset, block, true);
1131
+ directoryOffset += 4;
1132
+ }
1133
+ }
1134
+ const blockMapOffset = 3 * blockSize;
1135
+ const blockMapView = new DataView(output.buffer, blockMapOffset, blockSize);
1136
+ for (let index = 0; index < directoryBlockIndices.length; index++) {
1137
+ blockMapView.setUint32(index * 4, directoryBlockIndices[index], true);
1138
+ }
1139
+ for (let index = 0; index < directoryBlockIndices.length; index++) {
1140
+ const start = index * blockSize;
1141
+ output.set(
1142
+ directory.subarray(start, Math.min(start + blockSize, directory.length)),
1143
+ directoryBlockIndices[index] * blockSize
1144
+ );
1145
+ }
1146
+ streams.forEach(({ bytes }, streamIndex) => {
1147
+ if (bytes === null) return;
1148
+ streamBlockIndices[streamIndex].forEach((block, blockIndex) => {
1149
+ const start = blockIndex * blockSize;
1150
+ output.set(bytes.subarray(start, Math.min(start + blockSize, bytes.length)), block * blockSize);
1151
+ });
1152
+ });
1153
+ for (const freeMapBlock of [1, 2]) {
1154
+ const offset = freeMapBlock * blockSize;
1155
+ output.fill(0xff, offset, offset + blockSize);
1156
+ const markAllocated = (block) => {
1157
+ output[offset + Math.floor(block / 8)] &= ~(1 << (block % 8));
1158
+ };
1159
+ for (let block = 0; block < 4; block++) markAllocated(block);
1160
+ for (const block of directoryBlockIndices) markAllocated(block);
1161
+ for (const indices of streamBlockIndices) {
1162
+ for (const block of indices) markAllocated(block);
1163
+ }
1164
+ }
1165
+ return output;
1166
+ }
1167
+
1168
+ function replaceClassicPdbDirectoryBytes(bytes, pdb, offset, replacement) {
1169
+ if (!Number.isInteger(offset) || offset < 0 ||
1170
+ offset > pdb.msf.directoryByteLength - replacement.length) {
1171
+ throw new FormatError("Classic PDB directory replacement exceeds its stream bounds");
1172
+ }
1173
+ const output = Uint8Array.from(bytes);
1174
+ for (let index = 0; index < replacement.length; index++) {
1175
+ const position = offset + index;
1176
+ const blockIndex = Math.floor(position / pdb.blockSize);
1177
+ const block = pdb.msf.directoryBlocks[blockIndex];
1178
+ if (block === undefined) throw new FormatError("Classic PDB directory replacement exceeds its MSF block list");
1179
+ output[block * pdb.blockSize + position % pdb.blockSize] = replacement[index];
1180
+ }
1181
+ return output;
1182
+ }
1183
+
1184
+ function encodeClassicPdbReplacementText(value, context) {
1185
+ if (typeof value !== "string" || value.includes("\0")) {
1186
+ throw new TypeError(`${context} must be a string without null characters`);
1187
+ }
1188
+ const bytes = new TextEncoder().encode(value);
1189
+ if (new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes) !== value) {
1190
+ throw new TypeError(`${context} must contain valid Unicode`);
1191
+ }
1192
+ return bytes;
1193
+ }
1194
+
1195
+ export function replaceClassicPdbModuleNames(input, options = {}) {
1196
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
1197
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
1198
+ throw new TypeError("Classic PDB module replacement must be an object");
1199
+ }
1200
+ const { moduleIndex, moduleName, objectName } = options;
1201
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
1202
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
1203
+ }
1204
+ const updatesModuleName = Object.hasOwn(options, "moduleName");
1205
+ const updatesObjectName = Object.hasOwn(options, "objectName");
1206
+ if ((!updatesModuleName && !updatesObjectName) ||
1207
+ Object.keys(options).some((field) =>
1208
+ !["moduleIndex", "moduleName", "objectName"].includes(field)
1209
+ )) {
1210
+ throw new TypeError("Classic PDB module replacement requires supported name fields");
1211
+ }
1212
+
1213
+ const pdb = parseClassicPdb(bytes);
1214
+ const module = pdb.dbi?.modules[moduleIndex];
1215
+ if (!module) throw new RangeError("Classic PDB module index does not exist");
1216
+ const nextModuleName = updatesModuleName
1217
+ ? moduleName
1218
+ : module.moduleName;
1219
+ const nextObjectName = updatesObjectName
1220
+ ? objectName
1221
+ : module.objectName;
1222
+ const moduleNameBytes = encodeClassicPdbReplacementText(
1223
+ nextModuleName,
1224
+ "Classic PDB module name"
1225
+ );
1226
+ const objectNameBytes = encodeClassicPdbReplacementText(
1227
+ nextObjectName,
1228
+ "Classic PDB object name"
1229
+ );
1230
+ if (nextModuleName === module.moduleName && nextObjectName === module.objectName) {
1231
+ return Uint8Array.from(bytes);
1232
+ }
1233
+
1234
+ const dbiStream = pdb.streams[3]?.bytes;
1235
+ const moduleInfoSize = pdb.dbi.substreamSizes.moduleInfo;
1236
+ const moduleInfoEnd = 64 + moduleInfoSize;
1237
+ const recordStart = module.moduleInfoOffset;
1238
+ const recordEnd = pdb.dbi.modules[moduleIndex + 1]?.moduleInfoOffset ?? moduleInfoEnd;
1239
+ if (!dbiStream || recordStart < 64 || recordEnd > moduleInfoEnd ||
1240
+ recordEnd < recordStart + 64) {
1241
+ throw new FormatError("Classic PDB DBI module-info record has invalid bounds");
1242
+ }
1243
+ const findTerminator = (start, context) => {
1244
+ const end = dbiStream.indexOf(0, start);
1245
+ if (end < start || end >= recordEnd) {
1246
+ throw new FormatError(`Classic PDB DBI ${context} is not null-terminated`);
1247
+ }
1248
+ return end;
1249
+ };
1250
+ const moduleNameEnd = findTerminator(recordStart + 64, "module name");
1251
+ const objectNameEnd = findTerminator(moduleNameEnd + 1, "object name");
1252
+ if (((objectNameEnd + 1 + 3) & ~3) !== recordEnd) {
1253
+ throw new FormatError("Classic PDB DBI module-info record has invalid padding");
1254
+ }
1255
+
1256
+ const recordSize = (64 + moduleNameBytes.length + 1 +
1257
+ objectNameBytes.length + 1 + 3) & ~3;
1258
+ const replacementRecord = new Uint8Array(recordSize);
1259
+ replacementRecord.set(dbiStream.subarray(recordStart, recordStart + 64));
1260
+ let replacementOffset = 64;
1261
+ replacementRecord.set(moduleNameBytes, replacementOffset);
1262
+ replacementOffset += moduleNameBytes.length + 1;
1263
+ replacementRecord.set(objectNameBytes, replacementOffset);
1264
+ const oldRecordSize = recordEnd - recordStart;
1265
+ const sizeDelta = recordSize - oldRecordSize;
1266
+ const newModuleInfoSize = moduleInfoSize + sizeDelta;
1267
+ if (newModuleInfoSize < 0 || newModuleInfoSize > 0xffffffff ||
1268
+ dbiStream.length + sizeDelta > 0xffffffff) {
1269
+ throw new UnsupportedFormatError("Classic PDB DBI module names exceed supported size limits");
1270
+ }
1271
+ const rewrittenModuleInfo = new Uint8Array(newModuleInfoSize);
1272
+ rewrittenModuleInfo.set(dbiStream.subarray(64, recordStart));
1273
+ rewrittenModuleInfo.set(replacementRecord, recordStart - 64);
1274
+ rewrittenModuleInfo.set(
1275
+ dbiStream.subarray(recordEnd, moduleInfoEnd),
1276
+ recordStart - 64 + recordSize
1277
+ );
1278
+ const rewrittenDbi = new Uint8Array(dbiStream.length + sizeDelta);
1279
+ rewrittenDbi.set(dbiStream.subarray(0, 64));
1280
+ rewrittenDbi.set(rewrittenModuleInfo, 64);
1281
+ rewrittenDbi.set(dbiStream.subarray(moduleInfoEnd), 64 + newModuleInfoSize);
1282
+ new DataView(rewrittenDbi.buffer).setUint32(24, newModuleInfoSize, true);
1283
+
1284
+ let output = rebuildClassicPdbStreams(pdb, new Map([[3, rewrittenDbi]]));
1285
+ const overlay = bytes.subarray(pdb.numBlocks * pdb.blockSize);
1286
+ if (overlay.length > 0) {
1287
+ const withOverlay = new Uint8Array(output.length + overlay.length);
1288
+ withOverlay.set(output);
1289
+ withOverlay.set(overlay, output.length);
1290
+ output = withOverlay;
1291
+ }
1292
+ parseClassicPdb(output);
1293
+ return output;
1294
+ }
1295
+
1296
+ export function replaceClassicPdbLineInfo(
1297
+ input,
1298
+ { moduleIndex, lineSubsectionIndex, blockIndex, lineIndex, line } = {}
1299
+ ) {
1300
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
1301
+ for (const [value, label] of [
1302
+ [moduleIndex, "module"],
1303
+ [lineSubsectionIndex, "line subsection"],
1304
+ [blockIndex, "line block"],
1305
+ [lineIndex, "line"]
1306
+ ]) {
1307
+ if (!Number.isInteger(value) || value < 0) {
1308
+ throw new RangeError(`Classic PDB ${label} index must be a non-negative integer`);
1309
+ }
1310
+ }
1311
+ if (!line || typeof line !== "object" || Array.isArray(line)) {
1312
+ throw new TypeError("Classic PDB line replacement must be an object");
1313
+ }
1314
+ const allowedFields = new Set([
1315
+ "offset", "startLine", "endLineDelta", "isStatement", "startColumn", "endColumn"
1316
+ ]);
1317
+ if (Object.keys(line).length === 0 ||
1318
+ Object.keys(line).some((field) => !allowedFields.has(field))) {
1319
+ throw new TypeError("Classic PDB line replacement has no supported fields");
1320
+ }
1321
+
1322
+ const pdb = parseClassicPdb(bytes);
1323
+ const module = pdb.dbi?.modules[moduleIndex];
1324
+ const lineSubsection = module?.c13?.lines[lineSubsectionIndex];
1325
+ const block = lineSubsection?.blocks[blockIndex];
1326
+ const existingLine = block?.lines[lineIndex];
1327
+ if (!module || !lineSubsection || !block || !existingLine) {
1328
+ throw new RangeError("Classic PDB line location does not exist");
1329
+ }
1330
+ const startLine = line.startLine ?? existingLine.startLine;
1331
+ const endLineDelta = line.endLineDelta ?? existingLine.endLineDelta;
1332
+ const isStatement = line.isStatement ?? existingLine.isStatement;
1333
+ const startColumn = line.startColumn ?? existingLine.startColumn;
1334
+ const endColumn = line.endColumn ?? existingLine.endColumn;
1335
+ const offset = line.offset ?? existingLine.offset;
1336
+ if (!Number.isInteger(offset) || offset < 0 || offset > 0xffffffff ||
1337
+ !Number.isInteger(startLine) || startLine < 0 || startLine > 0x00ffffff ||
1338
+ !Number.isInteger(endLineDelta) || endLineDelta < 0 || endLineDelta > 0x7f ||
1339
+ startLine + endLineDelta > 0x00ffffff ||
1340
+ typeof isStatement !== "boolean") {
1341
+ throw new RangeError("Classic PDB line number fields are out of range");
1342
+ }
1343
+ if ("startColumn" in line || "endColumn" in line) {
1344
+ if ((lineSubsection.flags & 1) === 0) {
1345
+ throw new UnsupportedFormatError(
1346
+ "Classic PDB line subsection does not store column information"
1347
+ );
1348
+ }
1349
+ if (!Number.isInteger(startColumn) || startColumn < 0 || startColumn > 0xffff ||
1350
+ !Number.isInteger(endColumn) || endColumn < startColumn || endColumn > 0xffff) {
1351
+ throw new RangeError("Classic PDB line column fields are out of range");
1352
+ }
1353
+ }
1354
+
1355
+ const c13SubsectionIndex = module.c13.subsections
1356
+ .map((subsection, index) => subsection.kind === 0xf2 ? index : -1)
1357
+ .filter((index) => index >= 0)[lineSubsectionIndex];
1358
+ let c13Offset = 0;
1359
+ for (let index = 0; index < c13SubsectionIndex; index++) {
1360
+ const subsection = module.c13.subsections[index];
1361
+ c13Offset += 8 + ((subsection.data.length + 3) & ~3);
1362
+ }
1363
+ const lineData = module.c13.subsections[c13SubsectionIndex].data;
1364
+ const lineView = new DataView(lineData.buffer, lineData.byteOffset, lineData.byteLength);
1365
+ let blockOffset = 12;
1366
+ for (let index = 0; index < blockIndex; index++) {
1367
+ blockOffset += lineView.getUint32(blockOffset + 8, true);
1368
+ }
1369
+ const lineCount = lineView.getUint32(blockOffset + 4, true);
1370
+ const hasColumns = (lineSubsection.flags & 1) !== 0;
1371
+ const lineRecordOffset = blockOffset + 12 + lineIndex * 8;
1372
+ const lineFlags = (
1373
+ startLine |
1374
+ (endLineDelta << 24) |
1375
+ (isStatement ? 0x80000000 : 0)
1376
+ ) >>> 0;
1377
+ const replacement = new Uint8Array(8);
1378
+ const replacementView = new DataView(replacement.buffer);
1379
+ replacementView.setUint32(0, offset, true);
1380
+ replacementView.setUint32(4, lineFlags, true);
1381
+ const columnReplacement = new Uint8Array(4);
1382
+ const columnView = new DataView(columnReplacement.buffer);
1383
+ if (hasColumns) {
1384
+ const columnOffset = blockOffset + 12 + lineCount * 8 + lineIndex * 4;
1385
+ columnView.setUint16(0, startColumn, true);
1386
+ columnView.setUint16(2, endColumn, true);
1387
+ if (columnOffset + 4 > lineData.length) {
1388
+ throw new FormatError("Classic PDB line column record exceeds its subsection");
1389
+ }
1390
+ }
1391
+ const streamOffset = module.symbolBytes + module.c11Bytes +
1392
+ c13Offset + 8 + lineRecordOffset;
1393
+ let output = replaceClassicPdbStreamBytes(
1394
+ bytes,
1395
+ pdb,
1396
+ module.streamIndex,
1397
+ streamOffset,
1398
+ replacement
1399
+ );
1400
+ if (hasColumns && ("startColumn" in line || "endColumn" in line)) {
1401
+ const columnOffset = module.symbolBytes + module.c11Bytes +
1402
+ c13Offset + 8 + blockOffset + 12 + lineCount * 8 + lineIndex * 4;
1403
+ output = replaceClassicPdbStreamBytes(
1404
+ output,
1405
+ pdb,
1406
+ module.streamIndex,
1407
+ columnOffset,
1408
+ columnReplacement
1409
+ );
1410
+ }
1411
+ parseClassicPdb(output);
1412
+ return output;
1413
+ }
1414
+
1415
+ function rewriteClassicPdbLineBlock(
1416
+ input,
1417
+ { moduleIndex, lineSubsectionIndex, blockIndex, lines, fileChecksumOffset, operation }
1418
+ ) {
1419
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
1420
+ for (const [value, label] of [
1421
+ [moduleIndex, "module"],
1422
+ [lineSubsectionIndex, "line subsection"],
1423
+ [blockIndex, "line block"]
1424
+ ]) {
1425
+ if (!Number.isInteger(value) || value < 0) {
1426
+ throw new RangeError(`Classic PDB ${label} index must be a non-negative integer`);
1427
+ }
1428
+ }
1429
+ if (!["replace", "insert", "remove"].includes(operation)) {
1430
+ throw new TypeError("Classic PDB line-block operation is invalid");
1431
+ }
1432
+ if (!Array.isArray(lines) ||
1433
+ (operation === "remove" ? lines.length !== 0 : lines.length === 0)) {
1434
+ throw new TypeError(
1435
+ operation === "remove"
1436
+ ? "Classic PDB line-block removal does not accept replacement lines"
1437
+ : "Classic PDB line-block replacement requires a non-empty lines array"
1438
+ );
1439
+ }
1440
+
1441
+ const pdb = parseClassicPdb(bytes);
1442
+ const module = pdb.dbi?.modules[moduleIndex];
1443
+ const lineSubsection = module?.c13?.lines[lineSubsectionIndex];
1444
+ const block = lineSubsection?.blocks[blockIndex];
1445
+ if (!module || !lineSubsection ||
1446
+ (operation !== "insert" && !block) ||
1447
+ (operation === "insert" && blockIndex > lineSubsection.blocks.length)) {
1448
+ throw new RangeError("Classic PDB line-block location does not exist");
1449
+ }
1450
+ if (operation === "insert" &&
1451
+ (!Number.isInteger(fileChecksumOffset) || fileChecksumOffset < 0 ||
1452
+ !module.c13.fileChecksums.has(fileChecksumOffset))) {
1453
+ throw new RangeError("Classic PDB line-block checksum location does not exist");
1454
+ }
1455
+ const hasColumns = (lineSubsection.flags & 1) !== 0;
1456
+ const allowedFields = hasColumns
1457
+ ? new Set(["offset", "startLine", "endLineDelta", "isStatement", "startColumn", "endColumn"])
1458
+ : new Set(["offset", "startLine", "endLineDelta", "isStatement"]);
1459
+ const stride = hasColumns ? 12 : 8;
1460
+ const newBlockSize = operation === "remove" ? 0 : 12 + lines.length * stride;
1461
+ if (newBlockSize > 0xffffffff) {
1462
+ throw new RangeError("Classic PDB line block exceeds its 32-bit size limit");
1463
+ }
1464
+ const rewrittenBlock = new Uint8Array(newBlockSize);
1465
+ const rewrittenBlockView = new DataView(rewrittenBlock.buffer);
1466
+ if (operation !== "remove") {
1467
+ rewrittenBlockView.setUint32(
1468
+ 0,
1469
+ operation === "insert" ? fileChecksumOffset : block.fileChecksumOffset,
1470
+ true
1471
+ );
1472
+ rewrittenBlockView.setUint32(4, lines.length, true);
1473
+ rewrittenBlockView.setUint32(8, newBlockSize, true);
1474
+ }
1475
+ let previousOffset = -1;
1476
+ for (const [index, line] of lines.entries()) {
1477
+ if (!line || typeof line !== "object" || Array.isArray(line) ||
1478
+ Object.keys(line).length !== allowedFields.size ||
1479
+ Object.keys(line).some((field) => !allowedFields.has(field)) ||
1480
+ [...allowedFields].some((field) => !Object.hasOwn(line, field))) {
1481
+ throw new TypeError(`Classic PDB replacement line ${index} has invalid fields`);
1482
+ }
1483
+ if (!Number.isInteger(line.offset) || line.offset < 0 ||
1484
+ line.offset > lineSubsection.codeSize ||
1485
+ !Number.isInteger(line.startLine) || line.startLine < 0 ||
1486
+ line.startLine > 0x00ffffff ||
1487
+ !Number.isInteger(line.endLineDelta) || line.endLineDelta < 0 ||
1488
+ line.endLineDelta > 0x7f ||
1489
+ line.startLine + line.endLineDelta > 0x00ffffff ||
1490
+ typeof line.isStatement !== "boolean") {
1491
+ throw new RangeError(`Classic PDB replacement line ${index} has out-of-range fields`);
1492
+ }
1493
+ if (line.offset < previousOffset) {
1494
+ throw new RangeError("Classic PDB replacement line offsets must be nondecreasing");
1495
+ }
1496
+ previousOffset = line.offset;
1497
+ const recordOffset = 12 + index * 8;
1498
+ rewrittenBlockView.setUint32(recordOffset, line.offset, true);
1499
+ rewrittenBlockView.setUint32(
1500
+ recordOffset + 4,
1501
+ (line.startLine | (line.endLineDelta << 24) |
1502
+ (line.isStatement ? 0x80000000 : 0)) >>> 0,
1503
+ true
1504
+ );
1505
+ if (hasColumns) {
1506
+ if (!Number.isInteger(line.startColumn) || line.startColumn < 0 ||
1507
+ line.startColumn > 0xffff ||
1508
+ !Number.isInteger(line.endColumn) ||
1509
+ line.endColumn < line.startColumn || line.endColumn > 0xffff) {
1510
+ throw new RangeError(`Classic PDB replacement line ${index} has invalid columns`);
1511
+ }
1512
+ const columnOffset = 12 + lines.length * 8 + index * 4;
1513
+ rewrittenBlockView.setUint16(columnOffset, line.startColumn, true);
1514
+ rewrittenBlockView.setUint16(columnOffset + 2, line.endColumn, true);
1515
+ }
1516
+ }
1517
+
1518
+ const lineSubsectionIndexInC13 = module.c13.subsections
1519
+ .map((subsection, index) => subsection.kind === 0xf2 ? index : -1)
1520
+ .filter((index) => index >= 0)[lineSubsectionIndex];
1521
+ const lineSubsectionInC13 = module.c13.subsections[lineSubsectionIndexInC13];
1522
+ const lineData = lineSubsectionInC13.data;
1523
+ const lineView = new DataView(lineData.buffer, lineData.byteOffset, lineData.byteLength);
1524
+ let blockOffset = 12;
1525
+ for (let index = 0; index < blockIndex; index++) {
1526
+ blockOffset += lineView.getUint32(blockOffset + 8, true);
1527
+ }
1528
+ const oldBlockSize = operation === "insert"
1529
+ ? 0
1530
+ : lineView.getUint32(blockOffset + 8, true);
1531
+ if (operation !== "insert" &&
1532
+ oldBlockSize !== 12 + block.lines.length * stride) {
1533
+ throw new UnsupportedFormatError(
1534
+ "Classic PDB line-block replacement cannot discard unrecognized block data"
1535
+ );
1536
+ }
1537
+ const rewrittenLineDataLength = lineData.length - oldBlockSize + newBlockSize;
1538
+ if (rewrittenLineDataLength > 0xffffffff) {
1539
+ throw new RangeError("Classic PDB C13 line subsection exceeds its 32-bit size limit");
1540
+ }
1541
+ const rewrittenLineData = new Uint8Array(rewrittenLineDataLength);
1542
+ rewrittenLineData.set(lineData.subarray(0, blockOffset));
1543
+ rewrittenLineData.set(rewrittenBlock, blockOffset);
1544
+ rewrittenLineData.set(
1545
+ lineData.subarray(blockOffset + oldBlockSize),
1546
+ blockOffset + newBlockSize
1547
+ );
1548
+ const rewrittenSubsections = module.c13.subsections.map((subsection, index) =>
1549
+ index === lineSubsectionIndexInC13
1550
+ ? { ...subsection, data: rewrittenLineData, sourceIndex: index }
1551
+ : subsection
1552
+ );
1553
+ return rebuildClassicPdbC13Module(bytes, pdb, moduleIndex, rewrittenSubsections);
1554
+ }
1555
+
1556
+ export function replaceClassicPdbLineBlock(input, options = {}) {
1557
+ const { moduleIndex, lineSubsectionIndex, blockIndex, lines } = options;
1558
+ return rewriteClassicPdbLineBlock(input, {
1559
+ moduleIndex,
1560
+ lineSubsectionIndex,
1561
+ blockIndex,
1562
+ lines,
1563
+ operation: "replace"
1564
+ });
1565
+ }
1566
+
1567
+ export function insertClassicPdbLineBlock(input, options = {}) {
1568
+ const {
1569
+ moduleIndex,
1570
+ lineSubsectionIndex,
1571
+ beforeBlockIndex,
1572
+ fileChecksumOffset,
1573
+ lines
1574
+ } = options;
1575
+ return rewriteClassicPdbLineBlock(input, {
1576
+ moduleIndex,
1577
+ lineSubsectionIndex,
1578
+ blockIndex: beforeBlockIndex,
1579
+ fileChecksumOffset,
1580
+ lines,
1581
+ operation: "insert"
1582
+ });
1583
+ }
1584
+
1585
+ export function removeClassicPdbLineBlock(input, options = {}) {
1586
+ const { moduleIndex, lineSubsectionIndex, blockIndex } = options;
1587
+ return rewriteClassicPdbLineBlock(input, {
1588
+ moduleIndex,
1589
+ lineSubsectionIndex,
1590
+ blockIndex,
1591
+ lines: [],
1592
+ operation: "remove"
1593
+ });
1594
+ }
1595
+
1596
+ function getClassicPdbChecksumKind(checksum, checksumKind) {
1597
+ if (!(checksum instanceof Uint8Array)) {
1598
+ throw new TypeError("Classic PDB file checksum must be a Uint8Array");
1599
+ }
1600
+ const inferredKind = new Map([[0, 0], [16, 1], [20, 2], [32, 3]])
1601
+ .get(checksum.length);
1602
+ const resolvedKind = checksumKind ?? inferredKind;
1603
+ if (!Number.isInteger(resolvedKind) || resolvedKind < 0 || resolvedKind > 3) {
1604
+ throw new UnsupportedFormatError(
1605
+ `Classic PDB file-checksum length ${checksum.length} has no supported checksum kind`
1606
+ );
1607
+ }
1608
+ const expectedSize = new Map([[0, 0], [1, 16], [2, 20], [3, 32]])
1609
+ .get(resolvedKind);
1610
+ if (checksum.length !== expectedSize) {
1611
+ throw new RangeError(
1612
+ `Classic PDB checksum kind ${resolvedKind} requires ${expectedSize} bytes`
1613
+ );
1614
+ }
1615
+ return resolvedKind;
1616
+ }
1617
+
1618
+ function rewriteClassicPdbFileChecksumRecords(bytes, pdb, moduleIndex, records) {
1619
+ const module = pdb.dbi?.modules[moduleIndex];
1620
+ if (!module) throw new RangeError("Classic PDB module index does not exist");
1621
+ const checksumSubsectionIndex = module.c13?.subsections.findIndex(
1622
+ (subsection) => subsection.kind === 0xf4
1623
+ ) ?? -1;
1624
+ const checksumOffsets = new Map();
1625
+ const checksumData = [];
1626
+ let checksumDataLength = 0;
1627
+ for (const record of records) {
1628
+ const recordLength = (6 + record.checksum.length + 3) & ~3;
1629
+ if (checksumDataLength + recordLength > 0xffffffff) {
1630
+ throw new RangeError("Classic PDB file-checksum subsection exceeds its 32-bit size limit");
1631
+ }
1632
+ if (record.sourceOffset !== null) {
1633
+ if (checksumOffsets.has(record.sourceOffset)) {
1634
+ throw new FormatError("Classic PDB file-checksum source offsets are duplicated");
1635
+ }
1636
+ checksumOffsets.set(record.sourceOffset, checksumDataLength);
1637
+ }
1638
+ const recordBytes = new Uint8Array(recordLength);
1639
+ const recordView = new DataView(recordBytes.buffer);
1640
+ recordView.setUint32(0, record.fileNameOffset, true);
1641
+ recordView.setUint8(4, record.checksum.length);
1642
+ recordView.setUint8(5, record.checksumKind);
1643
+ recordBytes.set(record.checksum, 6);
1644
+ checksumData.push(recordBytes);
1645
+ checksumDataLength += recordLength;
1646
+ }
1647
+ const rewrittenChecksumData = new Uint8Array(checksumDataLength);
1648
+ let checksumDataOffset = 0;
1649
+ for (const recordBytes of checksumData) {
1650
+ rewrittenChecksumData.set(recordBytes, checksumDataOffset);
1651
+ checksumDataOffset += recordBytes.length;
1652
+ }
1653
+
1654
+ const rewrittenSubsections = (module.c13?.subsections ?? []).map((subsection, index) => {
1655
+ if (index === checksumSubsectionIndex) {
1656
+ return { ...subsection, data: rewrittenChecksumData, sourceIndex: index };
1657
+ }
1658
+ if (subsection.kind !== 0xf2) return subsection;
1659
+ const data = Uint8Array.from(subsection.data);
1660
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1661
+ let blockOffset = 12;
1662
+ while (blockOffset < data.length) {
1663
+ const oldOffset = view.getUint32(blockOffset, true);
1664
+ const newOffset = checksumOffsets.get(oldOffset);
1665
+ if (newOffset === undefined) {
1666
+ throw new FormatError(
1667
+ "Classic PDB line block references a missing file-checksum record"
1668
+ );
1669
+ }
1670
+ view.setUint32(blockOffset, newOffset, true);
1671
+ blockOffset += view.getUint32(blockOffset + 8, true);
1672
+ }
1673
+ return { ...subsection, data, sourceIndex: index };
1674
+ });
1675
+ if (checksumSubsectionIndex < 0) {
1676
+ const subsection = { kind: 0xf4, data: rewrittenChecksumData };
1677
+ const firstLineSubsection = rewrittenSubsections.findIndex((entry) => entry.kind === 0xf2);
1678
+ if (firstLineSubsection < 0) rewrittenSubsections.push(subsection);
1679
+ else rewrittenSubsections.splice(firstLineSubsection, 0, subsection);
1680
+ }
1681
+ return rebuildClassicPdbC13Module(bytes, pdb, moduleIndex, rewrittenSubsections);
1682
+ }
1683
+
1684
+ export function replaceClassicPdbFileChecksum(
1685
+ input,
1686
+ { moduleIndex, fileChecksumOffset, checksum, checksumKind } = {}
1687
+ ) {
1688
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
1689
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
1690
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
1691
+ }
1692
+ if (!Number.isInteger(fileChecksumOffset) || fileChecksumOffset < 0) {
1693
+ throw new RangeError("Classic PDB file-checksum offset must be a non-negative integer");
1694
+ }
1695
+
1696
+ const pdb = parseClassicPdb(bytes);
1697
+ const module = pdb.dbi?.modules[moduleIndex];
1698
+ const originalChecksum = module?.c13?.fileChecksums.get(fileChecksumOffset);
1699
+ if (!module || !originalChecksum) {
1700
+ throw new RangeError("Classic PDB file-checksum location does not exist");
1701
+ }
1702
+ const resolvedChecksumKind = getClassicPdbChecksumKind(checksum, checksumKind);
1703
+
1704
+ const checksumSubsectionIndex = module.c13.subsections.findIndex(
1705
+ (subsection) => subsection.kind === 0xf4
1706
+ );
1707
+ if (checksumSubsectionIndex < 0) {
1708
+ throw new FormatError("Classic PDB file-checksum subsection is missing");
1709
+ }
1710
+ let c13Offset = 0;
1711
+ for (let index = 0; index < checksumSubsectionIndex; index++) {
1712
+ const subsection = module.c13.subsections[index];
1713
+ c13Offset += 8 + ((subsection.data.length + 3) & ~3);
1714
+ }
1715
+ let output;
1716
+ if (checksum.length === originalChecksum.checksum.length) {
1717
+ const streamOffset = module.symbolBytes + module.c11Bytes +
1718
+ c13Offset + 8 + fileChecksumOffset;
1719
+ output = replaceClassicPdbStreamBytes(bytes, pdb, module.streamIndex, streamOffset + 4,
1720
+ Uint8Array.of(checksum.length, resolvedChecksumKind));
1721
+ output = replaceClassicPdbStreamBytes(
1722
+ output,
1723
+ pdb,
1724
+ module.streamIndex,
1725
+ streamOffset + 6,
1726
+ checksum
1727
+ );
1728
+ } else {
1729
+ const records = [...module.c13.fileChecksums].map(([offset, record]) => {
1730
+ const isTarget = offset === fileChecksumOffset;
1731
+ return {
1732
+ sourceOffset: offset,
1733
+ fileNameOffset: record.fileNameOffset,
1734
+ checksum: isTarget ? checksum : record.checksum,
1735
+ checksumKind: isTarget ? resolvedChecksumKind : record.checksumKind
1736
+ };
1737
+ });
1738
+ output = rewriteClassicPdbFileChecksumRecords(bytes, pdb, moduleIndex, records);
1739
+ }
1740
+ parseClassicPdb(output);
1741
+ return output;
1742
+ }
1743
+
1744
+ export function insertClassicPdbFileChecksum(
1745
+ input,
1746
+ { moduleIndex, fileNameOffset, checksum, checksumKind } = {}
1747
+ ) {
1748
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
1749
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
1750
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
1751
+ }
1752
+ if (!Number.isInteger(fileNameOffset) || fileNameOffset < 0 ||
1753
+ fileNameOffset > 0xffffffff) {
1754
+ throw new RangeError("Classic PDB file-name offset must be an unsigned 32-bit integer");
1755
+ }
1756
+ const pdb = parseClassicPdb(bytes);
1757
+ const module = pdb.dbi?.modules[moduleIndex];
1758
+ if (!module) throw new RangeError("Classic PDB module index does not exist");
1759
+ if (pdb.info.stringTable && !pdb.info.stringTable.strings.has(fileNameOffset)) {
1760
+ throw new RangeError("Classic PDB file-name offset does not exist in the /names stream");
1761
+ }
1762
+ const resolvedChecksumKind = getClassicPdbChecksumKind(checksum, checksumKind);
1763
+ const records = [...(module.c13?.fileChecksums ?? [])].map(([sourceOffset, record]) => ({
1764
+ sourceOffset,
1765
+ fileNameOffset: record.fileNameOffset,
1766
+ checksum: record.checksum,
1767
+ checksumKind: record.checksumKind
1768
+ }));
1769
+ records.push({
1770
+ sourceOffset: null,
1771
+ fileNameOffset,
1772
+ checksum,
1773
+ checksumKind: resolvedChecksumKind
1774
+ });
1775
+ return rewriteClassicPdbFileChecksumRecords(bytes, pdb, moduleIndex, records);
1776
+ }
1777
+
1778
+ export function removeClassicPdbFileChecksum(
1779
+ input,
1780
+ { moduleIndex, fileChecksumOffset } = {}
1781
+ ) {
1782
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
1783
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
1784
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
1785
+ }
1786
+ if (!Number.isInteger(fileChecksumOffset) || fileChecksumOffset < 0) {
1787
+ throw new RangeError("Classic PDB file-checksum offset must be a non-negative integer");
1788
+ }
1789
+ const pdb = parseClassicPdb(bytes);
1790
+ const module = pdb.dbi?.modules[moduleIndex];
1791
+ const checksums = module?.c13?.fileChecksums;
1792
+ if (!module || !checksums?.has(fileChecksumOffset)) {
1793
+ throw new RangeError("Classic PDB file-checksum location does not exist");
1794
+ }
1795
+ if (module.c13.lines.some((subsection) =>
1796
+ subsection.blocks.some((block) => block.fileChecksumOffset === fileChecksumOffset)
1797
+ )) {
1798
+ throw new FormatError(
1799
+ `Classic PDB file-checksum record ${fileChecksumOffset} is referenced by line blocks`
1800
+ );
1801
+ }
1802
+ const records = [...checksums]
1803
+ .filter(([offset]) => offset !== fileChecksumOffset)
1804
+ .map(([sourceOffset, record]) => ({
1805
+ sourceOffset,
1806
+ fileNameOffset: record.fileNameOffset,
1807
+ checksum: record.checksum,
1808
+ checksumKind: record.checksumKind
1809
+ }));
1810
+ return rewriteClassicPdbFileChecksumRecords(bytes, pdb, moduleIndex, records);
1811
+ }
1812
+
1813
+ function serializeCodeViewC13Subsections(subsections, originalPadding) {
1814
+ let byteLength = 0;
1815
+ for (const subsection of subsections) {
1816
+ if (subsection.data.length > 0xffffffff) {
1817
+ throw new UnsupportedFormatError("Classic PDB C13 subsection exceeds its size limit");
1818
+ }
1819
+ byteLength += 8 + ((subsection.data.length + 3) & ~3);
1820
+ }
1821
+ const output = new Uint8Array(byteLength);
1822
+ const view = new DataView(output.buffer);
1823
+ let offset = 0;
1824
+ for (const [index, subsection] of subsections.entries()) {
1825
+ view.setUint32(offset, subsection.kind, true);
1826
+ view.setUint32(offset + 4, subsection.data.length, true);
1827
+ output.set(subsection.data, offset + 8);
1828
+ const paddingLength = ((subsection.data.length + 3) & ~3) - subsection.data.length;
1829
+ const padding = originalPadding[index];
1830
+ if (paddingLength > 0 && padding?.length === paddingLength) {
1831
+ output.set(padding, offset + 8 + subsection.data.length);
1832
+ }
1833
+ offset += 8 + subsection.data.length + paddingLength;
1834
+ }
1835
+ return output;
1836
+ }
1837
+
1838
+ function appendClassicPdbOverlay(bytes, pdb, output) {
1839
+ const overlay = bytes.subarray(pdb.numBlocks * pdb.blockSize);
1840
+ if (overlay.length === 0) return output;
1841
+ const outputWithOverlay = new Uint8Array(output.length + overlay.length);
1842
+ outputWithOverlay.set(output);
1843
+ outputWithOverlay.set(overlay, output.length);
1844
+ return outputWithOverlay;
1845
+ }
1846
+
1847
+ function rewriteClassicPdbSymbolStream(bytes, pdb, module, streamBytes, symbolBytes) {
1848
+ const stream = pdb.streams[module.streamIndex];
1849
+ if (!stream || module.streamIndex <= 3 || pdb.dbi.modules.some((other) =>
1850
+ other !== module && other.streamIndex === module.streamIndex
1851
+ )) {
1852
+ throw new UnsupportedFormatError(
1853
+ "Classic PDB symbol edits require a dedicated module stream"
1854
+ );
1855
+ }
1856
+ if (!Number.isInteger(symbolBytes) || symbolBytes < 4 || symbolBytes > 0xffffffff ||
1857
+ streamBytes.length < symbolBytes || streamBytes.length > 0xffffffff) {
1858
+ throw new UnsupportedFormatError("Classic PDB symbol edit exceeds supported stream-size limits");
1859
+ }
1860
+ const dbiBytes = Uint8Array.from(pdb.streams[3].bytes);
1861
+ const updatedSymbolBytes = new Uint8Array(4);
1862
+ new DataView(updatedSymbolBytes.buffer).setUint32(0, symbolBytes, true);
1863
+ dbiBytes.set(updatedSymbolBytes, module.moduleInfoOffset + 36);
1864
+
1865
+ let output;
1866
+ if (Math.ceil(streamBytes.length / pdb.blockSize) !== stream.blockIndices.length) {
1867
+ output = rebuildClassicPdbStreams(pdb, new Map([
1868
+ [module.streamIndex, streamBytes],
1869
+ [3, dbiBytes]
1870
+ ]));
1871
+ output = appendClassicPdbOverlay(bytes, pdb, output);
1872
+ } else {
1873
+ output = writeClassicPdbStream(bytes, pdb, module.streamIndex, streamBytes);
1874
+ output = replaceClassicPdbStreamBytes(
1875
+ output,
1876
+ pdb,
1877
+ 3,
1878
+ module.moduleInfoOffset + 36,
1879
+ updatedSymbolBytes
1880
+ );
1881
+ const updatedStreamSize = new Uint8Array(4);
1882
+ new DataView(updatedStreamSize.buffer).setUint32(0, streamBytes.length, true);
1883
+ output = replaceClassicPdbDirectoryBytes(
1884
+ output,
1885
+ pdb,
1886
+ 4 + 4 * module.streamIndex,
1887
+ updatedStreamSize
1888
+ );
1889
+ }
1890
+ parseClassicPdb(output);
1891
+ return output;
1892
+ }
1893
+
1894
+ function rebuildClassicPdbC13Module(bytes, pdb, moduleIndex, subsections) {
1895
+ const module = pdb.dbi?.modules[moduleIndex];
1896
+ const originalStream = module && pdb.streams[module.streamIndex]?.bytes;
1897
+ if (!module || !originalStream || module.streamIndex <= 3 ||
1898
+ pdb.dbi.modules.some((other, index) =>
1899
+ index !== moduleIndex && other.streamIndex === module.streamIndex
1900
+ )) {
1901
+ throw new UnsupportedFormatError(
1902
+ "Classic PDB C13 edits require a dedicated module stream"
1903
+ );
1904
+ }
1905
+ const originalC13Offset = module.symbolBytes + module.c11Bytes;
1906
+ const originalSubsections = module.c13?.subsections ?? [];
1907
+ const paddingBySubsection = new Map();
1908
+ let subsectionOffset = 0;
1909
+ for (const subsection of originalSubsections) {
1910
+ const paddingStart = subsectionOffset + 8 + subsection.data.length;
1911
+ const paddedEnd = (paddingStart + 3) & ~3;
1912
+ paddingBySubsection.set(subsection, originalStream.slice(
1913
+ originalC13Offset + paddingStart,
1914
+ originalC13Offset + paddedEnd
1915
+ ));
1916
+ subsectionOffset = paddedEnd;
1917
+ }
1918
+ const rewrittenPadding = subsections.map((subsection) => {
1919
+ const sourceIndex = Number.isInteger(subsection.sourceIndex)
1920
+ ? subsection.sourceIndex
1921
+ : originalSubsections.indexOf(subsection);
1922
+ return sourceIndex < 0
1923
+ ? new Uint8Array()
1924
+ : paddingBySubsection.get(originalSubsections[sourceIndex]) ?? new Uint8Array();
1925
+ });
1926
+ const rewrittenC13 = serializeCodeViewC13Subsections(subsections, rewrittenPadding);
1927
+ if (rewrittenC13.length > 0xffffffff) {
1928
+ throw new RangeError("Classic PDB C13 module data exceeds its 32-bit size limit");
1929
+ }
1930
+ const trailingOffset = originalC13Offset + module.c13Bytes;
1931
+ const trailingBytes = originalStream.subarray(trailingOffset);
1932
+ const rewrittenModuleStream = new Uint8Array(
1933
+ originalC13Offset + rewrittenC13.length + trailingBytes.length
1934
+ );
1935
+ rewrittenModuleStream.set(originalStream.subarray(0, originalC13Offset));
1936
+ rewrittenModuleStream.set(rewrittenC13, originalC13Offset);
1937
+ rewrittenModuleStream.set(trailingBytes, originalC13Offset + rewrittenC13.length);
1938
+
1939
+ const dbiStream = pdb.streams[3]?.bytes;
1940
+ if (!dbiStream) throw new FormatError("Classic PDB DBI stream is missing");
1941
+ const rewrittenDbi = Uint8Array.from(dbiStream);
1942
+ new DataView(rewrittenDbi.buffer).setUint32(
1943
+ module.moduleInfoOffset + 44,
1944
+ rewrittenC13.length,
1945
+ true
1946
+ );
1947
+ let output = rebuildClassicPdbStreams(pdb, new Map([
1948
+ [module.streamIndex, rewrittenModuleStream],
1949
+ [3, rewrittenDbi]
1950
+ ]));
1951
+ output = appendClassicPdbOverlay(bytes, pdb, output);
1952
+ parseClassicPdb(output);
1953
+ return output;
1954
+ }
1955
+
1956
+ export function removeClassicPdbSymbol(input, { moduleIndex, recordIndex } = {}) {
1957
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
1958
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
1959
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
1960
+ }
1961
+ if (!Number.isInteger(recordIndex) || recordIndex < 0) {
1962
+ throw new RangeError("Classic PDB symbol record index must be a non-negative integer");
1963
+ }
1964
+ const pdb = parseClassicPdb(bytes);
1965
+ const module = pdb.dbi?.modules[moduleIndex];
1966
+ if (!module?.symbols) {
1967
+ throw new RangeError(`Classic PDB module ${moduleIndex} has no symbol records`);
1968
+ }
1969
+ const record = module.symbols.records[recordIndex];
1970
+ if (!record) throw new RangeError(`Classic PDB symbol record ${recordIndex} does not exist`);
1971
+ if (!["public", "data", "constant", "threadData"].includes(record.decoded?.kind)) {
1972
+ throw new UnsupportedFormatError(
1973
+ "Classic PDB symbol removal supports only standalone public, data, constant, and thread-data symbols"
1974
+ );
1975
+ }
1976
+ if (module.symbols.records.some((symbol) =>
1977
+ codeViewScopeSymbolKinds.has(symbol.kind) || symbol.decoded === undefined
1978
+ )) {
1979
+ throw new UnsupportedFormatError(
1980
+ "Classic PDB symbol removal cannot rewrite modules with scoped or unknown symbols"
1981
+ );
1982
+ }
1983
+ const stream = pdb.streams[module.streamIndex];
1984
+ const recordSize = 4 + record.data.length;
1985
+ const newSymbolBytes = module.symbolBytes - recordSize;
1986
+ const newStreamSize = stream.size - recordSize;
1987
+ if (newSymbolBytes < 4 || newSymbolBytes > 0xffffffff ||
1988
+ newStreamSize < newSymbolBytes || newStreamSize > 0xffffffff) {
1989
+ throw new UnsupportedFormatError("Classic PDB symbol removal exceeds supported stream-size limits");
1990
+ }
1991
+ const streamBytes = new Uint8Array(newStreamSize);
1992
+ streamBytes.set(stream.bytes.subarray(0, record.offset));
1993
+ streamBytes.set(
1994
+ stream.bytes.subarray(record.offset + recordSize),
1995
+ record.offset
1996
+ );
1997
+ return rewriteClassicPdbSymbolStream(bytes, pdb, module, streamBytes, newSymbolBytes);
1998
+ }
1999
+
2000
+ function encodeClassicPdbNamedSymbolRecord(kind, fields, nameBytes, symbolOffset) {
2001
+ const recordDataEnd = symbolOffset + 4 + fields.length + nameBytes.length + 1;
2002
+ const paddingSize = (4 - (recordDataEnd % 4)) % 4;
2003
+ const recordLength = 2 + fields.length + nameBytes.length + 1 + paddingSize;
2004
+ if (recordLength > 0xffff) {
2005
+ throw new RangeError("Classic PDB symbol record exceeds its 16-bit size limit");
2006
+ }
2007
+ const recordBytes = new Uint8Array(recordLength + 2);
2008
+ const recordView = new DataView(recordBytes.buffer);
2009
+ recordView.setUint16(0, recordLength, true);
2010
+ recordView.setUint16(2, kind, true);
2011
+ recordBytes.set(fields, 4);
2012
+ recordBytes.set(nameBytes, 4 + fields.length);
2013
+ recordBytes[4 + fields.length + nameBytes.length] = 0;
2014
+ for (let index = 0; index < paddingSize; index++) {
2015
+ recordBytes[5 + fields.length + nameBytes.length + index] =
2016
+ 0xf0 + paddingSize - index;
2017
+ }
2018
+ return recordBytes;
2019
+ }
2020
+
2021
+ function insertClassicPdbModuleSymbol(input, {
2022
+ moduleIndex,
2023
+ kind,
2024
+ fields,
2025
+ name,
2026
+ nameLabel
2027
+ } = {}) {
2028
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
2029
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
2030
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
2031
+ }
2032
+ const nameBytes = encodeClassicPdbReplacementText(name, nameLabel);
2033
+ const pdb = parseClassicPdb(bytes);
2034
+ const module = pdb.dbi?.modules[moduleIndex];
2035
+ if (!module) throw new RangeError(`Classic PDB module ${moduleIndex} does not exist`);
2036
+ const stream = pdb.streams[module.streamIndex];
2037
+ if (!stream?.bytes) {
2038
+ throw new UnsupportedFormatError("Classic PDB symbol insertion requires a module stream");
2039
+ }
2040
+ const symbolBytes = module.symbolBytes === 0 ? 4 : module.symbolBytes;
2041
+ const recordBytes = encodeClassicPdbNamedSymbolRecord(kind, fields, nameBytes, symbolBytes);
2042
+ const recordSize = recordBytes.length;
2043
+ const newSymbolBytes = symbolBytes + recordSize;
2044
+ const insertedSymbolBytes = newSymbolBytes - module.symbolBytes;
2045
+ const newStreamSize = stream.size + insertedSymbolBytes;
2046
+ if (newSymbolBytes > 0xffffffff || newStreamSize > 0xffffffff) {
2047
+ throw new RangeError("Classic PDB symbol insertion exceeds its 32-bit size limit");
2048
+ }
2049
+
2050
+ const streamBytes = new Uint8Array(newStreamSize);
2051
+ if (module.symbolBytes === 0) {
2052
+ new DataView(streamBytes.buffer).setUint32(0, 4, true);
2053
+ } else {
2054
+ streamBytes.set(stream.bytes.subarray(0, module.symbolBytes));
2055
+ }
2056
+ streamBytes.set(recordBytes, symbolBytes);
2057
+ streamBytes.set(
2058
+ stream.bytes.subarray(module.symbolBytes),
2059
+ newSymbolBytes
2060
+ );
2061
+ return rewriteClassicPdbSymbolStream(bytes, pdb, module, streamBytes, newSymbolBytes);
2062
+ }
2063
+
2064
+ export function insertClassicPdbPublicSymbol(
2065
+ input,
2066
+ { moduleIndex, flags = 0, offset, segment, name } = {}
2067
+ ) {
2068
+ if (!Number.isInteger(flags) || flags < 0 || flags > 0xffffffff) {
2069
+ throw new RangeError("Classic PDB public-symbol flags must be an unsigned 32-bit integer");
2070
+ }
2071
+ if (!Number.isInteger(offset) || offset < 0 || offset > 0xffffffff) {
2072
+ throw new RangeError("Classic PDB public-symbol offset must be an unsigned 32-bit integer");
2073
+ }
2074
+ if (!Number.isInteger(segment) || segment < 0 || segment > 0xffff) {
2075
+ throw new RangeError("Classic PDB public-symbol segment must be an unsigned 16-bit integer");
2076
+ }
2077
+ const fields = new Uint8Array(10);
2078
+ const view = new DataView(fields.buffer);
2079
+ view.setUint32(0, flags, true);
2080
+ view.setUint32(4, offset, true);
2081
+ view.setUint16(8, segment, true);
2082
+ return insertClassicPdbModuleSymbol(input, {
2083
+ moduleIndex,
2084
+ kind: 0x110e,
2085
+ fields,
2086
+ name,
2087
+ nameLabel: "Classic PDB public-symbol names"
2088
+ });
2089
+ }
2090
+
2091
+ export function insertClassicPdbDataSymbol(
2092
+ input,
2093
+ { moduleIndex, typeIndex, offset, segment, name } = {}
2094
+ ) {
2095
+ if (!Number.isInteger(typeIndex) || typeIndex < 0 || typeIndex > 0xffffffff) {
2096
+ throw new RangeError("Classic PDB data-symbol type index must be an unsigned 32-bit integer");
2097
+ }
2098
+ if (!Number.isInteger(offset) || offset < 0 || offset > 0xffffffff) {
2099
+ throw new RangeError("Classic PDB data-symbol offset must be an unsigned 32-bit integer");
2100
+ }
2101
+ if (!Number.isInteger(segment) || segment < 0 || segment > 0xffff) {
2102
+ throw new RangeError("Classic PDB data-symbol segment must be an unsigned 16-bit integer");
2103
+ }
2104
+ const fields = new Uint8Array(10);
2105
+ const view = new DataView(fields.buffer);
2106
+ view.setUint32(0, typeIndex, true);
2107
+ view.setUint32(4, offset, true);
2108
+ view.setUint16(8, segment, true);
2109
+ return insertClassicPdbModuleSymbol(input, {
2110
+ moduleIndex,
2111
+ kind: 0x110c,
2112
+ fields,
2113
+ name,
2114
+ nameLabel: "Classic PDB data-symbol names"
2115
+ });
2116
+ }
2117
+
2118
+ export function insertClassicPdbThreadDataSymbol(
2119
+ input,
2120
+ { moduleIndex, typeIndex, offset, segment, name } = {}
2121
+ ) {
2122
+ if (!Number.isInteger(typeIndex) || typeIndex < 0 || typeIndex > 0xffffffff) {
2123
+ throw new RangeError("Classic PDB thread-data symbol type index must be an unsigned 32-bit integer");
2124
+ }
2125
+ if (!Number.isInteger(offset) || offset < 0 || offset > 0xffffffff) {
2126
+ throw new RangeError("Classic PDB thread-data symbol offset must be an unsigned 32-bit integer");
2127
+ }
2128
+ if (!Number.isInteger(segment) || segment < 0 || segment > 0xffff) {
2129
+ throw new RangeError("Classic PDB thread-data symbol segment must be an unsigned 16-bit integer");
2130
+ }
2131
+ const fields = new Uint8Array(10);
2132
+ const view = new DataView(fields.buffer);
2133
+ view.setUint32(0, typeIndex, true);
2134
+ view.setUint32(4, offset, true);
2135
+ view.setUint16(8, segment, true);
2136
+ return insertClassicPdbModuleSymbol(input, {
2137
+ moduleIndex,
2138
+ kind: 0x1112,
2139
+ fields,
2140
+ name,
2141
+ nameLabel: "Classic PDB thread-data symbol names"
2142
+ });
2143
+ }
2144
+
2145
+ function replaceClassicPdbAddressSymbol(input, options, {
2146
+ recordKind,
2147
+ decodedKind,
2148
+ symbolLabel
2149
+ }) {
2150
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
2151
+ const { moduleIndex, recordIndex } = options;
2152
+ const updatesTypeIndex = Object.hasOwn(options, "typeIndex");
2153
+ const updatesOffset = Object.hasOwn(options, "offset");
2154
+ const updatesSegment = Object.hasOwn(options, "segment");
2155
+ if (!updatesTypeIndex && !updatesOffset && !updatesSegment) {
2156
+ throw new TypeError(`Classic PDB ${symbolLabel} replacement must include at least one field`);
2157
+ }
2158
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
2159
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
2160
+ }
2161
+ if (!Number.isInteger(recordIndex) || recordIndex < 0) {
2162
+ throw new RangeError("Classic PDB symbol record index must be a non-negative integer");
2163
+ }
2164
+ const pdb = parseClassicPdb(bytes);
2165
+ const module = pdb.dbi?.modules[moduleIndex];
2166
+ if (!module?.symbols) {
2167
+ throw new RangeError(`Classic PDB module ${moduleIndex} has no symbol records`);
2168
+ }
2169
+ const record = module.symbols.records[recordIndex];
2170
+ if (!record) throw new RangeError(`Classic PDB symbol record ${recordIndex} does not exist`);
2171
+ if (record.kind !== recordKind || record.decoded?.kind !== decodedKind) {
2172
+ throw new UnsupportedFormatError(
2173
+ `Classic PDB symbol record is not a module-local ${symbolLabel} symbol`
2174
+ );
2175
+ }
2176
+ const typeIndex = updatesTypeIndex ? options.typeIndex : record.decoded.typeIndex;
2177
+ const offset = updatesOffset ? options.offset : record.decoded.offset;
2178
+ const segment = updatesSegment ? options.segment : record.decoded.segment;
2179
+ if (!Number.isInteger(typeIndex) || typeIndex < 0 || typeIndex > 0xffffffff) {
2180
+ throw new RangeError(`Classic PDB ${symbolLabel} symbol type index must be an unsigned 32-bit integer`);
2181
+ }
2182
+ if (!Number.isInteger(offset) || offset < 0 || offset > 0xffffffff) {
2183
+ throw new RangeError(`Classic PDB ${symbolLabel} symbol offset must be an unsigned 32-bit integer`);
2184
+ }
2185
+ if (!Number.isInteger(segment) || segment < 0 || segment > 0xffff) {
2186
+ throw new RangeError(`Classic PDB ${symbolLabel} symbol segment must be an unsigned 16-bit integer`);
2187
+ }
2188
+ const fields = new Uint8Array(10);
2189
+ const view = new DataView(fields.buffer);
2190
+ view.setUint32(0, typeIndex, true);
2191
+ view.setUint32(4, offset, true);
2192
+ view.setUint16(8, segment, true);
2193
+ const nameBytes = encodeClassicPdbReplacementText(
2194
+ record.decoded.name,
2195
+ `Classic PDB ${symbolLabel} symbol names`
2196
+ );
2197
+ const recordBytes = encodeClassicPdbNamedSymbolRecord(
2198
+ record.kind,
2199
+ fields,
2200
+ nameBytes,
2201
+ record.offset
2202
+ );
2203
+ return replaceClassicPdbSymbolRecord(bytes, pdb, module, moduleIndex, record, recordBytes);
2204
+ }
2205
+
2206
+ export function replaceClassicPdbDataSymbol(input, options = {}) {
2207
+ return replaceClassicPdbAddressSymbol(input, options, {
2208
+ recordKind: 0x110c,
2209
+ decodedKind: "data",
2210
+ symbolLabel: "data"
2211
+ });
2212
+ }
2213
+
2214
+ export function replaceClassicPdbThreadDataSymbol(input, options = {}) {
2215
+ return replaceClassicPdbAddressSymbol(input, options, {
2216
+ recordKind: 0x1112,
2217
+ decodedKind: "threadData",
2218
+ symbolLabel: "thread-data"
2219
+ });
2220
+ }
2221
+
2222
+ export function insertClassicPdbConstantSymbol(
2223
+ input,
2224
+ { moduleIndex, typeIndex, value, name } = {}
2225
+ ) {
2226
+ if (!Number.isInteger(typeIndex) || typeIndex < 0 || typeIndex > 0xffffffff) {
2227
+ throw new RangeError("Classic PDB constant-symbol type index must be an unsigned 32-bit integer");
2228
+ }
2229
+ const numeric = encodeCodeViewNumeric(value);
2230
+ const fields = new Uint8Array(4 + numeric.length);
2231
+ new DataView(fields.buffer).setUint32(0, typeIndex, true);
2232
+ fields.set(numeric, 4);
2233
+ return insertClassicPdbModuleSymbol(input, {
2234
+ moduleIndex,
2235
+ kind: 0x1107,
2236
+ fields,
2237
+ name,
2238
+ nameLabel: "Classic PDB constant-symbol names"
2239
+ });
2240
+ }
2241
+
2242
+ function replaceClassicPdbSymbolRecord(bytes, pdb, module, moduleIndex, record, recordBytes) {
2243
+ const recordSize = recordBytes.length;
2244
+ const oldRecordSize = 4 + record.data.length;
2245
+ const sizeDelta = recordSize - oldRecordSize;
2246
+ const newSymbolBytes = module.symbolBytes + sizeDelta;
2247
+ const stream = pdb.streams[module.streamIndex];
2248
+ const newStreamSize = stream.size + sizeDelta;
2249
+ if (sizeDelta !== 0 && module.symbols.records.some((symbol) =>
2250
+ codeViewScopeSymbolKinds.has(symbol.kind) || symbol.decoded === undefined
2251
+ )) {
2252
+ throw new UnsupportedFormatError(
2253
+ "Classic PDB symbol replacement cannot resize modules with scoped or unknown symbols"
2254
+ );
2255
+ }
2256
+ if (sizeDelta !== 0 && (
2257
+ module.streamIndex <= 3 ||
2258
+ pdb.dbi.modules.some((other, index) =>
2259
+ index !== moduleIndex && other.streamIndex === module.streamIndex
2260
+ )
2261
+ )) {
2262
+ throw new UnsupportedFormatError(
2263
+ "Classic PDB symbol replacement cannot resize a reserved or shared module stream"
2264
+ );
2265
+ }
2266
+ if (newSymbolBytes < 4 || newSymbolBytes > 0xffffffff ||
2267
+ newStreamSize < newSymbolBytes || newStreamSize > 0xffffffff) {
2268
+ throw new UnsupportedFormatError(
2269
+ "Classic PDB symbol replacement exceeds supported stream-size limits"
2270
+ );
2271
+ }
2272
+ const streamBytes = new Uint8Array(newStreamSize);
2273
+ streamBytes.set(stream.bytes.subarray(0, record.offset));
2274
+ streamBytes.set(recordBytes, record.offset);
2275
+ streamBytes.set(
2276
+ stream.bytes.subarray(record.offset + oldRecordSize),
2277
+ record.offset + recordSize
2278
+ );
2279
+ if (sizeDelta !== 0) {
2280
+ return rewriteClassicPdbSymbolStream(bytes, pdb, module, streamBytes, newSymbolBytes);
2281
+ }
2282
+ const output = writeClassicPdbStream(bytes, pdb, module.streamIndex, streamBytes);
2283
+ parseClassicPdb(output);
2284
+ return output;
2285
+ }
2286
+
2287
+ export function replaceClassicPdbConstantSymbol(input, options = {}) {
2288
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
2289
+ const { moduleIndex, recordIndex, value } = options;
2290
+ const typeIndexWasProvided = Object.hasOwn(options, "typeIndex");
2291
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
2292
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
2293
+ }
2294
+ if (!Number.isInteger(recordIndex) || recordIndex < 0) {
2295
+ throw new RangeError("Classic PDB symbol record index must be a non-negative integer");
2296
+ }
2297
+ const pdb = parseClassicPdb(bytes);
2298
+ const module = pdb.dbi?.modules[moduleIndex];
2299
+ if (!module?.symbols) {
2300
+ throw new RangeError(`Classic PDB module ${moduleIndex} has no symbol records`);
2301
+ }
2302
+ const record = module.symbols.records[recordIndex];
2303
+ if (!record) throw new RangeError(`Classic PDB symbol record ${recordIndex} does not exist`);
2304
+ if (record.decoded?.kind !== "constant" ||
2305
+ !Number.isInteger(record.decoded.typeIndex) ||
2306
+ typeof record.decoded.name !== "string") {
2307
+ throw new UnsupportedFormatError("Classic PDB symbol record is not a supported constant");
2308
+ }
2309
+ const typeIndex = typeIndexWasProvided ? options.typeIndex : record.decoded.typeIndex;
2310
+ if (!Number.isInteger(typeIndex) || typeIndex < 0 || typeIndex > 0xffffffff) {
2311
+ throw new RangeError("Classic PDB constant-symbol type index must be an unsigned 32-bit integer");
2312
+ }
2313
+ const numeric = encodeCodeViewNumeric(value);
2314
+ const fields = new Uint8Array(4 + numeric.length);
2315
+ new DataView(fields.buffer).setUint32(0, typeIndex, true);
2316
+ fields.set(numeric, 4);
2317
+ const nameBytes = encodeClassicPdbReplacementText(
2318
+ record.decoded.name,
2319
+ "Classic PDB constant-symbol names"
2320
+ );
2321
+ const recordBytes = encodeClassicPdbNamedSymbolRecord(
2322
+ 0x1107,
2323
+ fields,
2324
+ nameBytes,
2325
+ record.offset
2326
+ );
2327
+ return replaceClassicPdbSymbolRecord(bytes, pdb, module, moduleIndex, record, recordBytes);
2328
+ }
2329
+
2330
+ export function replaceClassicPdbPublicSymbol(input, options = {}) {
2331
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
2332
+ const { moduleIndex, recordIndex } = options;
2333
+ const updatesFlags = Object.hasOwn(options, "flags");
2334
+ const updatesOffset = Object.hasOwn(options, "offset");
2335
+ const updatesSegment = Object.hasOwn(options, "segment");
2336
+ if (!updatesFlags && !updatesOffset && !updatesSegment) {
2337
+ throw new TypeError("Classic PDB public-symbol replacement must include at least one field");
2338
+ }
2339
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
2340
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
2341
+ }
2342
+ if (!Number.isInteger(recordIndex) || recordIndex < 0) {
2343
+ throw new RangeError("Classic PDB symbol record index must be a non-negative integer");
2344
+ }
2345
+ const pdb = parseClassicPdb(bytes);
2346
+ const module = pdb.dbi?.modules[moduleIndex];
2347
+ if (!module?.symbols) {
2348
+ throw new RangeError(`Classic PDB module ${moduleIndex} has no symbol records`);
2349
+ }
2350
+ const record = module.symbols.records[recordIndex];
2351
+ if (!record) throw new RangeError(`Classic PDB symbol record ${recordIndex} does not exist`);
2352
+ if (record.kind !== 0x110e || record.decoded?.kind !== "public") {
2353
+ throw new UnsupportedFormatError("Classic PDB symbol record is not a supported public symbol");
2354
+ }
2355
+ const flags = updatesFlags ? options.flags : record.decoded.flags;
2356
+ const offset = updatesOffset ? options.offset : record.decoded.offset;
2357
+ const segment = updatesSegment ? options.segment : record.decoded.segment;
2358
+ if (!Number.isInteger(flags) || flags < 0 || flags > 0xffffffff) {
2359
+ throw new RangeError("Classic PDB public-symbol flags must be an unsigned 32-bit integer");
2360
+ }
2361
+ if (!Number.isInteger(offset) || offset < 0 || offset > 0xffffffff) {
2362
+ throw new RangeError("Classic PDB public-symbol offset must be an unsigned 32-bit integer");
2363
+ }
2364
+ if (!Number.isInteger(segment) || segment < 0 || segment > 0xffff) {
2365
+ throw new RangeError("Classic PDB public-symbol segment must be an unsigned 16-bit integer");
2366
+ }
2367
+ const fields = new Uint8Array(10);
2368
+ const view = new DataView(fields.buffer);
2369
+ view.setUint32(0, flags, true);
2370
+ view.setUint32(4, offset, true);
2371
+ view.setUint16(8, segment, true);
2372
+ const nameBytes = encodeClassicPdbReplacementText(
2373
+ record.decoded.name,
2374
+ "Classic PDB public-symbol names"
2375
+ );
2376
+ const recordBytes = encodeClassicPdbNamedSymbolRecord(
2377
+ record.kind,
2378
+ fields,
2379
+ nameBytes,
2380
+ record.offset
2381
+ );
2382
+ return replaceClassicPdbSymbolRecord(bytes, pdb, module, moduleIndex, record, recordBytes);
2383
+ }
2384
+
2385
+ export function replaceClassicPdbSymbolName(input, { moduleIndex, recordIndex, name } = {}) {
2386
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
2387
+ if (!Number.isInteger(moduleIndex) || moduleIndex < 0) {
2388
+ throw new RangeError("Classic PDB module index must be a non-negative integer");
2389
+ }
2390
+ if (!Number.isInteger(recordIndex) || recordIndex < 0) {
2391
+ throw new RangeError("Classic PDB symbol record index must be a non-negative integer");
2392
+ }
2393
+ const replacement = encodeClassicPdbReplacementText(name, "Classic PDB symbol names");
2394
+ const pdb = parseClassicPdb(bytes);
2395
+ const module = pdb.dbi?.modules[moduleIndex];
2396
+ if (!module?.symbols) {
2397
+ throw new RangeError(`Classic PDB module ${moduleIndex} has no symbol records`);
2398
+ }
2399
+ const record = module.symbols.records[recordIndex];
2400
+ if (!record) throw new RangeError(`Classic PDB symbol record ${recordIndex} does not exist`);
2401
+ const nameOffset = getCodeViewSymbolNameOffset(record);
2402
+ if (nameOffset === null || typeof record.decoded?.name !== "string") {
2403
+ throw new UnsupportedFormatError("Classic PDB symbol record does not expose a replaceable name");
2404
+ }
2405
+ const recordDataEnd = record.offset + 4 + nameOffset + replacement.length + 1;
2406
+ const paddingSize = (4 - (recordDataEnd % 4)) % 4;
2407
+ const replacementData = new Uint8Array(nameOffset + replacement.length + 1 + paddingSize);
2408
+ replacementData.set(record.data.subarray(0, nameOffset));
2409
+ replacementData.set(replacement, nameOffset);
2410
+ replacementData[nameOffset + replacement.length] = 0;
2411
+ if (paddingSize > 0) {
2412
+ for (let index = 0; index < paddingSize; index++) {
2413
+ replacementData[nameOffset + replacement.length + 1 + index] = 0xf0 + paddingSize - index;
2414
+ }
2415
+ }
2416
+ const recordSize = 4 + replacementData.length;
2417
+ const recordBytes = new Uint8Array(recordSize);
2418
+ const recordView = new DataView(recordBytes.buffer);
2419
+ recordView.setUint16(0, 2 + replacementData.length, true);
2420
+ recordView.setUint16(2, record.kind, true);
2421
+ recordBytes.set(replacementData, 4);
2422
+ return replaceClassicPdbSymbolRecord(bytes, pdb, module, moduleIndex, record, recordBytes);
2423
+ }
2424
+
2425
+ export function replaceClassicPdbStringTableName(input, { nameOffset, name } = {}) {
2426
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
2427
+ if (!Number.isInteger(nameOffset) || nameOffset < 0) {
2428
+ throw new RangeError("Classic PDB string-table offset must be a non-negative integer");
2429
+ }
2430
+ const replacement = encodeClassicPdbReplacementText(name, "Classic PDB string-table names");
2431
+ const pdb = parseClassicPdb(bytes);
2432
+ const streamIndex = pdb.info.namedStreams.get("/names");
2433
+ if (streamIndex === undefined || !pdb.info.stringTable) {
2434
+ throw new UnsupportedFormatError("Classic PDB has no /names string table");
2435
+ }
2436
+ const existingName = pdb.info.stringTable.strings.get(nameOffset);
2437
+ if (existingName === undefined) {
2438
+ throw new RangeError(`Classic PDB string-table offset ${nameOffset} does not exist`);
2439
+ }
2440
+ const existingBytes = new TextEncoder().encode(existingName);
2441
+ if (replacement.length === existingBytes.length) {
2442
+ const output = replaceClassicPdbStreamBytes(
2443
+ bytes, pdb, streamIndex, 12 + nameOffset, replacement
2444
+ );
2445
+ parseClassicPdb(output);
2446
+ return output;
2447
+ }
2448
+
2449
+ const updatedStrings = [];
2450
+ const remappedOffsets = new Map();
2451
+ const encoder = new TextEncoder();
2452
+ let updatedStringByteLength = 0;
2453
+ for (const [offset, value] of [...pdb.info.stringTable.strings].sort((a, b) => a[0] - b[0])) {
2454
+ remappedOffsets.set(offset, updatedStringByteLength);
2455
+ const encoded = offset === nameOffset ? replacement : encoder.encode(value);
2456
+ updatedStrings.push(encoded);
2457
+ updatedStrings.push(Uint8Array.of(0));
2458
+ updatedStringByteLength += encoded.length + 1;
2459
+ }
2460
+ if (updatedStringByteLength > 0xffffffff) {
2461
+ throw new UnsupportedFormatError("Classic PDB /names string table exceeds its 32-bit size limit");
2462
+ }
2463
+ const stringBytes = new Uint8Array(updatedStringByteLength);
2464
+ let stringByteOffset = 0;
2465
+ for (const part of updatedStrings) {
2466
+ stringBytes.set(part, stringByteOffset);
2467
+ stringByteOffset += part.length;
2468
+ }
2469
+ const stringTable = pdb.info.stringTable;
2470
+ const nameIds = stringTable.nameIds.map((offset) => {
2471
+ const updatedOffset = offset === 0 ? 0 : remappedOffsets.get(offset);
2472
+ if (updatedOffset === undefined) {
2473
+ throw new FormatError("Classic PDB /names hash table references a missing string");
2474
+ }
2475
+ return updatedOffset;
2476
+ });
2477
+ const namesStream = new Uint8Array(20 + stringBytes.length + nameIds.length * 4);
2478
+ const namesView = new DataView(namesStream.buffer);
2479
+ namesView.setUint32(0, stringTable.signature, true);
2480
+ namesView.setUint32(4, stringTable.hashVersion, true);
2481
+ namesView.setUint32(8, stringBytes.length, true);
2482
+ namesStream.set(stringBytes, 12);
2483
+ let namesOffset = 12 + stringBytes.length;
2484
+ namesView.setUint32(namesOffset, nameIds.length, true);
2485
+ namesOffset += 4;
2486
+ for (const offset of nameIds) {
2487
+ namesView.setUint32(namesOffset, offset, true);
2488
+ namesOffset += 4;
2489
+ }
2490
+ namesView.setUint32(namesOffset, stringTable.nameCount, true);
2491
+
2492
+ const replacements = new Map([[streamIndex, namesStream]]);
2493
+ const mutableModuleStreams = new Map();
2494
+ for (const module of pdb.dbi?.modules ?? []) {
2495
+ if (!module.c13) continue;
2496
+ const stream = mutableModuleStreams.get(module.streamIndex) ??
2497
+ Uint8Array.from(pdb.streams[module.streamIndex].bytes);
2498
+ let updated = false;
2499
+ let subsectionOffset = 0;
2500
+ for (const subsection of module.c13.subsections) {
2501
+ if (subsection.kind === 0xf4) {
2502
+ const checksumData = Uint8Array.from(subsection.data);
2503
+ const checksumView = new DataView(checksumData.buffer);
2504
+ for (const [recordOffset, checksum] of module.c13.fileChecksums) {
2505
+ const updatedOffset = remappedOffsets.get(checksum.fileNameOffset);
2506
+ if (updatedOffset === undefined) {
2507
+ throw new FormatError("Classic PDB C13 checksum references a missing /names entry");
2508
+ }
2509
+ if (updatedOffset === checksum.fileNameOffset) continue;
2510
+ checksumView.setUint32(recordOffset, updatedOffset, true);
2511
+ updated = true;
2512
+ }
2513
+ if (updated) {
2514
+ stream.set(
2515
+ checksumData,
2516
+ module.symbolBytes + module.c11Bytes + subsectionOffset + 8
2517
+ );
2518
+ }
2519
+ }
2520
+ subsectionOffset = (subsectionOffset + 8 + subsection.data.length + 3) & ~3;
2521
+ }
2522
+ if (updated) mutableModuleStreams.set(module.streamIndex, stream);
2523
+ }
2524
+ for (const [moduleStreamIndex, moduleStream] of mutableModuleStreams) {
2525
+ replacements.set(moduleStreamIndex, moduleStream);
2526
+ }
2527
+ const output = rebuildClassicPdbStreams(pdb, replacements);
2528
+ parseClassicPdb(output);
2529
+ return output;
2530
+ }