jsilnet 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -3
- package/REFERENCE.md +191 -0
- package/dist/browser/index.js +345 -0
- package/dist/browser/worker.js +42480 -0
- package/example/README.md +22 -0
- package/example/Sample.cs +10 -0
- package/example/Sample.csproj +7 -0
- package/example/edit-constant.js +48 -0
- package/example/inspect.js +37 -0
- package/package.json +61 -6
- package/src/assembly/instruction-editing.js +165 -0
- package/src/assembly/method-removal.js +789 -0
- package/src/assembly/pointer-table-editing.js +506 -0
- package/src/assembly/portable-pdb-editing.js +441 -0
- package/src/binary/errors.js +17 -0
- package/src/binary/reader.js +206 -0
- package/src/browser/index.d.ts +123 -0
- package/src/browser/index.js +373 -0
- package/src/browser/worker-runtime.js +287 -0
- package/src/browser/worker.js +47 -0
- package/src/il/body.js +165 -0
- package/src/il/instructions.js +217 -0
- package/src/il/opcodes.js +102 -0
- package/src/il/rewrite.js +331 -0
- package/src/index.d.ts +267 -0
- package/src/index.js +13307 -0
- package/src/metadata/classic-pdb.js +2530 -0
- package/src/metadata/custom-attributes.js +387 -0
- package/src/metadata/portable-pdb.js +3347 -0
- package/src/metadata/resources.js +37 -0
- package/src/metadata/root-writer.js +54 -0
- package/src/metadata/signatures.js +339 -0
- package/src/metadata/streams.js +285 -0
- package/src/metadata/strong-name.js +230 -0
- package/src/metadata/tables.js +390 -0
- package/src/metadata/writer.js +191 -0
- package/src/pe/headers.js +491 -0
- package/src/platform/bytes.js +21 -0
- package/src/platform/compression.js +24 -0
- package/src/platform/crypto.js +17 -0
- package/src/resolution/module.js +64 -0
- package/src/types/assembly/instruction-editing.d.ts +33 -0
- package/src/types/assembly/method-removal.d.ts +3 -0
- package/src/types/assembly/pointer-table-editing.d.ts +3 -0
- package/src/types/assembly/portable-pdb-editing.d.ts +23 -0
- package/src/types/binary/errors.d.ts +7 -0
- package/src/types/binary/reader.d.ts +30 -0
- package/src/types/il/body.d.ts +51 -0
- package/src/types/il/instructions.d.ts +33 -0
- package/src/types/il/opcodes.d.ts +2 -0
- package/src/types/il/rewrite.d.ts +38 -0
- package/src/types/index.d.ts +864 -0
- package/src/types/metadata/classic-pdb.d.ts +688 -0
- package/src/types/metadata/custom-attributes.d.ts +28 -0
- package/src/types/metadata/portable-pdb.d.ts +296 -0
- package/src/types/metadata/resources.d.ts +1 -0
- package/src/types/metadata/root-writer.d.ts +1 -0
- package/src/types/metadata/signatures.d.ts +5 -0
- package/src/types/metadata/streams.d.ts +26 -0
- package/src/types/metadata/strong-name.d.ts +11 -0
- package/src/types/metadata/tables.d.ts +25 -0
- package/src/types/metadata/writer.d.ts +16 -0
- package/src/types/pe/headers.d.ts +160 -0
- package/src/types/platform/bytes.d.ts +3 -0
- package/src/types/platform/compression.d.ts +2 -0
- package/src/types/platform/crypto.d.ts +4 -0
- package/src/types/resolution/module.d.ts +12 -0
- package/src/types/writer/patch.d.ts +4 -0
- package/src/types/writer/pe.d.ts +9 -0
- package/src/types/writer/structural.d.ts +4 -0
- package/src/writer/patch.js +42 -0
- package/src/writer/pe.js +196 -0
- package/src/writer/structural.js +241 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { FormatError, UnsupportedFormatError } from "../binary/errors.js";
|
|
2
|
+
import { parseEmbeddedPortablePdbData } from "../pe/headers.js";
|
|
3
|
+
import {
|
|
4
|
+
addEmbeddedPortablePdbDocument as addPortablePdbDocument,
|
|
5
|
+
insertEmbeddedPortablePdbDocument as insertPortablePdbDocument,
|
|
6
|
+
addEmbeddedPortablePdbTypeDefinitionDocuments as addPortablePdbTypeDefinitionDocuments,
|
|
7
|
+
removeEmbeddedPortablePdbDocument as removePortablePdbDocument,
|
|
8
|
+
replaceEmbeddedPortablePdbTypeDefinitionDocuments as replacePortablePdbTypeDefinitionDocuments,
|
|
9
|
+
replaceEmbeddedPortablePdbDocumentMetadata as replacePortablePdbDocumentMetadata,
|
|
10
|
+
replaceEmbeddedPortablePdbDocumentName as replacePortablePdbDocumentName,
|
|
11
|
+
replaceEmbeddedPortablePdbSequencePoints as replacePortablePdbSequencePoints,
|
|
12
|
+
replaceEmbeddedPortablePdbMethodDocument as replacePortablePdbMethodDocument,
|
|
13
|
+
replaceEmbeddedPortablePdbImportScope as replacePortablePdbImportScope,
|
|
14
|
+
replaceEmbeddedPortablePdbLocalScope as replacePortablePdbLocalScope,
|
|
15
|
+
replaceEmbeddedPortablePdbLocalVariable as replacePortablePdbLocalVariable,
|
|
16
|
+
replaceEmbeddedPortablePdbLocalConstant as replacePortablePdbLocalConstant,
|
|
17
|
+
replaceEmbeddedPortablePdbCustomDebugInformation as replacePortablePdbCustomDebugInformation,
|
|
18
|
+
replaceEmbeddedPortablePdbSource as replacePortablePdbSource,
|
|
19
|
+
addEmbeddedPortablePdbSource as addPortablePdbSource,
|
|
20
|
+
removeEmbeddedPortablePdbSource as removePortablePdbSource,
|
|
21
|
+
removeEmbeddedPortablePdbCustomDebugInformation as removePortablePdbCustomDebugInformation,
|
|
22
|
+
replaceEmbeddedPortablePdbSourceLink as replacePortablePdbSourceLink,
|
|
23
|
+
removeEmbeddedPortablePdbSourceLink as removePortablePdbSourceLink,
|
|
24
|
+
addEmbeddedPortablePdbSourceLink as addPortablePdbSourceLink
|
|
25
|
+
} from "../metadata/portable-pdb.js";
|
|
26
|
+
|
|
27
|
+
export function createPortablePdbEditor(hooks) {
|
|
28
|
+
return {
|
|
29
|
+
addEmbeddedPortablePdbDocument(debugEntryIndex, name, options) {
|
|
30
|
+
hooks.ensureEditable();
|
|
31
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
32
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
33
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
34
|
+
}
|
|
35
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
36
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
37
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
38
|
+
}
|
|
39
|
+
const data = addPortablePdbDocument(
|
|
40
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
41
|
+
name,
|
|
42
|
+
options
|
|
43
|
+
);
|
|
44
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
insertEmbeddedPortablePdbDocument(debugEntryIndex, options) {
|
|
48
|
+
hooks.ensureEditable();
|
|
49
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
50
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
51
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
52
|
+
}
|
|
53
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
54
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
55
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
56
|
+
}
|
|
57
|
+
const data = insertPortablePdbDocument(
|
|
58
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
59
|
+
options
|
|
60
|
+
);
|
|
61
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
addEmbeddedPortablePdbTypeDefinitionDocuments(
|
|
65
|
+
debugEntryIndex,
|
|
66
|
+
typeRid,
|
|
67
|
+
documentRids
|
|
68
|
+
) {
|
|
69
|
+
hooks.ensureEditable();
|
|
70
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
71
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
72
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
73
|
+
}
|
|
74
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
75
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
76
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
77
|
+
}
|
|
78
|
+
const data = addPortablePdbTypeDefinitionDocuments(
|
|
79
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
80
|
+
typeRid,
|
|
81
|
+
documentRids
|
|
82
|
+
);
|
|
83
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
removeEmbeddedPortablePdbDocument(debugEntryIndex, documentRid) {
|
|
87
|
+
hooks.ensureEditable();
|
|
88
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
89
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
90
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
91
|
+
}
|
|
92
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
93
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
94
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
95
|
+
}
|
|
96
|
+
const data = removePortablePdbDocument(
|
|
97
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
98
|
+
documentRid
|
|
99
|
+
);
|
|
100
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
replaceEmbeddedPortablePdbTypeDefinitionDocuments(
|
|
104
|
+
debugEntryIndex,
|
|
105
|
+
typeRid,
|
|
106
|
+
documentRids
|
|
107
|
+
) {
|
|
108
|
+
hooks.ensureEditable();
|
|
109
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
110
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
111
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
112
|
+
}
|
|
113
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
114
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
115
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
116
|
+
}
|
|
117
|
+
const data = replacePortablePdbTypeDefinitionDocuments(
|
|
118
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
119
|
+
typeRid,
|
|
120
|
+
documentRids
|
|
121
|
+
);
|
|
122
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
replaceEmbeddedPortablePdbDocumentMetadata(debugEntryIndex, documentRid, options) {
|
|
126
|
+
hooks.ensureEditable();
|
|
127
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
128
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
129
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
130
|
+
}
|
|
131
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
132
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
133
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
134
|
+
}
|
|
135
|
+
const data = replacePortablePdbDocumentMetadata(
|
|
136
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
137
|
+
documentRid,
|
|
138
|
+
options
|
|
139
|
+
);
|
|
140
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
replaceEmbeddedPortablePdbDocumentName(debugEntryIndex, documentRid, name, separator) {
|
|
144
|
+
hooks.ensureEditable();
|
|
145
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
146
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
147
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
148
|
+
}
|
|
149
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
150
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
151
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
152
|
+
}
|
|
153
|
+
const data = replacePortablePdbDocumentName(
|
|
154
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
155
|
+
documentRid,
|
|
156
|
+
name,
|
|
157
|
+
separator
|
|
158
|
+
);
|
|
159
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
replaceEmbeddedPortablePdbSequencePoints(debugEntryIndex, methodRid, sequencePoints) {
|
|
163
|
+
hooks.ensureEditable();
|
|
164
|
+
if (this._portablePdbOffsetBaselines.has(methodRid)) {
|
|
165
|
+
throw new UnsupportedFormatError(
|
|
166
|
+
"Portable PDB sequence points cannot be replaced after IL offset remapping has been staged"
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
170
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
171
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
172
|
+
}
|
|
173
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
174
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
175
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
176
|
+
}
|
|
177
|
+
const data = replacePortablePdbSequencePoints(
|
|
178
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
179
|
+
methodRid,
|
|
180
|
+
sequencePoints
|
|
181
|
+
);
|
|
182
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
replaceEmbeddedPortablePdbMethodDocument(debugEntryIndex, methodRid, documentRid) {
|
|
186
|
+
hooks.ensureEditable();
|
|
187
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
188
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
189
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
190
|
+
}
|
|
191
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
192
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
193
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
194
|
+
}
|
|
195
|
+
const data = replacePortablePdbMethodDocument(
|
|
196
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
197
|
+
methodRid,
|
|
198
|
+
documentRid
|
|
199
|
+
);
|
|
200
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
replaceEmbeddedPortablePdbImportScope(debugEntryIndex, importScopeRid, options) {
|
|
204
|
+
hooks.ensureEditable();
|
|
205
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
206
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
207
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
208
|
+
}
|
|
209
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
210
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
211
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
212
|
+
}
|
|
213
|
+
const data = replacePortablePdbImportScope(
|
|
214
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
215
|
+
importScopeRid,
|
|
216
|
+
options
|
|
217
|
+
);
|
|
218
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
replaceEmbeddedPortablePdbLocalScope(debugEntryIndex, localScopeRid, options) {
|
|
222
|
+
hooks.ensureEditable();
|
|
223
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
224
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
225
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
226
|
+
}
|
|
227
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
228
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
229
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
230
|
+
}
|
|
231
|
+
const pdb = hooks.getPortablePdbForEdit(debugEntryIndex, entry);
|
|
232
|
+
const localScope = pdb.tables.rows.get(50)?.[localScopeRid - 1];
|
|
233
|
+
if (!localScope || localScope.rid !== localScopeRid) {
|
|
234
|
+
throw new RangeError(`Invalid Portable PDB LocalScope row ID ${localScopeRid}`);
|
|
235
|
+
}
|
|
236
|
+
if (this._portablePdbOffsetBaselines.has(localScope.Method.rid)) {
|
|
237
|
+
throw new UnsupportedFormatError(
|
|
238
|
+
"Portable PDB LocalScope offsets cannot be replaced after IL offset remapping has been staged"
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const data = replacePortablePdbLocalScope(pdb, localScopeRid, options);
|
|
242
|
+
const updatedPdb = parseEmbeddedPortablePdbData(data, 0, data.length);
|
|
243
|
+
const updatedScope = updatedPdb.tables.rows.get(50)?.[localScopeRid - 1];
|
|
244
|
+
const body = this.getMethodBody(localScope.Method.rid);
|
|
245
|
+
if (!updatedScope || !body) {
|
|
246
|
+
throw new FormatError(
|
|
247
|
+
`Portable PDB LocalScope row ${localScopeRid} does not resolve to a method body`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
const isBoundary = (offset) => offset === body.codeSize ||
|
|
251
|
+
body.instructions.some((instruction) => instruction.offset === offset);
|
|
252
|
+
const endOffset = updatedScope.StartOffset + updatedScope.Length;
|
|
253
|
+
if (!isBoundary(updatedScope.StartOffset) || !isBoundary(endOffset)) {
|
|
254
|
+
throw new FormatError(
|
|
255
|
+
`Portable PDB LocalScope row ${localScopeRid} does not align with MethodDef ${localScope.Method.rid} instruction boundaries`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
replaceEmbeddedPortablePdbLocalVariable(debugEntryIndex, localVariableRid, options) {
|
|
262
|
+
hooks.ensureEditable();
|
|
263
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
264
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
265
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
266
|
+
}
|
|
267
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
268
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
269
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
270
|
+
}
|
|
271
|
+
const data = replacePortablePdbLocalVariable(
|
|
272
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
273
|
+
localVariableRid,
|
|
274
|
+
options
|
|
275
|
+
);
|
|
276
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
replaceEmbeddedPortablePdbLocalConstant(debugEntryIndex, localConstantRid, options) {
|
|
280
|
+
hooks.ensureEditable();
|
|
281
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
282
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
283
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
284
|
+
}
|
|
285
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
286
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
287
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
288
|
+
}
|
|
289
|
+
const data = replacePortablePdbLocalConstant(
|
|
290
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
291
|
+
localConstantRid,
|
|
292
|
+
options
|
|
293
|
+
);
|
|
294
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
replaceEmbeddedPortablePdbCustomDebugInformation(
|
|
298
|
+
debugEntryIndex,
|
|
299
|
+
customDebugInformationRid,
|
|
300
|
+
value
|
|
301
|
+
) {
|
|
302
|
+
hooks.ensureEditable();
|
|
303
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
304
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
305
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
306
|
+
}
|
|
307
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
308
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
309
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
310
|
+
}
|
|
311
|
+
const data = replacePortablePdbCustomDebugInformation(
|
|
312
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
313
|
+
customDebugInformationRid,
|
|
314
|
+
value
|
|
315
|
+
);
|
|
316
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
replaceEmbeddedPortablePdbSource(debugEntryIndex, documentRid, source, options) {
|
|
320
|
+
hooks.ensureEditable();
|
|
321
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
322
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
323
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
324
|
+
}
|
|
325
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
326
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
327
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
328
|
+
}
|
|
329
|
+
const data = replacePortablePdbSource(
|
|
330
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
331
|
+
documentRid,
|
|
332
|
+
source,
|
|
333
|
+
options
|
|
334
|
+
);
|
|
335
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
addEmbeddedPortablePdbSource(debugEntryIndex, documentRid, source, options) {
|
|
339
|
+
hooks.ensureEditable();
|
|
340
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
341
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
342
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
343
|
+
}
|
|
344
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
345
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
346
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
347
|
+
}
|
|
348
|
+
const data = addPortablePdbSource(
|
|
349
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
350
|
+
documentRid,
|
|
351
|
+
source,
|
|
352
|
+
options
|
|
353
|
+
);
|
|
354
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
removeEmbeddedPortablePdbSource(debugEntryIndex, documentRid) {
|
|
358
|
+
hooks.ensureEditable();
|
|
359
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
360
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
361
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
362
|
+
}
|
|
363
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
364
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
365
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
366
|
+
}
|
|
367
|
+
const data = removePortablePdbSource(
|
|
368
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
369
|
+
documentRid
|
|
370
|
+
);
|
|
371
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
removeEmbeddedPortablePdbCustomDebugInformation(debugEntryIndex, recordRid) {
|
|
375
|
+
hooks.ensureEditable();
|
|
376
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
377
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
378
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
379
|
+
}
|
|
380
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
381
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
382
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
383
|
+
}
|
|
384
|
+
const data = removePortablePdbCustomDebugInformation(
|
|
385
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
386
|
+
recordRid
|
|
387
|
+
);
|
|
388
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
replaceEmbeddedPortablePdbSourceLink(debugEntryIndex, sourceLink) {
|
|
392
|
+
hooks.ensureEditable();
|
|
393
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
394
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
395
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
396
|
+
}
|
|
397
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
398
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
399
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
400
|
+
}
|
|
401
|
+
const data = replacePortablePdbSourceLink(
|
|
402
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
403
|
+
sourceLink
|
|
404
|
+
);
|
|
405
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
removeEmbeddedPortablePdbSourceLink(debugEntryIndex) {
|
|
409
|
+
hooks.ensureEditable();
|
|
410
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
411
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
412
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
413
|
+
}
|
|
414
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
415
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
416
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
417
|
+
}
|
|
418
|
+
const data = removePortablePdbSourceLink(
|
|
419
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry)
|
|
420
|
+
);
|
|
421
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
addEmbeddedPortablePdbSourceLink(debugEntryIndex, sourceLink) {
|
|
425
|
+
hooks.ensureEditable();
|
|
426
|
+
if (!Number.isInteger(debugEntryIndex) || debugEntryIndex < 0 ||
|
|
427
|
+
debugEntryIndex >= this.pe.debugDirectoryEntries.length) {
|
|
428
|
+
throw new RangeError(`Invalid debug-directory entry index ${debugEntryIndex}`);
|
|
429
|
+
}
|
|
430
|
+
const entry = this.pe.debugDirectoryEntries[debugEntryIndex];
|
|
431
|
+
if (entry.type !== 17 || !entry.embeddedPortablePdb) {
|
|
432
|
+
throw new UnsupportedFormatError("Debug-directory entry is not an embedded Portable PDB");
|
|
433
|
+
}
|
|
434
|
+
const data = addPortablePdbSourceLink(
|
|
435
|
+
hooks.getPortablePdbForEdit(debugEntryIndex, entry),
|
|
436
|
+
sourceLink
|
|
437
|
+
);
|
|
438
|
+
return this.replaceEmbeddedPortablePdb(debugEntryIndex, data);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class FormatError extends Error {
|
|
2
|
+
constructor(message, { offset, context } = {}) {
|
|
3
|
+
const location = Number.isInteger(offset) ? ` at file offset 0x${offset.toString(16)}` : "";
|
|
4
|
+
const scope = context ? ` (${context})` : "";
|
|
5
|
+
super(`${message}${location}${scope}`);
|
|
6
|
+
this.name = "FormatError";
|
|
7
|
+
this.offset = offset;
|
|
8
|
+
this.context = context;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class UnsupportedFormatError extends FormatError {
|
|
13
|
+
constructor(message, options = {}) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = "UnsupportedFormatError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { FormatError } from "./errors.js";
|
|
2
|
+
|
|
3
|
+
export class BinaryReader {
|
|
4
|
+
constructor(input, { start = 0, length, baseOffset = 0, context } = {}) {
|
|
5
|
+
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
6
|
+
const available = bytes.byteLength - start;
|
|
7
|
+
const size = length ?? available;
|
|
8
|
+
if (!Number.isInteger(start) || !Number.isInteger(size) || start < 0 || size < 0 || size > available) {
|
|
9
|
+
throw new RangeError("Invalid binary reader range");
|
|
10
|
+
}
|
|
11
|
+
this.bytes = bytes.subarray(start, start + size);
|
|
12
|
+
this.view = new DataView(this.bytes.buffer, this.bytes.byteOffset, this.bytes.byteLength);
|
|
13
|
+
this.baseOffset = baseOffset;
|
|
14
|
+
this.context = context;
|
|
15
|
+
this.position = 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get remaining() {
|
|
19
|
+
return this.bytes.length - this.position;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get absoluteOffset() {
|
|
23
|
+
return this.baseOffset + this.position;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
ensure(size) {
|
|
27
|
+
if (!Number.isInteger(size) || size < 0 || size > this.remaining) {
|
|
28
|
+
throw new FormatError(`Read of ${size} byte(s) exceeds the available ${this.remaining}`, {
|
|
29
|
+
offset: this.absoluteOffset,
|
|
30
|
+
context: this.context
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
seek(position) {
|
|
36
|
+
if (!Number.isInteger(position) || position < 0 || position > this.bytes.length) {
|
|
37
|
+
throw new FormatError(`Invalid seek position ${position}`, {
|
|
38
|
+
offset: this.baseOffset + Math.max(0, position || 0),
|
|
39
|
+
context: this.context
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
this.position = position;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
skip(size) {
|
|
46
|
+
this.ensure(size);
|
|
47
|
+
this.position += size;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
align(alignment) {
|
|
51
|
+
if (!Number.isInteger(alignment) || alignment < 1 || (alignment & (alignment - 1)) !== 0) {
|
|
52
|
+
throw new RangeError("Alignment must be a positive power of two");
|
|
53
|
+
}
|
|
54
|
+
this.skip((-this.position) & (alignment - 1));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
u1() {
|
|
58
|
+
this.ensure(1);
|
|
59
|
+
return this.view.getUint8(this.position++);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
i1() {
|
|
63
|
+
this.ensure(1);
|
|
64
|
+
return this.view.getInt8(this.position++);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
u2() {
|
|
68
|
+
this.ensure(2);
|
|
69
|
+
const value = this.view.getUint16(this.position, true);
|
|
70
|
+
this.position += 2;
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
i2() {
|
|
75
|
+
this.ensure(2);
|
|
76
|
+
const value = this.view.getInt16(this.position, true);
|
|
77
|
+
this.position += 2;
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
u4() {
|
|
82
|
+
this.ensure(4);
|
|
83
|
+
const value = this.view.getUint32(this.position, true);
|
|
84
|
+
this.position += 4;
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
i4() {
|
|
89
|
+
this.ensure(4);
|
|
90
|
+
const value = this.view.getInt32(this.position, true);
|
|
91
|
+
this.position += 4;
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
u8() {
|
|
96
|
+
this.ensure(8);
|
|
97
|
+
const value = this.view.getBigUint64(this.position, true);
|
|
98
|
+
this.position += 8;
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
f4() {
|
|
103
|
+
this.ensure(4);
|
|
104
|
+
const value = this.view.getFloat32(this.position, true);
|
|
105
|
+
this.position += 4;
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
f8() {
|
|
110
|
+
this.ensure(8);
|
|
111
|
+
const value = this.view.getFloat64(this.position, true);
|
|
112
|
+
this.position += 8;
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
take(size) {
|
|
117
|
+
this.ensure(size);
|
|
118
|
+
const result = this.bytes.subarray(this.position, this.position + size);
|
|
119
|
+
this.position += size;
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
slice(size, context = this.context) {
|
|
124
|
+
const start = this.position;
|
|
125
|
+
this.ensure(size);
|
|
126
|
+
this.position += size;
|
|
127
|
+
return new BinaryReader(this.bytes, {
|
|
128
|
+
start,
|
|
129
|
+
length: size,
|
|
130
|
+
baseOffset: this.baseOffset + start,
|
|
131
|
+
context
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function readCompressedUInt(reader) {
|
|
137
|
+
const first = reader.u1();
|
|
138
|
+
if ((first & 0x80) === 0) return first;
|
|
139
|
+
const offset = reader.absoluteOffset - 1;
|
|
140
|
+
let value;
|
|
141
|
+
if ((first & 0xc0) === 0x80) {
|
|
142
|
+
value = ((first & 0x3f) << 8) | reader.u1();
|
|
143
|
+
if (value < 0x80) {
|
|
144
|
+
throw new FormatError("Non-canonical compressed unsigned integer", {
|
|
145
|
+
offset,
|
|
146
|
+
context: reader.context
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
if ((first & 0xe0) === 0xc0) {
|
|
152
|
+
value = ((first & 0x1f) * 0x1000000) +
|
|
153
|
+
(reader.u1() << 16) +
|
|
154
|
+
(reader.u1() << 8) +
|
|
155
|
+
reader.u1();
|
|
156
|
+
if (value < 0x4000) {
|
|
157
|
+
throw new FormatError("Non-canonical compressed unsigned integer", {
|
|
158
|
+
offset,
|
|
159
|
+
context: reader.context
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
throw new FormatError("Invalid compressed unsigned integer prefix", {
|
|
165
|
+
offset,
|
|
166
|
+
context: reader.context
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function readCompressedInt(reader) {
|
|
171
|
+
const first = reader.u1();
|
|
172
|
+
let encoded;
|
|
173
|
+
let signOffset;
|
|
174
|
+
let width;
|
|
175
|
+
if ((first & 0x80) === 0) {
|
|
176
|
+
encoded = first;
|
|
177
|
+
signOffset = 0x40;
|
|
178
|
+
width = 1;
|
|
179
|
+
} else if ((first & 0xc0) === 0x80) {
|
|
180
|
+
encoded = ((first & 0x3f) << 8) | reader.u1();
|
|
181
|
+
signOffset = 0x2000;
|
|
182
|
+
width = 2;
|
|
183
|
+
} else if ((first & 0xe0) === 0xc0) {
|
|
184
|
+
encoded = (first & 0x1f) * 0x1000000 +
|
|
185
|
+
reader.u1() * 0x10000 +
|
|
186
|
+
reader.u1() * 0x100 +
|
|
187
|
+
reader.u1();
|
|
188
|
+
signOffset = 0x10000000;
|
|
189
|
+
width = 4;
|
|
190
|
+
} else {
|
|
191
|
+
throw new FormatError("Invalid compressed signed integer prefix", {
|
|
192
|
+
offset: reader.absoluteOffset - 1,
|
|
193
|
+
context: reader.context
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const value = Math.floor(encoded / 2);
|
|
197
|
+
const signedValue = (encoded & 1) === 0 ? value : value - signOffset;
|
|
198
|
+
if ((width === 2 && signedValue >= -64 && signedValue <= 63) ||
|
|
199
|
+
(width === 4 && signedValue >= -0x2000 && signedValue <= 0x1fff)) {
|
|
200
|
+
throw new FormatError("Non-canonical compressed signed integer", {
|
|
201
|
+
offset: reader.absoluteOffset - width,
|
|
202
|
+
context: reader.context
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return signedValue;
|
|
206
|
+
}
|